1作业需求:模拟实现一个ATM + 购物商城程序: 1.额度 15000或自定义 2.实现购物商城,买东西加入 购物车,调用信用卡接口结账 3.可以提现,手续费5% 4.支持多账户登录 5.支持账户间转账 6.记录每月日常消费流水 7.提供还款接口 8.ATM记录操作日志 9.提供管理接口,包括添加账户、用户额度,冻结账户等。。。 10.用户认证用装饰器一、软件定位,软件的基本功能。 实现一个简单的atm与购物车程序,二、运行代码的方法: 安装环境、启动命令等。 用Python3.5写的,语法就是至此之前所学的,直接打开运行即可三、目录总体结构设计。├── ATM #ATM主程目录│ ├── __init__.py│ ├── bin #ATM 执行文件 目录│ │ ├── __init__.py│ │ ├── atm.py #ATM 执行程序│ │ ├── manage.py #信用卡管理│ ├── 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 #存各个用户的账户数据 ,一个用户一个文件│ │ └── 123.json #新创建的用户账户示例文件│ │ └── 1234.json #一个用户账户示例文件│ │ └── 123456.json #一个用户账户示例文件│ │ └── 6230001.json #管理用户账户示例文件│ └── log #日志目录│ ├── access.log #用户访问和操作的相关日志│ └── login_in.log #登陆日志└── shopping_mall #电子商城程序,需单独实现,主要实现购物的功能。│ └── __init__.py│ └── product.txt #存放商品的txt文件│ └── shopping_list.txt #存放购物清单的txt.文件│ └── shopping_mall.py #购物商城程序├── README四、简要说明,更详细点可以说明软件的基本原理。 1.程序从/bin/atm.py开始执行if __name__ == '__main__': main.run() 2.程序转到/core/main.py下的run()函数,登陆时调用/core/auth的acc_login()进行登陆验证:用到了/core/auth下的acc_auth2()方法进行验证(此时传入的参数时用户输入的账户和密码)acc_auth2中有调用了/core/db_handler下的db_handler()方法(参数是输入的账户名)在db_handler中只是进行判断是什么引擎,return file_db_handle(数据库引擎)解析文件,返回文件执行加载输入的用户的账户的所有数据接下来判断是否为管理者账户,或者是否被冻结,若都不是,则判断输入的密码是否与数据库中的密码一样,在判断到期时间是否过期所有都通过的话就返回这个账户的数据,之前已经创建了一个空字典,里面有是否验证:用户数据:用户账户:,判断是否被验证过,然后把用户数据临时的传递到里面,执行主循环函数可以选择进入到购物商城,或者信用卡操作或者退出 1)购物商城 调用/shopping_mall/shopping_mall.py文件执行,主循环函数,选择你是商家还是用户, ①如果选择商家,商家有增加商品修改商品的功能 ②如果选择用户,用户则有购物,刷信用卡消费的功能,当退出时打印消费清单 2)信用卡操作 调用/core/main.py下interactive(用户的所有数据)调用主循环函数,可以打印账户信息、还款、取款、转账、账单、退出等操作 ①账户信息 ②还款 ③取款 ④转账 ⑤账单 ⑥退出 3)若在账户登陆的时候进行输入的时管理员账户调用/bin/manage.py则可以对用户进行管理,解冻用户、冻结用户、申领新卡 ①添加账户 ②冻结账户 ③解冻账户 ④退出五、常见问题说明。 日志没有实现,账单没有 2 3六、以下为功能架构及部分实现功能代码


1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 #-*- Coding:utf-8 -*- 4 4 5 5 import os,sys 6 6 7 7 8 8 BASE_DIR_Atm = os.path.dirname(os.path.dirname(__file__)) 9 9 BASE_DIR_PRO = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 1010 1111 sys.path.append(BASE_DIR_Atm) 1212 sys.path.append(BASE_DIR_PRO) 1313 1414 print(sys.path) 1515 1616 from core import main 1717 1818 if __name__ == '__main__': 1919 main.run()
online/Atm/bin/arm.py

