作业需求:
模拟实现一个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")
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()
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 }
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")
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()
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)
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对象
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!")
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) #用户交互开始
.json
{"status": 0, "expire_date": "2021-01-01", "credit": 15000, "pay_day": 22, "balance": 13650, "enroll_date": "2016-01-02", "id": 22, "password": "abc"}