Python实现ATM+购物商城

Python实现ATM+购物商城

 

一、程序介绍

需求:

模拟实现一个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

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

 

conf/settings.py

 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 }
View Code

 

 

core/main.py

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

 

 

core/transaction.py

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

 

 

core/accounts.py

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

 

 

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

 

 

core/db_handler.py

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

 

 

core/logger.py

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

 

db/account_sample.py

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

 

 

 

 





posted @ 2017-10-14 01:06  朱志文  阅读(2548)  评论(0编辑  收藏  举报