1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 # -*- coding:utf-8 -*- 4 4 import os,logging 5 5 BASE_DIR_Atm = os.path.dirname(os.path.dirname(__file__)) 6 6 7 7 DATABASE = { 8 8 'engine':'file_storage', 9 9 'name':'account', 1010 'path':"%s/dbs" % BASE_DIR_Atm 1111 } 1212 1313 LOG_LEVEL = logging.INFO 1414 LOG_TYPES = { 1515 'transaction':'transaction.log', 1616 'access':'access.log' 1717 } 1818 1919 #发生交易的配置类型 2020 TRANSACTION_TYPE = { 2121 'repay':{'action':'plus','interest':0}, #还款 2222 'withdrawal':{'action':'minus','interest':0.05}, #取现扣费,即降低可用余额 2323 'transfer':{'action':'minus','interest':0.05}, #转账扣费,即降低可用余额 2424 'consumption':{'action':'minus','interest':0} #消费 2525 }
online/Atm/conf/settings.py

1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 # -*- coding:utf-8 -*- 4 4 #用于从文件里加载和存储账户数据 5 5 import json 6 6 from core import db_handler 7 7 from conf import settings 8 8 9 9 def load_current_balance(account_id): 1010 ''' 1111 返回账号余额其他信息 1212 :param account_id:用户账户的名字 1313 :return:返回最新读到的数据文件中的最新数据 1414 ''' 1515 db_path = db_handler.db_handler() 1616 account_file = "%s/%s.json" %(db_path,account_id) 1717 with open(account_file,'r') as f: 1818 acc_data = json.load(f) 1919 return acc_data 2020 2121 #写入文件 2222 def dump_account(account_data): 2323 db_path = db_handler.db_handler() 2424 account_file = "%s/%s.json" %(db_path,account_data['id']) 2525 with open(account_file,'w') as f: 2626 acc_data = json.dump(account_data,f) 2727 return True
online/Atm/core/accounts.py

1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 # -*- coding:utf-8 -*- 4 4 # 用户认证模块 5 5 import os,json,time 6 6 from core import db_handler 7 7 8 8 #装饰器(用于验证账户是否登陆过) 9 9 def login_required(func): 1010 def wrapper(*args,**kwargs): 1111 if args[0].get('is_authenticated'): 1212 return func(*args,**kwargs) 1313 else: 1414 exit("用户认证失败") 1515 return wrapper 1616 1717 def acc_auth(account,password): 1818 ''' 1919 账号验证函数 2020 :param account: 2121 :param password: 2222 :return: 2323 ''' 2424 db_path = db_handler.db_handler() 2525 account_file = '%s/%s.json' % (db_path,account) 2626 # print('account_file:',account_file) 2727 # print('account_file:',type(account_file)) 2828 if os.path.isfile(account_file): 2929 with open(account_file,'r') as f: 3030 account_data = json.load(f) 3131 if account_data['password'] == password: 3232 exp_time_stamp = time.mktime(time.strptime(account_data['expire_date'],"%Y-%m-%d")) 3333 if time.time() > exp_time_stamp: 3434 print("\033[31;1m[%s]账户已经注销,请重新申领账户!\033[0m" % account) 3535 else: 3636 return account_data 3737 else: 3838 print("\033[31;1m账号或密码错误,请重新输入!\033[0m") 3939 else: 4040 print("\033[31;1m[%s]账户不存在!---\033[0m" % account) 4141 4242 def acc_login(user_data,log_obj): 4343 ''' 4444 账户登陆函数 4545 :param user_data:用户信息数据,只存在内存中 4646 :param log_obj: 4747 :return: 账户密码都对的情况下,返回所有账户数据 4848 ''' 4949 retry_count = 0; #初始化重试次数为0 5050 while user_data['is_authenticated'] is not True and retry_count < 3: #如果没有验证过,或循环此时没超过三次就执行下面的 5151 account = input("\033[32;1m账户:\033[0m").strip()#输入账户 5252 password = input("\033[32;1m密码:\033[0m").strip()#输入密码 5353 auth = acc_auth(account,password) #解耦,将输入的账户和密码传入到acc_auth2函数中,进行验证最后返回的是读取到的输入正确账户的所有数据)赋值给auth 5454 # print('auth:',auth) 5555 if auth: 5656 user_data['is_authenticated'] = True #登陆成功,将只存在与内存中的数据中的是否验证改为True 5757 user_data['account_id'] = account #将只存在与内存中的数据中的账户id改为账户名字(开始输入的帐户名) 5858 return auth #这一步操作就是验证此账户是否登陆,然后返回账户的所有数据(数据文件中的所有数据) 5959 retry_count += 1 6060 else: 6161 log_obj.error("[%s]账户太多次尝试" % account) 6262 exit()
online/Atm/core/auth.py

