作业需求:
模拟实现一个ATM + 购物商城程序
额度 15000或自定义
实现购物商城,买东西加入 购物车,调用信用卡接口结账
可以提现,手续费5%
支持多账户登录
支持账户间转账
记录每月日常消费流水
提供还款接口
ATM记录操作日志
提供管理接口,包括添加账户、用户额度,冻结账户等。。。
用户认证用装饰器
作业思路
实现购物商城和信用卡的ATM功能
本程序有6个模块,实现了购物和ATM的取款、还款、转账、账单查看和用户管理的功能。
程序结构:
test
├── README
├── ATM #ATM主程目录
│ ├── __init__.py
│ ├── bin #ATM 执行文件 目录
│ │ ├── __init__.py
│ │ ├── atm.py #ATM 执行程序
│ ├── conf #配置文件
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── core #主要程序逻辑都 在这个目录 里
│ │ ├── __init__.py
│ │ ├── accounts.py #用于从文件里加载和存储账户数据
│ │ ├── auth.py #用户认证模块
│ │ ├── db_handle.py #数据库连接引擎
│ │ ├── log.py #日志记录模块
│ │ ├── main.py #主逻辑交互程序
│ │ └── transaction.py #记账\还钱\取钱等所有的与账户金额相关的操作都 在这
│ ├── db #用户数据存储的地方
│ │ ├── __init__.py
│ │ └── accounts #存各个用户的账户数据 ,一个用户一个文件
│ │ └── zcl.json #一个用户账户示例文件
│ └── log #日志目录
│ ├── __init__.py
│ ├── access.log #用户访问和操作的相关日志
│ └── transactions.log #所有的交易日志
└── shopping #电子商城程序
├── shopping_mol #购物商城的程序
└── __init__.py
开始先运行atm.py时执行程序,直接到main下,输入正确用户zcl和密码abc,才能进行下一步的操作,然后列出atm的功能列表(还款、取款、转账、查看等)
shopping是一个独立的程序,调用了还款的金额,购物结束后把剩余的金额在写入到文件中,存入到信用卡中。
流程图
shopping_mol
1 importos,json2
3 dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))4 print(dir)5 file="%s/ATM/db/accounts/zcl.json"%dir6 print(file)7 with open(file, "r", encoding="utf-8") as f:8 account_data =json.load(f)9 print(account_data)10
11
12
13 product_list =[14 ("Apple Iphone",6000),15 ("Apple Watch",4600),16 ("Books",600),17 ("Bike",750),18 ("cups",120),19 ("Apple",50),20 ("banana",60),21 ]22 shopping_list =[]23 salary = account_data["balance"]24
25 whileTrue:26 for index,item inenumerate(product_list):27 print(index,item)28 user_choice = input ("Enter the serial number:")29 ifuser_choice.isdigit():30 user_choice =int (user_choice)31 if user_choice =0:32 p_item =product_list[user_choice]33 if p_item[1] <=salary:34 shopping_list.append(p_item)35 salary -= p_item[1]36 with open(file,"w+",encoding="utf-8") as f:37 account_data["balance"]=salary38 print(account_data)39 json.dump(account_data,f)40 print ("Added %s into your shopping cart,your current balance is %s"%(p_item,salary))41 else:42 print ("Your balance is not enough!!")43 else:44 print ("The goods you entered do not exist")45
46 elif user_choice == "q":47 print ("====shopping list====")48 for p inshopping_list:49 print(p)50 print ("Your current balance is %s"%salary)51 exit()52 else:53 print ("invalid option")
View Code
atm
1 #ATM程序的执行文件
2 importos3 importsys4
5 dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #找到路径
6 sys.path.insert(0,dir) #添加路径
7
8
9 print(dir)10 print(sys.path)11
12 #将main.py里面的所有代码封装成main变量
13 from core importmain14
15 if __name__ == '__main__':16 main.run()
View Code
settings
1 #配置文件
2 importlogging3 importos4
5 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#找到路径
6
7 LOGIN_LEVEL = logging.INFO#定义日志的记录级别
8
9 DATABASE ={10 "db_tool":"file_storage", #文件存储,这里可拓展成数据库形式的
11 "name":"accounts", #db下的文件名
12 "path":"%s/db"%BASE_DIR13 }14 #print(DATABASE)
15 #日志类型
16 LOGIN_TYPE={17 "access":"access.log",18 "transaction":"transaction.log"
19 }20
21 #用户交易类型,每个类型对应一个字典,包括帐户金额变动方式,利息
22 TRANSACTION_TYPE={23 "repay":{"action":"plus","interest":0},24 "withdraw":{"action":"minus","interest":0.05},25 "transfer":{"action":"minus","interest":0}26 }
View Code
account
1 """
2 用于处理用户信息的load or dump3 每进行一个操作就将信息更新到数据库4 """
5 from core importdb_handle6 from conf importsettings7 importjson8
9 defload_account(account_id):10 """
11 将用户信息从文件中load出来12 :return: 用户信息的字典13 """
14 #返回路径 ATM/db/accounts
15 db_path =db_handle.handle(settings.DATABASE)16 account_file = "%s/%s.json" %(db_path, account_id)17 with open(account_file, "r", encoding="utf-8") as f:18 account_data =json.load(f)19 returnaccount_data20
21
22 defdump_account(account_data):23 """
24 将已更改的用户信息更新到用户文件25 :param account_data: 每操作后用户的信息26 :return:27 """
28 db_path =db_handle.handle(settings.DATABASE)29 account_file = "%s/%s.json" % (db_path, account_data["id"])30 with open(account_file, "w+", encoding="utf-8") as f:31 json.dump(account_data, f)32
33 print("dump success")
View Code
auth
1 #认证模块
2 importos3 importjson4 importtime5
6 from core importdb_handle7 from conf importsettings8
9 defaccess_auth(account,password,log_obj):10 """
11 下面的access_login调用access_auth方法,用于登陆12 :param acount: 用户名13 :param password: 密码14 :return:如果未超期,返回字典,超期则打印相应提示15 """
16 db_path=db_handle.handle(settings.DATABASE) #调用db_handle下的handle方法,返回路径/db/accounts
17 print(db_path)18 account_file="%s/%s.json"%(db_path,account) #用户文件
19 print(account_file)20 if os.path.isfile(account_file): #如果用户文件存在(即用户存在)
21 with open(account_file,"r",encoding="utf-8") as f: #打开文件
22 account_data = json.load(f) #file_data为字典形式
23 print(account_data)24 if account_data["password"]==password:25 expire_time=time.mktime(time.strptime(account_data["expire_date"],"%Y-%m-%d"))26 #print(expire_time)
27 #print(time.strptime(account_data["expire_date"],"%Y-%m-%d"))
28 if time.time()>expire_time: #如果信用卡已超期
29 log_obj.error("Account [%s] had expired,Please contract the bank" %account)30 print("Account %s had expired,Please contract the bank"%account)31 else: #信用卡未超期,返回用户数据的字典
32 #print("return")
33 log_obj.info("Account [%s] logging success" %account)34 returnaccount_data35 else:36 log_obj.error("Account or Passworddoes not correct!")37 print("Account or Passworddoes not correct!")38 else:#用户不存在
39 log_obj.error("Account [%s] does not exist!" %account)40 print("Account [%s] does not exist!"%account)41
42
43 defaccess_login(user_data,log_obj):44 """
45 用记登陆,当登陆失败超过三次则退出46 :param user_data: main.py里面的字典47 :return:若用户帐号密码正确且信用卡未超期,返回用户数据的字典48 """
49 retry=050 while not user_data["is_authenticated"] and retry<3:51 account=input("Account:").strip()52 password= input("Password:").strip() #用户帐号密码正确且信用卡未超期,返回用户数据的字典
53 user_auth_data=access_auth(account,password,log_obj)54 ifuser_auth_data:55 user_data["is_authenticated"]=True #用户认证为True
56 user_data["account_id"]=account #用户帐号ID为帐号名
57 print("welcome %s"%account)58 returnuser_auth_data59 retry+=1
60 else:61 print("Account [%s] try logging too many times..." %account)62 log_obj.error("Account [%s] try logging too many times..." %account)63 exit()
View Code
db_handle
1 #处理与数据库的交互,若是file_db_storage,返回路径
2
3 deffile_db_handle(database):4 """
5 数据存在文件6 :param database:7 :return: 返回路径 ATM1/db/accounts8 """
9 db_path="%s/%s"%(database["path"],database["name"])10 #print(db_path)
11 returndb_path12
13 #def mysql_db_handle(database):
14 #"""
15 #处理mysql数据库,这里用文件来存数据,
16 #保留这个方法主要为了程序拓展性
17 #:return:
18 #"""
19 #pass
20
21 defhandle(database):22 """
23 对某种数据库形式处于是24 本程序用的是文件处理file_storage25 :param database: settings里面的DATABASE26 :return: 返回路径27 """
28 if database["db_tool"]=="file_storage":29 returnfile_db_handle(database)30 #if database["db_tool"]=="mysql":
31 #return mysql_db_handle(database)
View Code
log
1 importlogging2 from conf importsettings3 from core importdb_handle4
5 deflog(logging_type):6 """
7 main模块调用access_logger = log.log("access")8 :param logging_type: "access"9 return: 返回logger日志对象10 """
11 logger=logging.getLogger(logging_type) #传日志用例,生成日志对象
12 logger.setLevel(settings.LOGIN_LEVEL) #设置日志级别
13
14 ch = logging.StreamHandler() #日志打印到屏幕,获取对象
15 ch.setLevel(settings.LOGIN_LEVEL)16
17 #获取文件日志对象及日志文件
18 log_file="%s/log/%s"%(settings.BASE_DIR,settings.LOGIN_TYPE[logging_type])19 fh =logging.FileHandler(log_file)20 fh.setLevel(settings.LOGIN_LEVEL)21
22 #日志格式
23 formatter=logging.Formatter("%(asctime)s-%(name)s-%(levelname)s-%(message)s")24
25 #输出格式
26 ch.setFormatter(formatter)27 fh.setFormatter(formatter)28
29 #把日志打印到指定的handler
30 logger.addHandler(ch)31 logger.addHandler(fh)32
33 return logger #log方法返回logger对象
View Code
transaction
1 """
2 交易模块,处理用户金额移动3 """
4 from conf importsettings5 from core importaccount6 from core importlog7
8 def make_transaction(account_data,transcation_type,amount,log_obj,**kwargs):9 """
10 处理用户的交易11 :param account_data:字典,用户的帐户信息12 :param transaction_type:用户交易类型,repay or withdraw...13 :param amount:交易金额14 :return:用户交易后帐户的信息15 """
16 amount=float(amount) #将字符串类型转换为float类型
17 if transcation_type insettings.TRANSACTION_TYPE:18 interest=amount*settings.TRANSACTION_TYPE[transcation_type]["interest"] #利息计算
19 old_balace= account_data["balance"] #用户原金额
20 print(interest,old_balace)21 #如果帐户金额变化方式是"plus",加钱
22 if settings.TRANSACTION_TYPE[transcation_type]["action"]=="plus":23 new_balance=old_balace+amount+interest24 log_obj.info("Your account repay%s,your account new balance is %s"%(amount,new_balance))25 #如果帐户金额变化方式是"minus",减钱
26 elif settings.TRANSACTION_TYPE[transcation_type]["action"]=="minus":27 new_balance=old_balace-amount-interest28 log_obj.info("Your account withdraw%s,your account new balance is %s" %(amount, new_balance))29 if new_balance<0:30 print("Your Credit [%s] is not enough for transaction [-%s],"
31 "and Now your current balance is [%s]" % (account_data["credit"], (amount+interest), old_balace))32 return
33 account_data["balance"]=new_balance34 account.dump_account(account_data) #调用core下account模块将已更改的用户信息更新到用户文件
35 returnaccount_data36 else:37 print("Transaction is not exist!")
View Code
main
1 """
2 主逻辑交互模块3 """
4 from core importauth5 from core importlog6 from core importtransaction7 from core importaccount8 from conf importsettings9 from core importdb_handle10
11 importos12
13
14 #用户数据信息
15 user_data ={16 'account_id':None, #帐号ID
17 'is_authenticated':False, #是否认证
18 'account_data':None #帐号数据
19
20 }21
22 #调用log文件下的log方法,返回日志对象
23 access_logger = log.log("access")24 transaction_logger = log.log("transaction")25
26
27
28 defaccount_info(access_data):29 """
30 access_data:包括ID,is_authenticaed,用户帐号信息31 查看用户帐户信息32 :return:33 """
34 print(access_data)35
36
37
38
39 defrepay(access_data):40 """
41 access_data:包括ID,is_authenticaed,用户帐号信息42 还款43 :return:44 """
45 print(access_data)46 print("repay")47 #调用account模块的load_account方法,从数据库从load出用户信息
48 account_data = account.load_account(access_data["account_id"])49 print(account_data)50 current_balance = """
51 -------------BALANCE INFO--------------52 Credit:%s53 Balance:%s54 """ % (account_data["credit"], account_data["balance"])55 back_flag =False56 while notback_flag:57 print(current_balance)58 repay_amount = input("\033[31;1mInput repay amount(b=back):\033[0m").strip()59 #如果用户输入整型数字
60 if len(repay_amount) > 0 andrepay_amount.isdigit():61 #调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
62 new_account_data = transaction.make_transaction(account_data, "repay", repay_amount,transaction_logger)63 ifnew_account_data:64 print("\033[42;1mNew Balance:%s\033[0m" % new_account_data["balance"])65
66 else:67 print("\033[31;1m%s is not valid amount,Only accept interger!\033[0m" %repay_amount)68
69 if repay_amount =="b" or repay_amount == "back":70 back_flag =True71
72 defwithdraw(access_data):73 """
74 取款75 :return:76 """
77 print(access_data)78 print("withdraw")79 #调用account模块的load_account方法,从数据库从load出用户信息
80 account_data = account.load_account(access_data["account_id"])81 print(account_data)82 current_balance = """
83 -------------BALANCE INFO--------------84 Credit:%s85 Balance:%s86 """ % (account_data["credit"], account_data["balance"])87 back_flag =False88 while notback_flag:89 print(current_balance)90 withdraw_amount = input("\033[31;1mInput withdraw amount(b=back):\033[0m").strip()91 #如果用户输入整型数字
92 if len(withdraw_amount) > 0 andwithdraw_amount.isdigit():93 #调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
94 new_account_data = transaction.make_transaction(account_data, "withdraw", withdraw_amount,transaction_logger)95 ifnew_account_data:96 print("\033[42;1mNew Balance:%s\033[0m" % new_account_data["balance"])97
98 else:99 print("\033[31;1m%s is not valid amount,Only accept interger!\033[0m" %withdraw_amount)100
101 if withdraw_amount == "b" or withdraw_amount == "back":102 back_flag =True103
104
105 deftransfer(access_data):106 """
107 转帐108 :return:109 """
110 print(access_data)111 print("transfer")112 #调用account模块的load_account方法,从数据库从load出用户信息
113 account_data = account.load_account(access_data["account_id"])114 print(account_data)115 current_balance = """
116 -------------BALANCE INFO--------------117 Credit:%s118 Balance:%s119 """ % (account_data["credit"], account_data["balance"])120 back_flag =False121 while notback_flag:122 print(current_balance)123 transfer_amount = input("\033[31;1mInput transfer amount(b=back):\033[0m").strip()124 #如果用户输入整型数字
125 if len(transfer_amount) > 0 andtransfer_amount.isdigit():126 #调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
127 new_account_data = transaction.make_transaction(account_data, "transfer", transfer_amount,transaction_logger)128 ifnew_account_data:129 print("\033[42;1mNew Balance:%s\033[0m" % new_account_data["balance"])130 new_account_data2 = transaction.make_transaction(account_data, "repay", new_account_data["balance"],transaction_logger)131 ifnew_account_data2:132 print("\033[42;1mNew Balance2:%s\033[0m" % new_account_data2["balance"])133
134 else:135 print("\033[31;1m%s is not valid amount,Only accept interger!\033[0m" %transfer_amount)136
137 if transfer_amount == "b" or transfer_amount == "back":138 back_flag =True139
140
141 defpaycheck(access_data):142 """
143 账单查看144 :return:145 """
146
147 time=input("please input time(Y-M-D):")148 log_file = "%s/log/%s" % (settings.BASE_DIR, settings.LOGIN_TYPE["transaction"])149 print(log_file)150 with open (log_file,"r",encoding="utf-8") as f :151 for i inf.readlines():152 if time == i[0:10]:153 print(i)154 elif time == i[0:7]:155 print(i)156 elif time == i[0:4]:157 print(i)158
159
160
161
162 deflogout(access_data):163 """
164 退出登陆165 :return:166 """
167 q = input("If you want to quit,please input q:")168 if q =="q":169 exit()170
171
172 def interactive(access_data,**kwargs):173 """
174 用户交互175 :return:176 """
177 msg =(178 """
179 -------------ZhangChengLiang Bank---------------180 \033[31;1m181 1. 账户信息182 2. 存款183 3. 取款184 4. 转账185 5. 账单186 6. 退出187 \033[0m"""
188 )189 menu_dic ={190 "1":account_info,191 "2":repay,192 "3":withdraw,193 "4":transfer,194 "5":paycheck,195 "6":logout196 }197 flag =False198 while notflag:199 print(msg)200 choice = input("<<<: if choice inmenu_dic:202>
203 menu_dic[choice](access_data)204
205 else:206 print("\033[31;1mYou choice doesn't exist!\033[0m")207
208
209
210 defrun():211 """
212 当程序启动时调用,用于实现主要交互逻辑213 :return:214 """
215 #调用认证模块,返回用户文件json.load后的字典,传入access_logger日志对象
216 access_data =auth.access_login(user_data, access_logger)217 print("AA")218 if user_data["is_authenticated"]: #如果用户认证成功
219 print("has authenticated")220 #将用户文件的字典赋给user_data["account_data"]
221 user_data["account_data"] =access_data222 interactive(user_data) #用户交互开始
View Code
.json
{"status": 0, "expire_date": "2021-01-01", "credit": 15000, "pay_day": 22, "balance": 13650, "enroll_date": "2016-01-02", "id": 22, "password": "abc"}