• Python学习笔记-练习编写ATM+购物车(购物商城)


    作业需求:

    模拟实现一个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则可以对用户进行管理,解冻用户、冻结用户、申领新卡
    ①添加账户
    ②冻结账户
    ③解冻账户
    ④退出
    五、常见问题说明。
    日志没有实现,账单没有
    六、以下为功能架构及部分实现功能代码


             

     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 #-*- Coding:utf-8 -*-
     4 
     5 import os,sys
     6 
     7 
     8 BASE_DIR_Atm = os.path.dirname(os.path.dirname(__file__))
     9 BASE_DIR_PRO = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
    10 
    11 sys.path.append(BASE_DIR_Atm)
    12 sys.path.append(BASE_DIR_PRO)
    13 
    14 print(sys.path)
    15 
    16 from core import main
    17 
    18 if __name__ == '__main__':
    19     main.run()
    online/Atm/bin/arm.py
     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 # -*- coding:utf-8 -*-
     4 import os,logging
     5 BASE_DIR_Atm = os.path.dirname(os.path.dirname(__file__))
     6 
     7 DATABASE = {
     8     'engine':'file_storage',
     9     'name':'account',
    10     'path':"%s/dbs" % BASE_DIR_Atm
    11 }
    12 
    13 LOG_LEVEL = logging.INFO
    14 LOG_TYPES = {
    15     'transaction':'transaction.log',
    16     'access':'access.log'
    17 }
    18 
    19 #发生交易的配置类型
    20 TRANSACTION_TYPE = {
    21     'repay':{'action':'plus','interest':0},                       #还款
    22     'withdrawal':{'action':'minus','interest':0.05},                 #取现扣费,即降低可用余额
    23     'transfer':{'action':'minus','interest':0.05},                   #转账扣费,即降低可用余额
    24     'consumption':{'action':'minus','interest':0}                 #消费
    25 }
    online/Atm/conf/settings.py
     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 # -*- coding:utf-8 -*-
     4 #用于从文件里加载和存储账户数据
     5 import json
     6 from core import db_handler
     7 from conf import settings
     8 
     9 def load_current_balance(account_id):
    10     '''
    11     返回账号余额其他信息
    12     :param account_id:用户账户的名字
    13     :return:返回最新读到的数据文件中的最新数据
    14     '''
    15     db_path = db_handler.db_handler()
    16     account_file = "%s/%s.json" %(db_path,account_id)
    17     with open(account_file,'r') as f:
    18         acc_data = json.load(f)
    19         return acc_data
    20 
    21 #写入文件
    22 def dump_account(account_data):
    23     db_path = db_handler.db_handler()
    24     account_file = "%s/%s.json" %(db_path,account_data['id'])
    25     with open(account_file,'w') as f:
    26         acc_data = json.dump(account_data,f)
    27     return True
    online/Atm/core/accounts.py
     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 # -*- coding:utf-8 -*-
     4 # 用户认证模块
     5 import os,json,time
     6 from core import db_handler
     7 
     8 #装饰器(用于验证账户是否登陆过)
     9 def login_required(func):
    10     def wrapper(*args,**kwargs):
    11         if args[0].get('is_authenticated'):
    12             return func(*args,**kwargs)
    13         else:
    14             exit("用户认证失败")
    15     return wrapper
    16 
    17 def acc_auth(account,password):
    18     '''
    19     账号验证函数
    20     :param account:
    21     :param password:
    22     :return:
    23     '''
    24     db_path = db_handler.db_handler()
    25     account_file = '%s/%s.json' % (db_path,account)
    26 #    print('account_file:',account_file)
    27 #    print('account_file:',type(account_file))
    28     if os.path.isfile(account_file):
    29         with open(account_file,'r') as f:
    30             account_data = json.load(f)
    31             if account_data['password'] == password:
    32                 exp_time_stamp = time.mktime(time.strptime(account_data['expire_date'],"%Y-%m-%d"))
    33                 if time.time() > exp_time_stamp:
    34                     print("33[31;1m[%s]账户已经注销,请重新申领账户!33[0m" % account)
    35                 else:
    36                     return account_data
    37             else:
    38                 print("33[31;1m账号或密码错误,请重新输入!33[0m")
    39     else:
    40         print("33[31;1m[%s]账户不存在!---33[0m" % account)
    41 
    42 def acc_login(user_data,log_obj):
    43    '''
    44    账户登陆函数
    45    :param user_data:用户信息数据,只存在内存中
    46    :param log_obj:
    47    :return: 账户密码都对的情况下,返回所有账户数据
    48    '''
    49    retry_count = 0;  #初始化重试次数为0
    50    while user_data['is_authenticated'] is not True and retry_count < 3: #如果没有验证过,或循环此时没超过三次就执行下面的
    51        account = input("33[32;1m账户:33[0m").strip()#输入账户
    52        password = input("33[32;1m密码:33[0m").strip()#输入密码
    53        auth = acc_auth(account,password) #解耦,将输入的账户和密码传入到acc_auth2函数中,进行验证最后返回的是读取到的输入正确账户的所有数据)赋值给auth
    54 #       print('auth:',auth)
    55        if auth:
    56            user_data['is_authenticated'] = True #登陆成功,将只存在与内存中的数据中的是否验证改为True
    57            user_data['account_id'] = account #将只存在与内存中的数据中的账户id改为账户名字(开始输入的帐户名)
    58            return auth  #这一步操作就是验证此账户是否登陆,然后返回账户的所有数据(数据文件中的所有数据)
    59        retry_count += 1
    60    else:
    61        log_obj.error("[%s]账户太多次尝试" % account)
    62        exit()
    online/Atm/core/auth.py
     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 # -*- coding:utf-8 -*-
     4 import os,sys,time,json
     5 
     6 from conf import settings
     7 
     8 
     9 
    10 #数据库句柄
    11 def db_handler():
    12     '''
    13     连接数据库
    14     :return:
    15     '''
    16     conn_params = settings.DATABASE   #conf下配置的数据库地址
    17 #    print("conn_params:",conn_params)
    18 #    print('conn_params:',type(conn_params))
    19     if conn_params['engine'] == 'file_storage': #判断Settings下的DABASE是什么引擎,这里只用文件文件引擎
    20         return file_db_handler(conn_params) #则把Settings下的DABASE的数据传给file_db_handle并返回
    21     elif conn_params['engine'] == 'mysql':
    22         pass #支持数据源类型扩展
    23 
    24 def file_db_handler(conn_params):
    25     '''
    26     file db操作
    27     :return:
    28     '''
    29 #    print('file_db:',conn_params)
    30     db_path = '%s/%s' % (conn_params['path'],conn_params['name'])
    31     return db_path
    online/Atm/core/db_handler.py
     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 # -*- coding:utf-8 -*-
     4 #日志记录模块,处理所有日志工作
     5 
     6 import logging
     7 from conf import settings
     8 
     9 def logger(log_type):
    10     #create logger
    11     logger = logging.getLogger(log_type)
    12     logger.setLevel(settings.LOG_LEVEL)
    13 
    14     #创建控制台处理程序并将级别设置为调试
    15     ch = logging.StreamHandler()
    16     ch.setLevel(settings.LOG_LEVEL)
    17 
    18     #创建文件处理程序并设置级别为警告
    19     log_file = "%s/%s" % (settings.BASE_DIR_Atm,settings.LOG_TYPES[log_type])
    20     fh = logging.FileHandler(log_file)
    21     fh.setLevel(settings.LOG_LEVEL)
    22 
    23     #创建格式化程序
    24     formatter = logging.Formatter('%(asctime)s - %(name)s - %(levename)s- %(message)s')
    25 
    26     #添加格式化的CH和FH
    27     ch.setFormatter(formatter)
    28     fh.setFormatter(formatter)
    29 
    30     #添加CH和FH到loggerh
    31     logger.addHandler(ch)
    32     logger.addHandler(fh)
    33 
    34     return logger
    35     '''
    36     #应用程序代码
    37     logger.debug('debug message')
    38     '''
    online/Atm/core/logger.py
    __author__ = 'ZFH'
    # 2018.09.26
    # -*- coding:utf-8 -*-
    
    import os,time,sys
    from core import auth
    from core.auth import login_required
    from core import db_handler
    from core import logger
    from core import transaction
    from core import logger
    from conf import settings
    from core import accounts
    import json
    
    #访问日志
    access_logger = logger.logger('access')
    #交易日志
    trans_logger= logger.logger('transaction')
    
    #用户数据信息
    user_data = {
        'account_id':None,                             #账号ID
        'is_authenticated':False,                      #是否认证
        'account_data':None                            #账号数据
    }
    #调用log文件下的log方法,返回日志对象
    
    def run():
    #    print("测试调用不同路径")
        acc_data = auth.acc_login(user_data,access_logger)                     #程序从这里开始,执行auth下的acc_login函
    #    print('acc_data:',acc_data)
    #  (返回的是验证过的正确的账户数据)赋值给acc_data(此时这里的数据为输入账户名字的数据文件的数据)
    
        if user_data['is_authenticated']:
            user_data['account_data'] = acc_data                #把账户所有信息传给账户开始时的临时的账户数据空字典,
            # 把所有的数据文件传给账户的账户数据里面,
    #        print(acc_data)
            main_menu(user_data)
    
    def account_info(acc_data):
        '''
        账户信息
        :param acc_data:账户信息
        :return:
        '''
        account_info = acc_data
        creidt = account_info['account_data']['credit']
        balance = account_info['account_data']['balance']
        pay_day =  account_info['account_data']['pay_day']
        print(' creidt: %s 
     balance: %s 
     pay_day: %s' % (creidt,balance,pay_day))
    
    def login_out(acc_data):
        '''
        用户退出登录
        :param acc_data: 退出用户
        :return:
        '''
        exit('退出')
    
    def main_menu(acc_data):
        '''
        展现主菜单,并让用户选择功能
        1.银行卡操作
        2.购物商城
        3.退出
        :param acc_data: 传入赋值的临时账户信息
        :return:
        '''
        main_menu = u'''
        --------主菜单--------
        33[32;1m
        1.银行卡操作
        2.购物商城
        e.退出
        33[0m
        '''
        main_menu_dic = {
            '1':'银行卡操作',
            '2':'购物商城',
            'e':'直接退出',
        }
        exit_flag = False
        while not exit_flag:
            print(main_menu)
            user__main_option = input('请输入您的选择的操作:').strip()
            if user__main_option in main_menu_dic:
    #            print('acc_data:',acc_data)
                if user__main_option == 'e':
                    login_out(acc_data)
                    return
                else:
                    interactive(acc_data,user__main_option)
            else:
                print('33[31;1m选择不存在!33[0m')
    
    
    def interactive(acc_data,user__main_option):
        '''
        处理主菜单选择
        :param acc_data: 缓存记录的用户信息
        :param user__main_option: 主菜单用户选择
        :return:
        '''
        if user__main_option == '1':
            interactive_bank(acc_data)
        elif user__main_option == '2':
            interactive_shaopping(acc_data)
        return interactive
    
    def interactive_shaopping(acc_data):
        '''
        展现购物菜单
        :param acc_data: 缓存记录的用户信息
        :return:
        '''
        return
    
    def interactive_bank(acc_data):
        '''
        展现银行卡主菜单,并让用户选择功能
        1.查看账户
        2.取款
        3.还款
        4.转账
        5.账单
        e.直接退出
        返回上一层
        :param acc_data: 传入赋值的临时账户信息
        :param user_option: 传入用户上一层选择
        :return:
        '''
        print('进入下一个菜单')
        main_menu_bank = u'''
        --------银行卡主菜单--------
        33[32;1m
            1.查看账户
            2.取款
            3.还款
            4.转账
            5.账单
            e.直接退出
        33[0m
        '''
        main_menu_bank_dic = {
            '1':'查看账户',
            '2':'取款',
            '3':'还款',
            '4':'转账',
            '5':'账单',
            'e':'直接退出',
        }
    
        exit_flag = False
        while not exit_flag:
            print(main_menu_bank)
            user_option = input('请输入您的选择的操作(33[32;1m返回上一层请输入r33[0m):').strip()
            if user_option in main_menu_bank_dic:
                if user_option == 'e':
                    login_out(acc_data)
                    break
                elif user_option == '1':
                    account_info(acc_data)
                    user_option = input('33[32;1m返回上一层请输入r33[0m:').strip()
                    if user_option == 'r':
                        interactive_bank(acc_data)
                    else:
                        print('33[31;1m选择不存在!33[0m返回上一层请输入r,直接退出请输入e')
                        account_info(acc_data)
                        user_option = input('33[32;1m返回上一层请输入r33[0m:').strip()
                        if user_option =='r':
                            interactive_bank(acc_data)
                        elif user_option == 'e':
                           login_out(acc_data)
                           break
                elif user_option == '2':
                    withdrawal(acc_data)
                elif user_option == '3':
                    repay(acc_data)
            elif user_option == 'r':
                return main_menu(acc_data)
            else:
                print('33[31;1m选择不存在!请按提示重新输入!!!33[0m')
    
    @login_required   #调用用户验证装饰器
    def withdrawal(acc_data):
        '''
        取款操作,打印可取款额度
        :param acc_data:
        :return:
        '''
        account_data = accounts.load_current_balance(acc_data['account_id'])
        current_balance = '''
        ---------银行信息----------
        信用额度: %s
        可用余额: %s
        账单日:   %s
        ''' % (account_data['credit'],account_data['balance'],account_data['pay_day'])
        print(current_balance)
        withdrawal_flag = False
        while not withdrawal_flag:
            withdrawal_amount = input("33[33;1m输入取款金额:33[0m").strip()
            if len(withdrawal_amount) > 0 and withdrawal_amount.isdigit():
                #将数据传入make_transaction中(交易日志,用户数据,交易类型,还款金额)进行操作,最后返回的是最新操作之后的账户数据
                new_balance = transaction.make_transaction(trans_logger,account_data,'withdrawal',withdrawal_amount)
                if  new_balance:
                    print('''33[42;1m最新的余额:%s33[0m''' %(new_balance['balance']))
                    return
                if withdrawal_flag == 'b':
                    withdrawal_flag == True
            elif withdrawal_amount == 'e':
                interactive_bank(acc_data)
            else:
                print('33[31;1m[%s]是无效的账户!33[0m' % withdrawal_amount)
            if withdrawal_amount == 'b':
                withdrawal_flag == True
    
    
    
    @login_required   #调用用户验证装饰器
    def repay(acc_data):
        '''
        还款操作,打印当前余额
        :param acc_data:
        :return:
        '''
        account_data = accounts.load_current_balance(acc_data['account_id'])
        current_balance = '''
        ---------银行信息----------
        信用额度: %s
        可用余额: %s
        账单日:   %s
        ''' % (account_data['credit'],account_data['balance'],account_data['pay_day'])
        print(current_balance)
        repay_flag = False
        while not repay_flag:
            repay_amount = input("33[33;1m输入你要还款的金额(重选操作请输入e):33[0m").strip()
            if len(repay_amount) > 0 and repay_amount.isdigit():
                #将数据传入make_transaction中(交易日志,用户数据,交易类型,还款金额)进行操作,最后返回的是最新操作之后的账户数据
                new_balance = transaction.make_transaction(trans_logger,account_data,'repay',repay_amount)
                if  new_balance:
                    print('''33[42;1m最新的余额:%s33[0m''' %(new_balance['balance']))
                    return
                if repay_amount == 'b':
                    repay_flag == True
            elif repay_amount == 'e':
                interactive_bank(acc_data)
            else:
                print('33[31;1m[%s]是无效的账户!33[0m' % repay_amount)
            if repay_amount == 'b':
                repay_flag == True
    online/Atm/core/main.py
     1 __author__ = 'ZFH'
     2 # 2018.09.26
     3 # -*- coding:utf-8 -*-
     4 #记账还钱取钱与账户金额相关的操作,冻结或者锁定用户
     5 # 交易处理模块
     6 from conf import settings
     7 from core import db_handler
     8 from core import accounts
     9 
    10 import json,time
    11 
    12 def make_transaction(log_obj,account_data,tran_type,amount,**kwargs):
    13     '''
    14     处理所有用户的交易
    15     :param log_obj: 写入日志
    16     :param account_data:用户最新数据
    17     :param tran_type:交易类型
    18     :param amount:交易金额
    19     :param kwargs:其他参数
    20     :return:返回最新的账户数
    21     '''
    22     amount = float(amount)    #转换为浮点数
    23     if tran_type in settings.TRANSACTION_TYPE:      #判断传入的类型是否在配置参数里面
    24         interest = round(amount * settings.TRANSACTION_TYPE[tran_type]['interest'],2)    #根据交易类型计算利息赋值给interest
    25         old_lalance = float(account_data['balance'])      #读取数据中账户余额
    26 #        print('interest:',type(interest))
    27 #        print('amount:',type(amount))
    28 #        print('old_lalance:',type(old_lalance))
    29 
    30         #还款操作
    31         if settings.TRANSACTION_TYPE[tran_type]['action'] == 'plus':      #因为是信用卡,所以需要判断操作操作,加plus
    32 #            print(settings.TRANSACTION_TYPE)
    33             new_balance = old_lalance + amount + interest     #执行的是信用卡的还款操作,计算方法是,旧余额+还款的钱和利息=最后的账户可用余额
    34          #取现
    35         elif settings.TRANSACTION_TYPE[tran_type]['action'] == 'minus' and settings.TRANSACTION_TYPE[tran_type] == 'transfer':    #因为是信用卡,所以取现都是降低可用余额的操作
    36             pass
    37         else:
    38             balance = old_lalance - amount - interest
    39             if balance > 0:
    40                 new_balance = balance
    41             else:
    42                 print("33[31;1m余额不足33[0m")
    43                 return
    44 
    45         account_data['balance'] = new_balance
    46 #        print(account_data)
    47         accounts.dump_account(account_data)
    48         return account_data
    49 
    50     else:
    51         print("33[31;1m%s交易类型不存在33[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
    
    
  • 相关阅读:
    伴郎
    MySQL出现Waiting for table metadata lock的场景浅析
    相同name,取最小的id的值,mysql根据相同字段 更新其它字段
    Sequence contains no elements
    Check if List<Int32> values are consecutive
    comparison of truncate vs delete in mysql/sqlserver
    Are query string keys case sensitive?浏览器种输入url附带的参数是否区分大小写
    Understanding Action Filters (C#) 可以用来做权限检查
    糖果缤纷乐攻略
    DNGuard HVM Unpacker(3.71 trial support and x64 fixed)
  • 原文地址:https://www.cnblogs.com/fameg/p/9790200.html
Copyright © 2020-2023  润新知