模擬實現一個ATM + 購物商城程序 額度 15000或自定義 實現購物商城,買東西加入 購物車,調用信用卡接口結帳 能夠提現,手續費5% 每個月22號出帳單,每個月10號爲還款日,過時未還,按欠款總額 萬分之5 每日計息 支持多帳戶登陸 支持帳戶間轉帳 記錄每個月平常消費流水 提供還款接口 ATM記錄操做日誌 提供管理接口,包括添加帳戶、用戶額度,凍結帳戶等。。。 用戶認證用裝飾器 實現功能: 額度 15000或自定義 實現購物商城,買東西加入 購物車,調用信用卡接口結帳 能夠提現,手續費5% 支持多帳戶登陸 記錄每個月平常消費流水 提供還款接口 ATM記錄操做日誌 提供管理接口,包括添加帳戶、用戶額度,凍結帳戶等。。。 用戶認證用裝飾器
atm/ ├── README └── atm #ATM主程目錄 ├── __init__.py ├── bin #ATM 執行文件 目錄 │ ├── __init__.py │ ├── atm.py #ATM 執行程序 │ └── manage.py #ATM 管理端,未實現 ├── conf #配置文件 │ ├── __init__.py │ └── settings.py ├── core #主要程序邏輯都 在這個目錄 裏 │ ├── __init__.py │ ├── accounts.py #用於從文件里加載和存儲帳戶數據 │ ├── auth.py #用戶認證模塊 │ ├── db_handler.py #數據庫鏈接引擎 │ ├── logger.py #日誌記錄模塊 │ ├── main.py #主邏輯交互程序 │ └── transaction.py #記帳\還錢\取錢等全部的與帳戶金額相關的操做都 在這 ├── db #用戶數據存儲的地方 │ ├── __init__.py │ ├── account_sample.py #生成一個初始的帳戶數據 ,把這個數據 存成一個 以這個帳戶id爲文件名的文件,放在accounts目錄 就好了,程序本身去會這裏找 │ └── accounts #存各個用戶的帳戶數據 ,一個用戶一個文件 │ └── 1234.json #一個用戶帳戶示例文件 └── log #日誌目錄 ├── __init__.py ├── access.log #用戶訪問和操做的相關日誌 └── transactions.log #全部的交易日誌
bin/atm.py
python
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import os 5 import sys 6 base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 print(base_dir) 8 sys.path.append(base_dir) 9 10 from core import main 11 12 if __name__ == '__main__': 13 main.run()
conf/settings.py
mysql
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import os 4 import sys 5 import logging 6 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 8 9 DATABASE = { 10 'engine': 'file_storage', #support mysql,postgresql in the future 11 'name':'accounts', 12 'path': "%s/db" % BASE_DIR 13 } 14 15 16 LOG_LEVEL = logging.INFO 17 LOG_TYPES = { 18 'transaction': 'transactions.log', 19 'access': 'access.log', 20 } 21 22 TRANSACTION_TYPE = { 23 'repay':{'action':'plus', 'interest':0.03}, 24 'withdraw':{'action':'minus', 'interest':0.05}, 25 'transfer':{'action':'minus', 'interest':0.05}, 26 'consume':{'action':'minus', 'interest':0}, 27 28 }
core/main.py
git
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 ''' 5 main program handle module , handle all the user interaction stuff 6 7 ''' 8 9 from core import auth 10 from core import accounts 11 from core import logger 12 from core import accounts 13 from core import transaction 14 from core.auth import login_required 15 import time 16 17 #transaction logger 18 trans_logger = logger.logger('transaction') 19 #access logger 20 access_logger = logger.logger('access') 21 22 23 #temp account data ,only saves the data in memory 24 user_data = { 25 'account_id':None, 26 'is_authenticated':False, 27 'account_data':None 28 29 } 30 31 def account_info(acc_data): 32 account_data = accounts.load_current_balance(acc_data['account_id']) 33 data_info = u''' 34 \033[34;1m 帳號ID:%s 35 餘額: %s 36 信用度:%s 37 帳號註冊時間:%s 38 帳號過時時間:%s 39 工資天數:%s 40 \033[0m'''%(acc_data['account_id'], 41 account_data['balance'], 42 account_data['credit'], 43 account_data['enroll_date'], 44 account_data['expire_date'], 45 account_data['pay_day'],) 46 print(data_info) 47 48 49 @login_required 50 def repay(acc_data): 51 ''' 52 print current balance and let user repay the bill 53 :return: 54 ''' 55 account_data = accounts.load_current_balance(acc_data['account_id']) 56 #再從硬盤加載一次數據, 爲了確保數據是最新的 57 #for k,v in account_data.items(): 58 # print(k,v ) 59 current_balance= ''' --------- BALANCE INFO -------- 60 Credit : %s 61 Balance: %s''' %(account_data['credit'],account_data['balance']) 62 print(current_balance) 63 back_flag = False 64 while not back_flag: 65 repay_amount = input("\033[33;1mInput repay amount:\033[0m").strip() 66 if len(repay_amount) >0 and repay_amount.isdigit(): 67 #print('ddd 00') 68 new_balance = transaction.make_transaction(trans_logger,account_data,'repay', repay_amount) 69 if new_balance: 70 print('''\033[42;1mNew Balance:%s\033[0m''' %(new_balance['balance'])) 71 72 else: 73 print('\033[31;1m[%s] is not a valid amount, only accept integer!\033[0m' % repay_amount) 74 75 if repay_amount == 'b': 76 back_flag = True 77 def withdraw(acc_data): 78 ''' 79 print current balance and let user do the withdraw action 80 :param acc_data: 81 :return: 82 ''' 83 account_data = accounts.load_current_balance(acc_data['account_id']) 84 current_balance= ''' --------- BALANCE INFO -------- 85 Credit : %s 86 Balance: %s''' %(account_data['credit'],account_data['balance']) 87 print(current_balance) 88 back_flag = False 89 while not back_flag: 90 withdraw_amount = input("\033[33;1mInput withdraw amount:\033[0m").strip() 91 if len(withdraw_amount) >0 and withdraw_amount.isdigit(): 92 new_balance = transaction.make_transaction(trans_logger,account_data,'withdraw', withdraw_amount) 93 if new_balance: 94 print('''\033[42;1mNew Balance:%s\033[0m''' %(new_balance['balance'])) 95 96 else: 97 print('\033[31;1m[%s] is not a valid amount, only accept integer!\033[0m' % withdraw_amount) 98 99 if withdraw_amount == 'b': 100 back_flag = True 101 102 def transfer(acc_data): 103 pass 104 def pay_check(acc_data): 105 pass 106 def logout(acc_data): 107 exit() 108 109 110 def shopping(acc_data): 111 ''' 112 113 :param acc_data: 114 :return: 115 ''' 116 product_list = [ 117 ['Iphone7 Plus', 6500], 118 ['Iphone8 ', 8200], 119 ['MacBook Pro', 12000], 120 ['Python Book', 99], 121 ['Coffee', 33], 122 ['Bike', 666], 123 ['pen', 2] 124 ] 125 shopping_cart = [] 126 count = 0 127 salary = acc_data['account_data']['balance'] 128 while True: 129 account_data = accounts.load_current_balance(acc_data['account_id']) 130 print(">> 歡迎來到電子商城 您的餘額是 %s 元<<" % (salary)) 131 for index, i in enumerate(product_list): # 循環商品列表,商品列表索引 132 print("%s.\t%s\t%s" % (index, i[0], i[1])) # 打印商品列表,顯示商品列表索引 133 choice = input(">>請輸入商品序號或輸入 exit 退出商城>>: ").strip() 134 if len(choice) == 0: # 判斷輸入字符串是否爲空和字符串長度 135 print('-->您沒有選擇商品<--') 136 continue 137 if choice.isdigit(): # 判斷輸入的choice是否是一個數字 138 choice = int(choice) # 把輸入的字符串轉成整型 139 if choice < len(product_list) and choice >= 0: # 輸入的整數必須小於商品列表的數量 140 product_item = product_list[choice] # 獲取商品 141 if salary >= product_item[1]: # 拿現有金額跟商品對比,是否買得起 142 salary -= product_item[1] # 扣完商品的價格 143 shopping_cart.append(product_item) # 把選着的商品加入購物車 144 print("添加 \033[32;1m%s\033[0m 到購物車,您目前的金額是 \ 145 \033[31;1m%s\033[0m" % (product_item[0], salary)) 146 else: 147 print("對不起,您的金額不足,還差 \033[31;1m%s\033[0m" % (product_item[1] - salary,)) 148 else: 149 print("-->沒有此商品<--") 150 elif choice == "exit": 151 total_cost = 0 152 print("您的購物車列表:") 153 for i in shopping_cart: 154 print(i) 155 total_cost += i[1] 156 print("您的購物車總價是: \033[31;1m%s\033[0m" % (total_cost,)) 157 print("您目前的餘額是: \033[31;1m%s\033[0m" % (salary,)) 158 new_balance = transaction.make_transaction(trans_logger, account_data, 'withdraw', total_cost) 159 if new_balance: 160 print('''\033[42;1mNew Balance:%s\033[0m''' % (new_balance['balance'])) 161 break 162 163 164 def interactive(acc_data): 165 ''' 166 interact with user 167 :return: 168 ''' 169 menu = u''' 170 ------- hehe Bank --------- 171 \033[32;1m 172 1. 帳戶信息(實現) 173 2. 還款(實現) 174 3. 取款(實現) 175 4. 轉帳 176 5. 帳單 177 6. 商城(實現) 178 7. 退出(實現) 179 \033[0m''' 180 menu_dic = { 181 '1': account_info, 182 '2': repay, 183 '3': withdraw, 184 '4': transfer, 185 '5': pay_check, 186 '6': shopping, 187 '7': logout, 188 } 189 exit_flag = False 190 while not exit_flag: 191 print(menu) 192 user_option = input(">>:").strip() 193 if user_option in menu_dic: 194 #print('accdata',acc_data) 195 #acc_data['is_authenticated'] =False 196 menu_dic[user_option](acc_data) 197 198 else: 199 print("\033[31;1mOption does not exist!\033[0m") 200 def run(): 201 ''' 202 this function will be called right a way when the program started, here handles the user interaction stuff 203 :return: 204 ''' 205 acc_data = auth.acc_login(user_data,access_logger) 206 if user_data['is_authenticated']: 207 user_data['account_data'] = acc_data 208 interactive(user_data)
core/transaction.py
web
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 from conf import settings 5 from core import accounts 6 from core import logger 7 #transaction logger 8 9 10 11 def make_transaction(log_obj,account_data,tran_type,amount,**others): 12 ''' 13 deal all the user transactions 14 :param account_data: user account data 15 :param tran_type: transaction type 16 :param amount: transaction amount 17 :param others: mainly for logging usage 18 :return: 19 ''' 20 amount = float(amount) 21 if tran_type in settings.TRANSACTION_TYPE: 22 23 interest = amount * settings.TRANSACTION_TYPE[tran_type]['interest'] 24 old_balance = account_data['balance'] 25 if settings.TRANSACTION_TYPE[tran_type]['action'] == 'plus': 26 new_balance = old_balance + amount + interest 27 elif settings.TRANSACTION_TYPE[tran_type]['action'] == 'minus': 28 new_balance = old_balance - amount - interest 29 #check credit 30 if new_balance <0: 31 print('''\033[31;1mYour credit [%s] is not enough for this transaction [-%s], your current balance is 32 [%s]''' %(account_data['credit'],(amount + interest), old_balance )) 33 return 34 account_data['balance'] = new_balance 35 accounts.dump_account(account_data) #save the new balance back to file 36 log_obj.info("account:%s action:%s amount:%s interest:%s" % 37 (account_data['id'], tran_type, amount,interest) ) 38 return account_data 39 else: 40 print("\033[31;1mTransaction type [%s] is not exist!\033[0m" % tran_type)
core/accounts.py
sql
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import json 5 import time 6 from core import db_handler 7 from conf import settings 8 9 10 def load_current_balance(account_id): 11 ''' 12 return account balance and other basic info 13 :param account_id: 14 :return: 15 ''' 16 # db_path = db_handler.db_handler(settings.DATABASE) 17 # account_file = "%s/%s.json" %(db_path,account_id) 18 # 19 db_api = db_handler.db_handler() 20 data = db_api("select * from accounts where account=%s" % account_id) 21 22 return data 23 24 # with open(account_file) as f: 25 # acc_data = json.load(f) 26 # return acc_data 27 def dump_account(account_data): 28 ''' 29 after updated transaction or account data , dump it back to file db 30 :param account_data: 31 :return: 32 ''' 33 db_api = db_handler.db_handler() 34 data = db_api("update accounts where account=%s" % account_data['id'],account_data=account_data) 35 36 # db_path = db_handler.db_handler(settings.DATABASE) 37 # account_file = "%s/%s.json" %(db_path,account_data['id']) 38 # with open(account_file, 'w') as f: 39 # acc_data = json.dump(account_data,f) 40 41 return True
core/auth.py
數據庫
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import os 4 from core import db_handler 5 from conf import settings 6 from core import logger 7 import json 8 import time 9 10 11 12 def login_required(func): 13 "驗證用戶是否登陸" 14 15 def wrapper(*args,**kwargs): 16 #print('--wrapper--->',args,kwargs) 17 if args[0].get('is_authenticated'): 18 return func(*args,**kwargs) 19 else: 20 exit("User is not authenticated.") 21 return wrapper 22 23 24 def acc_auth(account,password): 25 ''' 26 account auth func 27 :param account: credit account number 28 :param password: credit card password 29 :return: if passed the authentication , retun the account object, otherwise ,return None 30 ''' 31 db_path = db_handler.db_handler(settings.DATABASE) 32 account_file = "%s/%s.json" %(db_path,account) 33 print(account_file) 34 if os.path.isfile(account_file): 35 with open(account_file,'r') as f: 36 account_data = json.load(f) 37 if account_data['password'] == password: 38 exp_time_stamp = time.mktime(time.strptime(account_data['expire_date'], "%Y-%m-%d")) 39 if time.time() >exp_time_stamp: 40 print("\033[31;1mAccount [%s] has expired,please contact the back to get a new card!\033[0m" % account) 41 else: #passed the authentication 42 return account_data 43 else: 44 print("\033[31;1mAccount ID or password is incorrect!\033[0m") 45 else: 46 print("\033[31;1mAccount [%s] does not exist!\033[0m" % account) 47 48 49 def acc_auth2(account,password): 50 ''' 51 優化版認證接口 52 :param account: credit account number 53 :param password: credit card password 54 :return: if passed the authentication , retun the account object, otherwise ,return None 55 56 ''' 57 db_api = db_handler.db_handler() #鏈接數據庫 file_execute內存地址 58 data = db_api("select * from accounts where account=%s" % account) #執行sql 59 if data['password'] == password: 60 exp_time_stamp = time.mktime(time.strptime(data['expire_date'], "%Y-%m-%d")) 61 if time.time() > exp_time_stamp: 62 print("\033[31;1mAccount [%s] has expired,please contact the back to get a new card!\033[0m" % account) 63 else: # passed the authentication 64 return data 65 else: 66 print("\033[31;1mAccount ID or password is incorrect!\033[0m") 67 68 def acc_login(user_data,log_obj): 69 ''' 70 account login func 71 :user_data: user info data , only saves in memory 72 :return: 73 ''' 74 retry_count = 0 75 while user_data['is_authenticated'] is not True and retry_count < 3 : 76 account = input("\033[32;1maccount:\033[0m").strip() 77 password = input("\033[32;1mpassword:\033[0m").strip() 78 auth = acc_auth2(account, password) 79 if auth: #not None means passed the authentication 80 user_data['is_authenticated'] = True 81 user_data['account_id'] = account 82 #print("welcome") 83 return auth 84 retry_count +=1 85 else: 86 log_obj.error("account [%s] too many login attempts" % account) 87 exit()
core/db_handler.py
json
1 #!_*_coding:utf-8_*_ 2 #__author__:"Alex Li" 3 4 ''' 5 handle all the database interactions 6 ''' 7 import json,time ,os 8 from conf import settings 9 def file_db_handle(conn_params): 10 ''' 11 parse the db file path 12 :param conn_params: the db connection params set in settings 13 :return: 14 ''' 15 # print('file db:',conn_params) 16 #db_path ='%s/%s' %(conn_params['path'],conn_params['name']) 17 return file_execute 18 def db_handler(): 19 ''' 20 connect to db 21 :param conn_parms: the db connection params set in settings 22 :return:a 23 ''' 24 conn_params = settings.DATABASE 25 if conn_params['engine'] == 'file_storage': 26 return file_db_handle(conn_params) 27 elif conn_params['engine'] == 'mysql': 28 pass #todo 29 30 31 32 def file_execute(sql,**kwargs): 33 conn_params = settings.DATABASE 34 db_path = '%s/%s' % (conn_params['path'], conn_params['name']) 35 36 # print(sql,db_path) 37 sql_list = sql.split("where") 38 # print(sql_list) 39 if sql_list[0].startswith("select") and len(sql_list)> 1:#has where clause 40 column,val = sql_list[1].strip().split("=") 41 42 if column == 'account': 43 account_file = "%s/%s.json" % (db_path, val) 44 print(account_file) 45 if os.path.isfile(account_file): 46 with open(account_file, 'r') as f: 47 account_data = json.load(f) 48 return account_data 49 else: 50 exit("\033[31;1mAccount [%s] does not exist!\033[0m" % val ) 51 52 elif sql_list[0].startswith("update") and len(sql_list)> 1:#has where clause 53 column, val = sql_list[1].strip().split("=") 54 if column == 'account': 55 account_file = "%s/%s.json" % (db_path, val) 56 #print(account_file) 57 if os.path.isfile(account_file): 58 account_data = kwargs.get("account_data") 59 with open(account_file, 'w') as f: 60 acc_data = json.dump(account_data, f) 61 return True
core/logger.py
api
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 ''' 5 handle all the logging works 6 ''' 7 8 import logging 9 from conf import settings 10 11 def logger(log_type): 12 13 #create logger 14 logger = logging.getLogger(log_type) 15 logger.setLevel(settings.LOG_LEVEL) 16 17 18 # create console handler and set level to debug 19 ch = logging.StreamHandler() 20 ch.setLevel(settings.LOG_LEVEL) 21 22 # create file handler and set level to warning 23 log_file = "%s/log/%s" %(settings.BASE_DIR, settings.LOG_TYPES[log_type]) 24 fh = logging.FileHandler(log_file) 25 fh.setLevel(settings.LOG_LEVEL) 26 # create formatter 27 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 28 29 # add formatter to ch and fh 30 ch.setFormatter(formatter) 31 fh.setFormatter(formatter) 32 33 # add ch and fh to logger 34 logger.addHandler(ch) 35 logger.addHandler(fh) 36 37 return logger 38 # 'application' code 39 '''logger.debug('debug message') 40 logger.info('info message') 41 logger.warn('warn message') 42 logger.error('error message') 43 logger.critical('critical message')'''
db/account_sample.py
app
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 5 import json 6 acc_dic = { 7 'id': 1234, 8 'password': 'abc', 9 'credit': 15000, 10 'balance': 15000, 11 'enroll_date': '2016-01-02', 12 'expire_date': '2021-01-01', 13 'pay_day': 22, 14 'status': 0 # 0 = normal, 1 = locked, 2 = disabled 15 } 16 17 print(json.dumps(acc_dic))