1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 # -*- coding:utf-8 -*- 4 4 import os,sys,time,json 5 5 6 6 from conf import settings 7 7 8 8 9 9 1010 #数据库句柄 1111 def db_handler(): 1212 ''' 1313 连接数据库 1414 :return: 1515 ''' 1616 conn_params = settings.DATABASE #conf下配置的数据库地址 1717 # print("conn_params:",conn_params) 1818 # print('conn_params:',type(conn_params)) 1919 if conn_params['engine'] == 'file_storage': #判断Settings下的DABASE是什么引擎,这里只用文件文件引擎 2020 return file_db_handler(conn_params) #则把Settings下的DABASE的数据传给file_db_handle并返回 2121 elif conn_params['engine'] == 'mysql': 2222 pass #支持数据源类型扩展 2323 2424 def file_db_handler(conn_params): 2525 ''' 2626 file db操作 2727 :return: 2828 ''' 2929 # print('file_db:',conn_params) 3030 db_path = '%s/%s' % (conn_params['path'],conn_params['name']) 3131 return db_path
online/Atm/core/db_handler.py

1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 # -*- coding:utf-8 -*- 4 4 #日志记录模块,处理所有日志工作 5 5 6 6 import logging 7 7 from conf import settings 8 8 9 9 def logger(log_type): 1010 #create logger 1111 logger = logging.getLogger(log_type) 1212 logger.setLevel(settings.LOG_LEVEL) 1313 1414 #创建控制台处理程序并将级别设置为调试 1515 ch = logging.StreamHandler() 1616 ch.setLevel(settings.LOG_LEVEL) 1717 1818 #创建文件处理程序并设置级别为警告 1919 log_file = "%s/%s" % (settings.BASE_DIR_Atm,settings.LOG_TYPES[log_type]) 2020 fh = logging.FileHandler(log_file) 2121 fh.setLevel(settings.LOG_LEVEL) 2222 2323 #创建格式化程序 2424 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levename)s- %(message)s') 2525 2626 #添加格式化的CH和FH 2727 ch.setFormatter(formatter) 2828 fh.setFormatter(formatter) 2929 3030 #添加CH和FH到loggerh 3131 logger.addHandler(ch) 3232 logger.addHandler(fh) 3333 3434 return logger 3535 ''' 3636 #应用程序代码 3737 logger.debug('debug message') 3838 '''
online/Atm/core/logger.py

