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 pythonView Code
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
1 #!/usr/bin/env pythonView Code
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
1 #!/usr/bin/env pythonView Code
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
1 #!/usr/bin/env pythonView Code
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
1 #!/usr/bin/env pythonView Code
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 pythonView Code
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
1 #!_*_coding:utf-8_*_View Code
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
1 #!/usr/bin/env pythonView Code
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
1 #!/usr/bin/env pythonView Code
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))