作业需求:
模拟实现一个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 import os,json
2
3 dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
4 print(dir)
5 file="%s/ATM/db/accounts/zcl.json"%dir
6 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 while True:
26 for index,item in enumerate(product_list):
27 print (index,item)
28 user_choice = input ("Enter the serial number:")
29 if user_choice.isdigit():
30 user_choice = int (user_choice)
31 if user_choice <len (product_list) and 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"]=salary
38 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 in shopping_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 import os
3 import sys
4
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 import main
14
15 if __name__ == '__main__':
16 main.run()
View Code
settings
1 #配置文件
2 import logging
3 import os
4
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_DIR
13 }
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 dump
3 每进行一个操作就将信息更新到数据库
4 """
5 from core import db_handle
6 from conf import settings
7 import json
8
9 def load_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 return account_data
20
21
22 def dump_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 import os
3 import json
4 import time
5
6 from core import db_handle
7 from conf import settings
8
9 def access_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 return account_data
35 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 def access_login(user_data,log_obj):
44 """
45 用记登陆,当登陆失败超过三次则退出
46 :param user_data: main.py里面的字典
47 :return:若用户帐号密码正确且信用卡未超期,返回用户数据的字典
48 """
49 retry=0
50 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 if user_auth_data:
55 user_data["is_authenticated"]=True #用户认证为True
56 user_data["account_id"]=account #用户帐号ID为帐号名
57 print("welcome %s"%account)
58 return user_auth_data
59 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 def file_db_handle(database):
4 """
5 数据存在文件
6 :param database:
7 :return: 返回路径 ATM1/db/accounts
8 """
9 db_path="%s/%s"%(database["path"],database["name"])
10 #print(db_path)
11 return db_path
12
13 # def mysql_db_handle(database):
14 # """
15 # 处理mysql数据库,这里用文件来存数据,
16 # 保留这个方法主要为了程序拓展性
17 # :return:
18 # """
19 # pass
20
21 def handle(database):
22 """
23 对某种数据库形式处于是
24 本程序用的是文件处理file_storage
25 :param database: settings里面的DATABASE
26 :return: 返回路径
27 """
28 if database["db_tool"]=="file_storage":
29 return file_db_handle(database)
30 # if database["db_tool"]=="mysql":
31 # return mysql_db_handle(database)
View Code
log
1 import logging
2 from conf import settings
3 from core import db_handle
4
5 def log(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 import settings
5 from core import account
6 from core import log
7
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 in settings.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+interest
24 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-interest
28 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_balance
34 account.dump_account(account_data) #调用core下account模块将已更改的用户信息更新到用户文件
35 return account_data
36 else:
37 print("Transaction is not exist!")
View Code
main
1 """
2 主逻辑交互模块
3 """
4 from core import auth
5 from core import log
6 from core import transaction
7 from core import account
8 from conf import settings
9 from core import db_handle
10
11 import os
12
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 def account_info(access_data):
29 """
30 access_data:包括ID,is_authenticaed,用户帐号信息
31 查看用户帐户信息
32 :return:
33 """
34 print(access_data)
35
36
37
38
39 def repay(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:%s
53 Balance:%s
54 """ % (account_data["credit"], account_data["balance"])
55 back_flag = False
56 while not back_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 and repay_amount.isdigit():
61 #调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
62 new_account_data = transaction.make_transaction(account_data, "repay", repay_amount,transaction_logger)
63 if new_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 = True
71
72 def withdraw(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:%s
85 Balance:%s
86 """ % (account_data["credit"], account_data["balance"])
87 back_flag = False
88 while not back_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 and withdraw_amount.isdigit():
93 # 调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
94 new_account_data = transaction.make_transaction(account_data, "withdraw", withdraw_amount,transaction_logger)
95 if new_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 = True
103
104
105 def transfer(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:%s
118 Balance:%s
119 """ % (account_data["credit"], account_data["balance"])
120 back_flag = False
121 while not back_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 and transfer_amount.isdigit():
126 # 调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
127 new_account_data = transaction.make_transaction(account_data, "transfer", transfer_amount,transaction_logger)
128 if new_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 if new_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 = True
139
140
141 def paycheck(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 in f.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 def logout(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;1m
181 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":logout
196 }
197 flag = False
198 while not flag:
199 print(msg)
200 choice = input("<<<:").strip()
201 if choice in menu_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 def run():
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_data
222 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"}