1__author__ = 'ZFH' 2# 2018.09.26 3# -*- coding:utf-8 -*- 4 5import os,time,sys 6from core import auth 7from core.auth import login_required 8from core import db_handler 9from core import logger 10from core import transaction 11from core import logger 12from conf import settings 13from core import accounts 14import json 15 16#访问日志 17access_logger = logger.logger('access') 18#交易日志 19trans_logger= logger.logger('transaction') 20 21#用户数据信息 22user_data = { 23 'account_id':None, #账号ID 24 'is_authenticated':False, #是否认证 25 'account_data':None #账号数据 26} 27#调用log文件下的log方法,返回日志对象 28 29def run(): 30# print("测试调用不同路径") 31 acc_data = auth.acc_login(user_data,access_logger) #程序从这里开始,执行auth下的acc_login函 32# print('acc_data:',acc_data) 33# (返回的是验证过的正确的账户数据)赋值给acc_data(此时这里的数据为输入账户名字的数据文件的数据) 34 35 if user_data['is_authenticated']: 36 user_data['account_data'] = acc_data #把账户所有信息传给账户开始时的临时的账户数据空字典, 37 # 把所有的数据文件传给账户的账户数据里面, 38# print(acc_data) 39 main_menu(user_data) 40 41def account_info(acc_data): 42 ''' 43 账户信息 44 :param acc_data:账户信息 45 :return: 46 ''' 47 account_info = acc_data 48 creidt = account_info['account_data']['credit'] 49 balance = account_info['account_data']['balance'] 50 pay_day = account_info['account_data']['pay_day'] 51 print(' creidt: %s \n balance: %s \n pay_day: %s' % (creidt,balance,pay_day)) 52 53def login_out(acc_data): 54 ''' 55 用户退出登录 56 :param acc_data: 退出用户 57 :return: 58 ''' 59 exit('退出') 60 61def main_menu(acc_data): 62 ''' 63 展现主菜单,并让用户选择功能 64 1.银行卡操作 65 2.购物商城 66 3.退出 67 :param acc_data: 传入赋值的临时账户信息 68 :return: 69 ''' 70 main_menu = u''' 71 --------主菜单-------- 72 \033[32;1m 73 1.银行卡操作 74 2.购物商城 75 e.退出 76 \033[0m 77 ''' 78 main_menu_dic = { 79 '1':'银行卡操作', 80 '2':'购物商城', 81 'e':'直接退出', 82 } 83 exit_flag = False 84 while not exit_flag: 85 print(main_menu) 86 user__main_option = input('请输入您的选择的操作:').strip() 87 if user__main_option in main_menu_dic: 88# print('acc_data:',acc_data) 89 if user__main_option == 'e': 90 login_out(acc_data) 91 return 92 else: 93 interactive(acc_data,user__main_option) 94 else: 95 print('\033[31;1m选择不存在!\033[0m') 96 97 98def interactive(acc_data,user__main_option): 99 ''' 100 处理主菜单选择 101 :param acc_data: 缓存记录的用户信息 102 :param user__main_option: 主菜单用户选择 103 :return: 104 ''' 105 if user__main_option == '1': 106 interactive_bank(acc_data) 107 elif user__main_option == '2': 108 interactive_shaopping(acc_data) 109 return interactive 110 111def interactive_shaopping(acc_data): 112 ''' 113 展现购物菜单 114 :param acc_data: 缓存记录的用户信息 115 :return: 116 ''' 117 return 118 119def interactive_bank(acc_data): 120 ''' 121 展现银行卡主菜单,并让用户选择功能 122 1.查看账户 123 2.取款 124 3.还款 125 4.转账 126 5.账单 127 e.直接退出 128 返回上一层 129 :param acc_data: 传入赋值的临时账户信息 130 :param user_option: 传入用户上一层选择 131 :return: 132 ''' 133 print('进入下一个菜单') 134 main_menu_bank = u''' 135 --------银行卡主菜单-------- 136 \033[32;1m 137 1.查看账户 138 2.取款 139 3.还款 140 4.转账 141 5.账单 142 e.直接退出 143 \033[0m 144 ''' 145 main_menu_bank_dic = { 146 '1':'查看账户', 147 '2':'取款', 148 '3':'还款', 149 '4':'转账', 150 '5':'账单', 151 'e':'直接退出', 152 } 153 154 exit_flag = False 155 while not exit_flag: 156 print(main_menu_bank) 157 user_option = input('请输入您的选择的操作(\033[32;1m返回上一层请输入r\033[0m):').strip() 158 if user_option in main_menu_bank_dic: 159 if user_option == 'e': 160 login_out(acc_data) 161 break 162 elif user_option == '1': 163 account_info(acc_data) 164 user_option = input('\033[32;1m返回上一层请输入r\033[0m:').strip() 165 if user_option == 'r': 166 interactive_bank(acc_data) 167 else: 168 print('\033[31;1m选择不存在!\033[0m返回上一层请输入r,直接退出请输入e') 169 account_info(acc_data) 170 user_option = input('\033[32;1m返回上一层请输入r\033[0m:').strip() 171 if user_option =='r': 172 interactive_bank(acc_data) 173 elif user_option == 'e': 174 login_out(acc_data) 175 break 176 elif user_option == '2': 177 withdrawal(acc_data) 178 elif user_option == '3': 179 repay(acc_data) 180 elif user_option == 'r': 181 return main_menu(acc_data) 182 else: 183 print('\033[31;1m选择不存在!请按提示重新输入!!!\033[0m') 184 185@login_required #调用用户验证装饰器 186def withdrawal(acc_data): 187 ''' 188 取款操作,打印可取款额度 189 :param acc_data: 190 :return: 191 ''' 192 account_data = accounts.load_current_balance(acc_data['account_id']) 193 current_balance = ''' 194 ---------银行信息---------- 195 信用额度: %s 196 可用余额: %s 197 账单日: %s 198 ''' % (account_data['credit'],account_data['balance'],account_data['pay_day']) 199 print(current_balance) 200 withdrawal_flag = False 201 while not withdrawal_flag: 202 withdrawal_amount = input("\033[33;1m输入取款金额:\033[0m").strip() 203 if len(withdrawal_amount) > 0 and withdrawal_amount.isdigit(): 204 #将数据传入make_transaction中(交易日志,用户数据,交易类型,还款金额)进行操作,最后返回的是最新操作之后的账户数据 205 new_balance = transaction.make_transaction(trans_logger,account_data,'withdrawal',withdrawal_amount) 206 if new_balance: 207 print('''\033[42;1m最新的余额:%s\033[0m''' %(new_balance['balance'])) 208 return 209 if withdrawal_flag == 'b': 210 withdrawal_flag == True 211 elif withdrawal_amount == 'e': 212 interactive_bank(acc_data) 213 else: 214 print('\033[31;1m[%s]是无效的账户!\033[0m' % withdrawal_amount) 215 if withdrawal_amount == 'b': 216 withdrawal_flag == True 217 218 219 220@login_required #调用用户验证装饰器 221def repay(acc_data): 222 ''' 223 还款操作,打印当前余额 224 :param acc_data: 225 :return: 226 ''' 227 account_data = accounts.load_current_balance(acc_data['account_id']) 228 current_balance = ''' 229 ---------银行信息---------- 230 信用额度: %s 231 可用余额: %s 232 账单日: %s 233 ''' % (account_data['credit'],account_data['balance'],account_data['pay_day']) 234 print(current_balance) 235 repay_flag = False 236 while not repay_flag: 237 repay_amount = input("\033[33;1m输入你要还款的金额(重选操作请输入e):\033[0m").strip() 238 if len(repay_amount) > 0 and repay_amount.isdigit(): 239 #将数据传入make_transaction中(交易日志,用户数据,交易类型,还款金额)进行操作,最后返回的是最新操作之后的账户数据 240 new_balance = transaction.make_transaction(trans_logger,account_data,'repay',repay_amount) 241 if new_balance: 242 print('''\033[42;1m最新的余额:%s\033[0m''' %(new_balance['balance'])) 243 return 244 if repay_amount == 'b': 245 repay_flag == True 246 elif repay_amount == 'e': 247 interactive_bank(acc_data) 248 else: 249 print('\033[31;1m[%s]是无效的账户!\033[0m' % repay_amount) 250 if repay_amount == 'b': 251 repay_flag == True
online/Atm/core/main.py

