作業需求:
模擬實現一個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"}