• python之ATM


      每次做一点就发出来,大神不要嫌重复

     

      2016/11/4

      今天来搞ATM,反正逃不了的,说来惭愧,这个作业是我10/4号20天前拿到的,当时是万脸蒙比的,今天又做了一点,现在算是百脸蒙比吧。

     

    一、需求:模拟实现一个ATM + 购物商城程序


      额度 15000或自定义 
      实现购物商城,买东西加入 购物车,调用信用卡接口结账   其实是两套单独程序
      可以提现,手续费5%  提现不能超过总余额一半   
      每月22号出账单,每月10号为还款日,过期未还,按欠款总额 万分之5 每日计息
      支持多账户登录,每个用户有单独信息
      支持账户间转账,
      记录每月日常消费流水
      提供还款接口
      ATM记录操作日志
      提供管理接口,包括添加账户、用户额度,冻结账户等。。。

     

    二、需求分析:

      角色:

          管理员功能:
                  增删改查
                  记录日志
                  基本信息
                  额度 15000
                
          普通用户功能:
              可以提现,手续费5%
              支持多账户登录
              支持账户间转账
              记录每月日常消费流水
              提供还款接口
              ATM记录操作日志

    三、文件创建

      刚开始的蒙比从不知道文件怎么创建开始?擦!怎么多需求,肯定不止一两个文件的,那多个文件又是怎么建的?我还特的心血来潮花一个早上去图去馆研究一下,最后挺乱的,后来看了视频,才发现有文件创建上是有开发规范的!

      

         

      bin  用于执行可执行文件

      conf  配置文件

      db  用于存放用户数据

      log  日志,记录相关信息

     

    四、begin funny coding

      这里我每次写一些,改一些,方便我这种小白看思路,想看最终版的直接拉到文章最后。

    16/11/4  9:22

      今天实现了检查帐户是否存在,信用卡是否超期的功能。自我感觉良好,哈哈~

      

    学到的新技能:

        1. 如何让字体打印出来有颜色??

    print("33[31;1mAccount[%s]doesnotexist!33[0m" % account)

        运行结果:Account [qee] does not exist!        打印出来是红色的

     

        2.如何将一个文件内的字符串形式通过json转化为相应的字典格式??

          用json.load()就好嘛,不过还是遇到一点小问题。

    #account_file是文件的绝对路径
    with open(account_file, "r", encoding="utf-8") as f:   #打开文件
      file_data = json.load(account_file)
        print(file_data)
    这样竟然出错了!! 错误信息:AttributeError:
    'str' object has no attribute 'read'

      

      于是我去看了下json.load()的源码。

    def load(fp, cls=None, object_hook=None, parse_float=None,
            parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
        """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
        a JSON document) to a Python object.
    
        ``object_hook`` is an optional function that will be called with the
        result of any object literal decode (a ``dict``). The return value of
        ``object_hook`` will be used instead of the ``dict``. This feature
        can be used to implement custom decoders (e.g. JSON-RPC class hinting).
    
        ``object_pairs_hook`` is an optional function that will be called with the
        result of any object literal decoded with an ordered list of pairs.  The
        return value of ``object_pairs_hook`` will be used instead of the ``dict``.
        This feature can be used to implement custom decoders that rely on the
        order that the key and value pairs are decoded (for example,
        collections.OrderedDict will remember the order of insertion). If
        ``object_hook`` is also defined, the ``object_pairs_hook`` takes priority.
    
        To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
        kwarg; otherwise ``JSONDecoder`` is used.
    
        """
        return loads(fp.read(),
            cls=cls, object_hook=object_hook,
            parse_float=parse_float, parse_int=parse_int,
            parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
    View Code

      

      注意这一句return loads(fp.read(),……),我以为搞个文件的绝对路径就可以的,结果错误信息,当然是路径没有read()功能。

    #改正:
    if os.path.isfile(account_file):     #如果用户文件存在(即用户存在)
        with open(account_file, "r", encoding="utf-8") as f:   #打开文件
            file_data = json.load(f)
            print(file_data)

    运行正确,GOOD!

        3.如何把时间字符串转化为时间戳(忘了的)

          先通过time.strptime()将时间字符串转成struct_time格式,再通过time.mktime()将struct_time转成时间戳。 

     11/4  文件形式

    atm.py

    """
    ATM程序的执行文件
    """
    import os
    import sys
    
    dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))   #找到路径
    sys.path.insert(0, dir)     #添加路径
    print(dir)
    
    #将main.py里面所有代码封装成main变量
    from core import main
    
    if __name__ == "__main__":
        #这里我刚开始用run()就爆错了
        main.run()
    View Code

     

    settings.py

     1 """
     2 初始化的配置
     3 """
     4 
     5 import logging
     6 import os
     7 
     8 #到ATM目录,方便后面创建帐户文件
     9 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    10 
    11 LOGIN_LEVEL = logging.INFO    #初始化日志级别
    12 
    13 
    14 DATABASE = {
    15     "db_tool":"file_storage",   #文件存储,这里可拓展成数据库形式的
    16     "name":"accounts",          #db下的文件名
    17     "path":"%s/db" % BASE_DIR
    18 }
    View Code

     

    auth.py

     1 """
     2 认证模块
     3 """
     4 print("BB")
     5 import os
     6 import json
     7 import time
     8 
     9 from core import db_handle
    10 from conf import settings
    11 
    12 
    13 def access_auth(account, password):
    14     """
    15     下面的access_login调用access_auth方法,用于登陆
    16     :param acount: 用户名
    17     :param password: 密码
    18     :return:如果未超期,返回字典,超期则打印相应提示
    19     """
    20     db_path = db_handle.handle(settings.DATABASE)    #调用db_handle下的handle方法,返回路径/db/accounts
    21     print(db_path)
    22     account_file = "%s/%s.json" % (db_path, account)    #用户文件
    23     print(account_file)
    24     if os.path.isfile(account_file):     #如果用户文件存在(即用户存在)
    25         with open(account_file, "r", encoding="utf-8") as f:   #打开文件
    26             account_data = json.load(f)   #file_data为字典形式
    27             print(account_data)
    28             if account_data["password"] == password:
    29                 expire_time = time.mktime(time.strptime(account_data["expire_date"], "%Y-%m-%d"))
    30                 print(expire_time)
    31                 print(time.strptime(account_data["expire_date"], "%Y-%m-%d"))
    32                 if time.time() > expire_time:   #如果信用卡已超期
    33                     print("33[31;1mAccount %s had expired,Please contract the bank" % account)
    34                 else:     #信用卡未超期,返回用户数据的字典
    35                     print("return")
    36                     return account_data
    37             else:
    38                 print("33[31;1mAccount or Passworddoes not correct!33[0m")
    39     else:  #用户不存在
    40         print("33[31;1mAccount [%s] does not exist!33[0m" % account)
    41 
    42 
    43 
    44 
    45 def access_login(user_data):
    46     """
    47     用记登陆,当登陆失败超过三次??
    48     :param user_data:
    49     :return:
    50     """
    51     retry = 0
    52     while not user_data["is_authenticated"] and retry < 3:
    53         account = input("Account:").strip()
    54         password = input("Password:").strip()
    55         access_auth(account, password)
    View Code

     

    db_handle.py

     1 """
     2 处理与数据库的交互,若是file_db_storage,返回路径
     3 """
     4 
     5 def file_db_handle(database):
     6     """
     7     数据存在文件
     8     :param database:
     9     :return: 返回路径  ATM/db/accounts
    10     """
    11     db_path = "%s/%s" % (database["path"], database["name"])
    12     print(db_path)
    13     return db_path
    14 
    15 
    16 
    17 def mysql_db_handle(database):
    18     """
    19     处理mysql数据库,这里用文件来存数据,
    20     保留这个方法主要为了程序拓展性
    21     :return:
    22     """
    23     pass
    24 
    25 
    26 def handle(database):
    27     """
    28     对某种数据库形式处于是
    29     本程序用的是文件处理file_storage
    30     :param database: settings里面的DATABASE
    31     :return: 返回路径
    32     """
    33     if database["db_tool"] == "file_storage":
    34         return file_db_handle(database)
    35     if database["db_tool"] == "mysql":
    36         return mysql_db_handle(database)
    View Code

     

    main.py

     1 """
     2 主逻辑交互模块
     3 """
     4 from core import auth
     5 
     6 
     7 #用户数据信息
     8 user_data = {
     9     'account_id':None,          #帐号ID
    10     'is_authenticated':False,  #是否认证
    11     'account_data':None        #帐号数据
    12 
    13 }
    14 
    15 
    16 def account_info():
    17     """
    18     用户帐户信息
    19     :return:
    20     """
    21     pass
    22 
    23 
    24 def repay():
    25     """
    26     还款
    27     :return:
    28     """
    29     pass
    30 
    31 
    32 def withdraw():
    33     """
    34     退出
    35     :return:
    36     """
    37     pass
    38 
    39 
    40 def transfer():
    41     """
    42     转帐
    43     :return:
    44     """
    45     pass
    46 
    47 
    48 def paycheck():
    49     """
    50     转帐检查
    51     :return:
    52     """
    53     pass
    54 
    55 
    56 def logout():
    57     """
    58     退出登陆
    59     :return:
    60     """
    61     pass
    62 
    63 
    64 def interactive():
    65     """
    66     交互
    67     :return:
    68     """
    69     pass
    70 
    71 
    72 
    73 def run():
    74     """
    75     当程序启动时调用,用于实现主要交互逻辑
    76     :return:
    77     """
    78     access_data = auth.access_login(user_data)   #调用认证模块,返回用户文件json.load后的字典
    79     print("AA")
    View Code

     

    zcl.json

    {"status": 0, "expire_date": "2021-01-01", "credit": 15000, "pay_day": 22, "balance": 15000, "enroll_date": "2016-01-02", "id": 22, "password": "abc"}

       测试结果1:

    C:UsersAdministratorPycharmProjectslaonanhaiATM
    BB
    Account:zcl
    Password:abc
    C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts/zcl.json
    {'id': 22, 'credit': 15000, 'password': 'abc', 'status': 0, 'expire_date': '2012-01-01', 'enroll_date': '2016-01-02', 'pay_day': 22, 'balance': 15000}
    1325347200.0
    time.struct_time(tm_year=2012, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=1, tm_isdst=-1)
    Account zcl had expired,Please contract the bank
    Account:
    View Code

      测试结果2:

    C:UsersAdministratorPycharmProjectslaonanhaiATM
    BB
    Account:zcl
    Password:abc
    C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts/zcl.json
    {'expire_date': '2021-01-01', 'status': 0, 'enroll_date': '2016-01-02', 'balance': 15000, 'id': 22, 'password': 'abc', 'credit': 15000, 'pay_day': 22}
    1609430400.0
    time.struct_time(tm_year=2021, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=1, tm_isdst=-1)
    return
    Account:
    View Code

     

      OK, 今日退朝,明日继续!

    思路总结-1:

      1.amt.py运行时调用core目录下的main.py

      2.main.py调用core目录下的auth.py进行登陆和信用卡认证

      3.auth.py调用db_handle.py,返回帐号文件的路径,进行信用卡帐户检查

      4.auth.py进行登陆认证,登陆失败不能超过三次(未完全实现)

    2016/11/5  10:38

      今天完善了登陆功能,实现了日志功能,写了一点点交互功能,比如查看帐户信息,自我感觉良好。

    学到新技能:

      1.看下面这段代码

     1 msg = (
     2         """
     3         -------------ZhangChengLiang Bank---------------
     4         33[31;1m 1.  账户信息
     5         2.  还款
     6         3.  取款
     7         4.  转账
     8         5.  账单
     9         6.  退出
    10         33[0m"""
    11     )
    12     menu_dic = {
    13         "1":account_info,
    14         "2":repay,
    15         "3":withdraw,
    16         "4":transfer,
    17         "5":paycheck,
    18         "6":logout
    19     }
    20     flag = False
    21     while not flag:
    22         print(msg)
    23         choice = input("<<<:").strip()
    24         if choice in menu_dic:
    25             #很重要!!省了很多代码,不用像之前一个一个判断!
    26             menu_dic[choice](acc_data)
    27 
    28         else:
    29             print("33[31;1mYou choice doesn't exist!33[0m")
    View Code

    当然,在面向对象可以用反射(我还没有过),但这里我感觉用得相当巧!!不用像我之前一个一个用if...else..来判断。

      2.掌握日志的用法

    之前只是知道,写单独的一个文件,这次综合的运用,感觉我对日志的用法已经相当有信心了。

    16/11/5  文件目录

     与昨天的基本没有什么变化,只是运行时在log包下多了个access.log文件还有在core包下多了log.py文件

    11/5  代码展示

    atm.py 不变

    settings.py

      只多了个LOGIN_TYPE字典,用于后面生成access.log日志文件

     1 """
     2 初始化的配置
     3 """
     4 
     5 import logging
     6 import os
     7 
     8 #到ATM目录,方便后面创建帐户文件
     9 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    10 
    11 LOGIN_LEVEL = logging.INFO    #初始化日志级别
    12 
    13 LOGIN_TYPE = {
    14     "access":"access.log"
    15 }
    16 
    17 
    18 DATABASE = {
    19     "db_tool":"file_storage",   #文件存储,这里可拓展成数据库形式的
    20     "name":"accounts",          #db下的文件名
    21     "path":"%s/db" % BASE_DIR
    22 }
    View Code

    auth.py

      完善了登陆功能,日志功能

     1 """
     2 认证模块
     3 """
     4 print("BB")
     5 import os
     6 import json
     7 import time
     8 
     9 from core import db_handle
    10 from conf import settings
    11 
    12 
    13 def access_auth(account, password, log_obj):
    14     """
    15     下面的access_login调用access_auth方法,用于登陆
    16     :param acount: 用户名
    17     :param password: 密码
    18     :return:如果未超期,返回字典,超期则打印相应提示
    19     """
    20     db_path = db_handle.handle(settings.DATABASE)    #调用db_handle下的handle方法,返回路径/db/accounts
    21     print(db_path)
    22     account_file = "%s/%s.json" % (db_path, account)    #用户文件
    23     print(account_file)
    24     if os.path.isfile(account_file):     #如果用户文件存在(即用户存在)
    25         with open(account_file, "r", encoding="utf-8") as f:   #打开文件
    26             account_data = json.load(f)   #file_data为字典形式
    27             print(account_data)
    28             if account_data["password"] == password:
    29                 expire_time = time.mktime(time.strptime(account_data["expire_date"], "%Y-%m-%d"))
    30                 print(expire_time)
    31                 print(time.strptime(account_data["expire_date"], "%Y-%m-%d"))
    32                 if time.time() > expire_time:   #如果信用卡已超期
    33                     log_obj.error("Account [%s] had expired,Please contract the bank" % account)
    34                     print("33[31;1mAccount [%s] had expired,Please contract the bank" % account)
    35                 else:     #信用卡未超期,返回用户数据的字典
    36                     print("return")
    37                     log_obj.info("Account [%s] logging success" % account)
    38                     return account_data
    39             else:
    40                 log_obj.error("Account or Passworddoes not correct!")
    41                 print("33[31;1mAccount or Passworddoes not correct!33[0m")
    42     else:  #用户不存在
    43         log_obj.error("Account [%s] does not exist!" % account)
    44         print("33[31;1mAccount [%s] does not exist!33[0m" % account)
    45 
    46 
    47 
    48 
    49 def access_login(user_data, log_obj):
    50     """
    51     用记登陆,当登陆失败超过三次则退出
    52     :param user_data: main.py里面的字典
    53     :return:若用户帐号密码正确且信用卡未超期,返回用户数据的字典
    54     """
    55     retry = 0
    56     while not user_data["is_authenticated"] and retry < 3:
    57         account = input("Account:").strip()
    58         password = input("Password:").strip()
    59         #用户帐号密码正确且信用卡未超期,返回用户数据的字典
    60         user_auth_data = access_auth(account, password, log_obj)
    61         if user_auth_data:
    62             user_data["is_authenticated"] = True   #用户认证为True
    63             user_data["account_id"] = account       #用户帐号ID为帐号名
    64             print("welcome")
    65             return user_auth_data
    66         retry += 1      #登陆和信用卡认证出错,则次数加1
    67 
    68     else:        #若次数超过三次,打印相关信息并退出
    69         print("Account [%s] try logging too many times..." % account)
    70         log_obj.error("Account [%s] try logging too many times..." % account)
    71         exit()
    View Code

    db_handle.py不变

    log.py

       实现日志功能,我里我先文件和屏幕都有输出。

     1 import logging
     2 from conf import settings
     3 
     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对象
    34 
    35     # logger.debug('debugmessage')
    36     # logger.info('infomessage')
    37     # logger.warn('warnmessage')
    38     # logger.error('errormessage')
    39     # logger.critical('criticalmessage')
    View Code

    main.py

      增加了一点用交互功能(未完成)

      1 """
      2 主逻辑交互模块
      3 """
      4 from core import auth
      5 from core import log
      6 
      7 
      8 #用户数据信息
      9 user_data = {
     10     'account_id':None,          #帐号ID
     11     'is_authenticated':False,  #是否认证
     12     'account_data':None        #帐号数据
     13 
     14 }
     15 
     16 #调用log文件下的log方法,返回日志对象
     17 access_logger = log.log("access")
     18 
     19 
     20 def account_info(acc_data):
     21     """
     22     查看用户帐户信息
     23     :return:
     24     """
     25     print(acc_data)
     26 
     27 
     28 def repay(acc_data):
     29     """
     30     还款
     31     :return:
     32     """
     33     pass
     34 
     35 
     36 def withdraw():
     37     """
     38     退出
     39     :return:
     40     """
     41     pass
     42 
     43 
     44 def transfer():
     45     """
     46     转帐
     47     :return:
     48     """
     49     pass
     50 
     51 
     52 def paycheck():
     53     """
     54     转帐检查
     55     :return:
     56     """
     57     pass
     58 
     59 
     60 def logout():
     61     """
     62     退出登陆
     63     :return:
     64     """
     65     pass
     66 
     67 
     68 def interactive(acc_data):
     69     """
     70     用户交互
     71     :return:
     72     """
     73     msg = (
     74         """
     75         -------------ZhangChengLiang Bank---------------
     76         33[31;1m 1.  账户信息
     77         2.  还款
     78         3.  取款
     79         4.  转账
     80         5.  账单
     81         6.  退出
     82         33[0m"""
     83     )
     84     menu_dic = {
     85         "1":account_info,
     86         "2":repay,
     87         "3":withdraw,
     88         "4":transfer,
     89         "5":paycheck,
     90         "6":logout
     91     }
     92     flag = False
     93     while not flag:
     94         print(msg)
     95         choice = input("<<<:").strip()
     96         if choice in menu_dic:
     97             #很重要!!省了很多代码,不用像之前一个一个判断!
     98             menu_dic[choice](acc_data)
     99 
    100         else:
    101             print("33[31;1mYou choice doesn't exist!33[0m")
    102 
    103 
    104 
    105 def run():
    106     """
    107     当程序启动时调用,用于实现主要交互逻辑
    108     :return:
    109     """
    110     # 调用认证模块,返回用户文件json.load后的字典,传入access_logger日志对象
    111     access_data = auth.access_login(user_data, access_logger)
    112     print("AA")
    113     if user_data["is_authenticated"]:       #如果用户认证成功
    114         print("has authenticated")
    115         #将用户文件的字典赋给user_data["account_data"]
    116         user_data["account_data"] = access_data
    117         interactive(user_data)   #用户交互开始
    View Code

    16/5 测试用例

     1 C:UsersAdministratorPycharmProjectslaonanhaiATM
     2 BB
     3 Account:123
     4 Password:333
     5 2016-11-05 23:03:55,210-access-ERROR-Account [123] does not exist!
     6 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
     7 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
     8 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts/123.json
     9 Account [123] does not exist!
    10 Account:zcl
    11 Password:abc
    12 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    13 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    14 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts/zcl.json
    15 {'balance': 15000, 'enroll_date': '2016-01-02', 'expire_date': '2021-01-01', 'status': 0, 'id': 22, 'password': 'abc', 'credit': 15000, 'pay_day': 22}
    16 2016-11-05 23:04:08,977-access-INFO-Account [zcl] logging success
    17 1609430400.0
    18 time.struct_time(tm_year=2021, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=1, tm_isdst=-1)
    19 return
    20 welcome
    21 AA
    22 has authenticated
    23 
    24         -------------ZhangChengLiang Bank---------------
    25          1.  账户信息
    26         2.  还款
    27         3.  取款
    28         4.  转账
    29         5.  账单
    30         6.  退出
    31         
    32 <<<:1
    33 {'account_data': {'balance': 15000, 'enroll_date': '2016-01-02', 'expire_date': '2021-01-01', 'status': 0, 'id': 22, 'password': 'abc', 'credit': 15000, 'pay_day': 22}, 'is_authenticated': True, 'account_id': 'zcl'}
    34 
    35         -------------ZhangChengLiang Bank---------------
    36          1.  账户信息
    37         2.  还款
    38         3.  取款
    39         4.  转账
    40         5.  账单
    41         6.  退出
    View Code

    思路总结-2:

      1.log.py设置日志级别、生成日志对象,返回logger日志对象

      2.main.py在调用auth.py认证模块时,传logger对象

      3.通过logger对象进行一系列操作

      4.main.py中run()方法调用interactive交互接口

      5.interactive()方法通过用户选择进行交互(大部分还未实现)

    11/5 OK,明日继续!

    11/6  半夜三更

       今天都在实现用户的交互功能,我只实现了存款功能repay,别看只实现了一个,我拓展性搞得还可以的!其他未实现,但感觉套路都是差不多的。

    (重要)思路VS思考

    在实现用户交互功能之一(存款)时,我的思考如下:

    1. 在main.py下的repay()方法中搞个while循环,让用户输入存款的金额
    2. 接着我须要搞一个模块来处理存款的数据,比如说将用户更新后的balance写到用户数据库中
    3. 再想,不对!按2的说法,我同样也需要为提现写一个模块,转帐写一个模块,取款写一个模块…,因为这些交互都使用户的金额数据产生变化
    4. 按第3的做法,拓展性不好,也是要我的老命啊! 那我应该搞一个通用的模块,于是我写了transaction.py来处理用户交易
    5. 接下来注意了,这个点我想了很久!
    • 在transaction.py中的make_transaction() 方法中我传的是用户信息的字典(登陆时从用户数据库中load出来的)
    • 在make_transaction()写到差不多时,我意识到需要另写一个模块来处理用户更新后的信息,写到用户数据库中去(用户每进行数据的操作,都要及时写都数据库中去)
    • 于是我建了account.py模块,在模块写了dump_account()方法
    • 问题来了,当我第二次存款时,发现我第一次存的钱不见了!比如我原来有15000,第一次存500,第二次想再存时,发现帐户金额还是15000,擦,尴尬了。
    • 分析这个问题,很简单,原因在于我第二次调用repay()时没有从用户数据库中load()出来,导致内存中用户数据不变(当然make_transaction中的用户数据是变的,才能将更新后的用户数据写到数据库中去,不过这两份用户数据只是实参与形参,无直接关系,不影响),在repay中打印出来的balance/credit是不变的!!
    • finally,我在每次调用repay时,都先将用户信息读出来,就完美解决了!      

      6. 到这里我大悟,每次用户调用ATM交互功能,应先将用户信息读出来,每一个功能结束后都应将信息更新到数据库中去

    11/6 新技能

    1.用户交易模块的拓展性惊人

    def make_transaction(account_data, transaction_type, amount): 这里的transaction_type交易类型(存,取,转…)

    在settings.py定义好交易的类型,拓展性很好!

    1 #用户交易类型,每个类型对应一个字典,包括帐户金额变动方式,利息
    2 TRANSACTION_TYPE = {
    3     "repay":{"action":"plus", "interest":0},  #存款
    4     "withdraw":{"action":"minus", "interest": 0.05}  #取款(提现)
    5 }



    2. if num == "b" or "back"

      这里我SB了,哈哈~

    1 num = input(">>>>:")
    2 if num == "b" or "back":
    3     print("OK")
    4 else:
    5     print("ERROR")

    这里我测试时,明明输入的不是"b" 或者"back", 但结果却是“OK", 一度让我怀疑人生,于是我做了上面的测试。

    仔细一想, or "back",“back” 不为0,所以这个判断为True, 一定输出的是"OK".      .......以后再也不敢了~

    11/6  文件目录

      与11/5多了transaction.py模块,account.py模块,test.py是我自己测试用的,可以忽略~

    代码展示:

    main.py

      实现了还款功能

      1 """
      2 主逻辑交互模块
      3 """
      4 from core import auth
      5 from core import log
      6 from core import transaction
      7 from core import account
      8 
      9 
     10 #用户数据信息
     11 user_data = {
     12     'account_id':None,          #帐号ID
     13     'is_authenticated':False,  #是否认证
     14     'account_data':None        #帐号数据
     15 
     16 }
     17 
     18 #调用log文件下的log方法,返回日志对象
     19 access_logger = log.log("access")
     20 
     21 
     22 def account_info(acc_data):
     23     """
     24     acc_data:包括ID,is_authenticaed,用户帐号信息
     25     查看用户帐户信息
     26     :return:
     27     """
     28     print(acc_data)
     29 
     30 
     31 def repay(acc_data):
     32     """
     33     acc_data:包括ID,is_authenticaed,用户帐号信息
     34     还款
     35     :return:
     36     """
     37     print(acc_data)
     38     print("??")
     39     #调用account模块的load_account方法,从数据库从load出用户信息
     40     account_data = account.load_account(acc_data["id"])
     41     print(account_data)
     42     current_balance = """
     43     -------------BALANCE INFO--------------
     44     Credit:%s
     45     Balance:%s
     46     """ % (account_data["credit"], account_data["balance"])
     47     back_flag = False
     48     while not back_flag:
     49         print(current_balance)
     50         repay_amount = input("33[31;1mInput repay amount(b=back):33[0m").strip()
     51         #如果用户输入整型数字
     52         if len(repay_amount) > 0 and repay_amount.isdigit():
     53             #调用transaction模块的方法,参数分别是用户帐户信息,交易类型,交易金额
     54             new_account_data = transaction.make_transaction(account_data, "repay", repay_amount)
     55             if new_account_data:
     56                 print("33[42;1mNew Balance:%s33[0m" % new_account_data["balance"])
     57 
     58         else:
     59             print("33[31;1m%s is not valid amount,Only accept interger!33[0m" % repay_amount)
     60 
     61         if repay_amount =="b" or repay_amount == "back":
     62             back_flag = True
     63 
     64 def withdraw():
     65     """
     66     取款
     67     :return:
     68     """
     69     pass
     70 
     71 
     72 def transfer():
     73     """
     74     转帐
     75     :return:
     76     """
     77     pass
     78 
     79 
     80 def paycheck():
     81     """
     82     转帐检查
     83     :return:
     84     """
     85     pass
     86 
     87 
     88 def logout():
     89     """
     90     退出登陆
     91     :return:
     92     """
     93     pass
     94 
     95 
     96 def interactive(acc_data):
     97     """
     98     用户交互
     99     :return:
    100     """
    101     msg = (
    102         """
    103         -------------ZhangChengLiang Bank---------------
    104         33[31;1m 1.  账户信息
    105         2.  存款
    106         3.  取款
    107         4.  转账
    108         5.  账单
    109         6.  退出
    110         33[0m"""
    111     )
    112     menu_dic = {
    113         "1":account_info,
    114         "2":repay,
    115         "3":withdraw,
    116         "4":transfer,
    117         "5":paycheck,
    118         "6":logout
    119     }
    120     flag = False
    121     while not flag:
    122         print(msg)
    123         choice = input("<<<:").strip()
    124         if choice in menu_dic:
    125             #很重要!!省了很多代码,不用像之前一个一个判断!
    126             menu_dic[choice](acc_data)
    127 
    128         else:
    129             print("33[31;1mYou choice doesn't exist!33[0m")
    130 
    131 
    132 
    133 def run():
    134     """
    135     当程序启动时调用,用于实现主要交互逻辑
    136     :return:
    137     """
    138     # 调用认证模块,返回用户文件json.load后的字典,传入access_logger日志对象
    139     access_data = auth.access_login(user_data, access_logger)
    140     print("AA")
    141     if user_data["is_authenticated"]:       #如果用户认证成功
    142         print("has authenticated")
    143         #将用户文件的字典赋给user_data["account_data"]
    144         user_data["account_data"] = access_data
    145         interactive(user_data)   #用户交互开始
    View Code

    settings.py

      增加了用户交易类型TRANSACTION_TYPE{}

     1 """
     2 初始化的配置
     3 """
     4 
     5 import logging
     6 import os
     7 
     8 #到ATM目录,方便后面创建帐户文件
     9 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    10 
    11 LOGIN_LEVEL = logging.INFO    #初始化日志级别
    12 
    13 LOGIN_TYPE = {
    14     "access":"access.log"
    15 }
    16 
    17 
    18 DATABASE = {
    19     "db_tool":"file_storage",   #文件存储,这里可拓展成数据库形式的
    20     "name":"accounts",          #db下的文件名
    21     "path":"%s/db" % BASE_DIR
    22 }
    23 
    24 #用户交易类型,每个类型对应一个字典,包括帐户金额变动方式,利息
    25 TRANSACTION_TYPE = {
    26     "repay":{"action":"plus", "interest":0},  #存款
    27     "withdraw":{"action":"minus", "interest": 0.05}  #取款(提现)
    28 }
    View Code

    transaction.py

      处理用户交易的模块

     1 """
     2 交易模块,处理用户金额移动
     3 """
     4 import json
     5 from conf import settings
     6 from core import account
     7 
     8 
     9 def make_transaction(account_data, transaction_type, amount):
    10     """
    11     处理用户的交易
    12     :param account_data:字典,用户的帐户信息
    13     :param transaction_type:用户交易类型,repay or withdraw...
    14     :param amount:交易金额
    15     :return:用户交易后帐户的信息
    16     """
    17     #将字符串类型转换为float类型
    18     amount = float(amount)
    19     #如果交易类型存在
    20     if transaction_type in settings.TRANSACTION_TYPE:
    21         #利息金额
    22         interest = amount * settings.TRANSACTION_TYPE[transaction_type]["interest"]
    23         #用户原金额
    24         old_balace = account_data["balance"]
    25         print(interest,old_balace)
    26         #如果帐户金额变化方式是"plus",加钱
    27         if settings.TRANSACTION_TYPE[transaction_type]["action"] == "plus":
    28             new_balance = old_balace + amount + interest
    29         #如果帐户金额变化方式是"minus",减钱
    30         elif settings.TRANSACTION_TYPE[transaction_type]["action"] == "minus":
    31             new_balance = old_balace - amount - interest
    32             #减钱时对帐户金额进行检查,防止超额
    33             if new_balance < 0:
    34                 print("33[31;1mYour Credit [%s] is not enough for transaction [-%s], and Now your"
    35                       " current balance is [%s]" % (account_data["credit"], (amount+interest), old_balace))
    36                 return
    37 
    38         account_data["balance"] = new_balance
    39         #调用core下account模块将已更改的用户信息更新到用户文件
    40         account.dump_account(account_data)
    41         return account_data
    42 
    43     else:
    44         print("33[31;1mTransaction is not exist!033[0m")
    View Code

    account.py

      将用户信息读出或写入的模块

     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")
    View Code

    测试用例 

     1 C:UsersAdministratorPycharmProjectslaonanhaiATM
     2 BB
     3 Account:zcl
     4 Password:abc
     5 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
     6 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
     7 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts/zcl.json
     8 {'balance': 26000.0, 'expire_date': '2021-01-01', 'credit': 15000, 'status': 0, 'enroll_date': '2016-01-02', 'password': 'abc', 'pay_day': 22, 'id': 'zcl'}
     9 1609430400.0
    10 time.struct_time(tm_year=2021, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=1, tm_isdst=-1)
    11 return
    12 2016-11-07 13:42:24,678-access-INFO-Account [zcl] logging success
    13 welcome
    14 AA
    15 has authenticated
    16 
    17         -------------ZhangChengLiang Bank---------------
    18          1.  账户信息
    19         2.  存款
    20         3.  取款
    21         4.  转账
    22         5.  账单
    23         6.  退出
    24         
    25 <<<:2
    26 {'is_authenticated': True, 'account_data': {'balance': 26000.0, 'expire_date': '2021-01-01', 'credit': 15000, 'status': 0, 'enroll_date': '2016-01-02', 'password': 'abc', 'pay_day': 22, 'id': 'zcl'}, 'account_id': None, 'id': 'zcl'}
    27 ??
    28 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    29 {'balance': 26000.0, 'expire_date': '2021-01-01', 'credit': 15000, 'status': 0, 'enroll_date': '2016-01-02', 'password': 'abc', 'pay_day': 22, 'id': 'zcl'}
    30 
    31     -------------BALANCE INFO--------------
    32     Credit:15000
    33     Balance:26000.0
    34     
    35 Input repay amount(b=back):4500
    36 0.0 26000.0
    37 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    38 dump success
    39 New Balance:30500.0
    40 
    41     -------------BALANCE INFO--------------
    42     Credit:15000
    43     Balance:26000.0
    44     
    45 Input repay amount(b=back):back
    46 back is not valid amount,Only accept interger!
    47 
    48         -------------ZhangChengLiang Bank---------------
    49          1.  账户信息
    50         2.  存款
    51         3.  取款
    52         4.  转账
    53         5.  账单
    54         6.  退出
    55         
    56 <<<:2
    57 {'is_authenticated': True, 'account_data': {'balance': 26000.0, 'expire_date': '2021-01-01', 'credit': 15000, 'status': 0, 'enroll_date': '2016-01-02', 'password': 'abc', 'pay_day': 22, 'id': 'zcl'}, 'account_id': None, 'id': 'zcl'}
    58 ??
    59 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    60 {'balance': 30500.0, 'expire_date': '2021-01-01', 'credit': 15000, 'status': 0, 'enroll_date': '2016-01-02', 'password': 'abc', 'pay_day': 22, 'id': 'zcl'}
    61 
    62     -------------BALANCE INFO--------------
    63     Credit:15000
    64     Balance:30500.0
    65     
    66 Input repay amount(b=back):10000
    67 0.0 30500.0
    68 C:UsersAdministratorPycharmProjectslaonanhaiATM/db/accounts
    69 dump success
    70 New Balance:40500.0
    71 
    72     -------------BALANCE INFO--------------
    73     Credit:15000
    74     Balance:30500.0
    75     
    76 Input repay amount(b=back):
    View Code

    思路总结-3 

      1.repay还款功能调用account.py通过ID读出用户信息

      2.repay还款功能调用transaction.py交易模块

      3.transaction.py交易模块调用account.py更新用户数据到数据库

      4.settings.py里的TRANSACTION_TYPE字典传入交易方法,实现拓展性

    今日超神,明日继续实现交互功能或体息一天!

    11/8

     做了还款,取款,转帐功能,都是套路了,难度不大。

    咋晚给舍友看了我做了东西,竟然被舍友说,亮点在哪里。时间有限,生命有限,继续学习之路,这个小项目就到此为止了。

    ATM压缩包:http://pan.baidu.com/s/1hrNF5TM

  • 相关阅读:
    IDEA 中直接连接远程服务器
    浙江大学软件学院2020年保研上机
    PAT甲级近五年题一览表
    浙江大学计算机与软件学院2021年考研复试上机
    浙江大学计算机与软件学院2019年保研上机
    PAT(甲级)2021年春季考试
    PAT(甲级)2020年冬季考试
    PAT(甲级)2020年秋季考试
    PAT(甲级)2020年春季考试
    PAT(甲级)2019年冬季考试
  • 原文地址:https://www.cnblogs.com/0zcl/p/6031815.html
Copyright © 2020-2023  润新知