1 1 __author__ = 'ZFH' 2 2 # 2018.09.26 3 3 # -*- coding:utf-8 -*- 4 4 #记账\还钱\取钱\与账户金额相关的操作,冻结或者锁定用户 5 5 # 交易处理模块 6 6 from conf import settings 7 7 from core import db_handler 8 8 from core import accounts 9 9 1010 import json,time 1111 1212 def make_transaction(log_obj,account_data,tran_type,amount,**kwargs): 1313 ''' 1414 处理所有用户的交易 1515 :param log_obj: 写入日志 1616 :param account_data:用户最新数据 1717 :param tran_type:交易类型 1818 :param amount:交易金额 1919 :param kwargs:其他参数 2020 :return:返回最新的账户数 2121 ''' 2222 amount = float(amount) #转换为浮点数 2323 if tran_type in settings.TRANSACTION_TYPE: #判断传入的类型是否在配置参数里面 2424 interest = round(amount * settings.TRANSACTION_TYPE[tran_type]['interest'],2) #根据交易类型计算利息赋值给interest 2525 old_lalance = float(account_data['balance']) #读取数据中账户余额 2626 # print('interest:',type(interest)) 2727 # print('amount:',type(amount)) 2828 # print('old_lalance:',type(old_lalance)) 2929 3030 #还款操作 3131 if settings.TRANSACTION_TYPE[tran_type]['action'] == 'plus': #因为是信用卡,所以需要判断操作操作,加plus 3232 # print(settings.TRANSACTION_TYPE) 3333 new_balance = old_lalance + amount + interest #执行的是信用卡的还款操作,计算方法是,旧余额+还款的钱和利息=最后的账户可用余额 3434 #取现 3535 elif settings.TRANSACTION_TYPE[tran_type]['action'] == 'minus' and settings.TRANSACTION_TYPE[tran_type] == 'transfer': #因为是信用卡,所以取现都是降低可用余额的操作 3636 pass 3737 else: 3838 balance = old_lalance - amount - interest 3939 if balance > 0: 4040 new_balance = balance 4141 else: 4242 print("\033[31;1m余额不足\033[0m") 4343 return 4444 4545 account_data['balance'] = new_balance 4646 # print(account_data) 4747 accounts.dump_account(account_data) 4848 return account_data 4949 5050 else: 5151 print("\033[31;1m%s交易类型不存在\033[0m" % tran_type)
online/Atm/core/transaction.py

1 {"enroll_date": "2018-09-27", "id": "123", "expire_date": "2032-01-01", "status": 0, "password": "123", "pay_day": "22", "balance": 513.8, "credit": "15000"}
online/Atm/dbs/account/123.json