python 模拟实现一个ATM + 购物商城程序
2018-11-29 16:47 小于漫谈 阅读(398) 评论(0) 编辑 收藏 举报思路:ATM是一个单独程序,提供给消费的是一个接口core下的settlement.py,只做了个人的,没写管理的模块
Shopping也是一个单独的,只做了一个购物的消费模块,没写商家模块,偷懒用了银行的数据库,用户名和密码都是用的一套的
具体目录如下:
atm.py:
import os
import sys
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# print(base_dir)
sys.path.append(base_dir)
from core import main
if __name__ == '__main__':
main.run()
settings.py:
import os
import sys
import logging
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATABASE = {
'engine': 'file_storage',
'name':'accounts',
'path':"%s\\db" % BASE_DIR
}
LOG_LEVEL = logging.INFO
LOG_TYPES = {
'transaction':'transactions.log',
'access':'access.log',
}
TRANSACTION_TYPE = {
'repay':{'action':'plus','interest':0},
'withdraw':{'action':'minus','interest':0.05},
'transfer':{'action':'minus','interest':0.05},
'consume':{'action':'minus','interest':0},
}
accounts.py:
import json,os,sys,time
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from core import db_handler
from conf import settings
def load_current_balance(account_id):
"""
:param account_id:
:return:返回账号的资金和其它信息
"""
db_path = db_handler.db_handler(settings.DATABASE)
account_file = "%s\%s.json" %(db_path,account_id)
with open(account_file) as f:
acc_data = json.load(f)
return acc_data
def dump_account(account_data):
db_path = db_handler.db_handler(settings.DATABASE)
account_file = "%s\%s.json" %(db_path,account_data['id'])
with open(account_file,'w') as f:
acc_data = json.dump(account_data,f)
return True
auth.py:
import os
import sys
import json
import time
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from core import db_handler
from conf import settings
from core import logger
def acc_auth(account,password):
"""
:param account: 信用卡账号
:param password: 信用卡密码
:return:
"""
db_path = db_handler.db_handler(settings.DATABASE) #数据库存储的目录
account_file = "%s\%s.json" %(db_path,account) #个人的json数据
# print(account_file)
if os.path.isfile(account_file): #还必须判断这个文件是否存在
with open(account_file) as f:
account_data = json.load(f) #还原成字典
if account_data['password'] == password:
#mktime() 接收struct_time对象作为参数,返回用秒数来表示时间的浮点数,
# time.strptime(string[, format])根据指定的格式把一个时间字符串解析为时间元组
exp_time_stamp = time.mktime(time.strptime(account_data['expire_date'],"%Y-%m-%d"))
if time.time() > exp_time_stamp:
print("\033[31;1m你的账号[%s]已经过期,请联系银行服务中心\033[0m"%account)
else: #如果通过了,就返回账号的所有信息
return account_data
else:
print("\033[31;1m你的账号或者密码错误\033[0m")
else:
print("\033[31;1m账号[%s]不存在!\033[0m"% account)
def acc_login(user_data,log_obj):
"""
登录接口
:param user_data:user_data在主函数定义是临时账户信息
:param log_obj:log_obj执行完logger()之后返回的logger,可以调用相关方法写入日志信息
:return:
"""
retry_count = 0
while user_data['is_authenticated'] is not True and retry_count < 3:
account = input("请输入账号:").strip()
password = input("请输入密码:").strip()
auth = acc_auth(account,password)
if auth: #不是空代表验证成功了,因为验证成功之后会返回用户所有信息
user_data['is_authenticated'] = True
user_data['account_id'] = account
return auth #又返回了用户的全部数据
retry_count +=1
else:
log_obj.error("account[%s] too many login attempts" %account)
exit()
db_handler.py:
"""
根据数据库的类型,对数据进行处理
"""
import os
def file_db_handle(conn_params):
"""
数据库的存放的路径
:param conn_params: 传入的数据是settings中的DATABASE
:return:
"""
l = os.sep
# print('file db:',conn_params)
db_path = '%s\%s' %(conn_params['path'],conn_params['name'])
return db_path
def db_handler(conn_parms):
"""
根据不同的类型,用不同的函数进行处理
:param conn_parms:
:return:
"""
if conn_parms['engine'] == 'file_storage':
return file_db_handle(conn_parms)
elif conn_parms['engine'] == 'mysql':
pass
logger.py:
import logging
import os,sys
#想要从父类导入,需要先加入环境变量
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import settings
def logger(log_type):
#create logger
logger = logging.getLogger(log_type)
logger.setLevel(settings.LOG_LEVEL)
#create file handler and set level to warning,这是会输出到屏幕上的
ch = logging.StreamHandler()
ch.setLevel(settings.LOG_LEVEL)
#创建输入到文件夹中的日志
log_file = "%s/log/%s" %(settings.BASE_DIR,settings.LOG_TYPES[log_type])
fh = logging.FileHandler(log_file)
fh.setLevel(settings.LOG_LEVEL)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
#在日志中加入格式
# ch.setFormatter(formatter)
fh.setFormatter(formatter)
#将内容输入到创建的日志文件中
# logger.addHandler(ch)
logger.addHandler(fh)
return logger #这可以后边可以调用,直接写入日志
# #对logger进行测试
# a = logger("access")
# a.warn("test")
main.py:
import os
import sys,time
BARE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BARE_DIR)
from core import auth
from core import logger
from core import transaction
from core import accounts
from conf import settings
#logger()函数执行,并且返回了logger,可以进行调用warn方法进行日志写入
trans_logger = logger.logger('transaction')
access_logger = logger.logger('access')
user_data = {
'account_id':None,
'is_authenticated':False,
'account_data':None
}
def account_info(acc_data):
print(user_data['account_data'])
def repay(acc_data):
account_data = user_data['account_data']
# for k,v in account_data.items():
# print(k,v)
current_balance = """
----------- BALANCE INFO --------
Credit:%s
Balance:%s
""" %(account_data['credit'],account_data['balance'])
print(current_balance)
back_flag = False #设置标志,输入b跳出while循环
while not back_flag:
repay_amount = input("\033[33;1mInput repay amount:\033[0m").strip()
if len(repay_amount) > 0 and repay_amount.isdigit():
# print('ddd 00')
new_balance = transaction.make_transaction(trans_logger,account_data,'repay',repay_amount)
# print(new_balance)
if new_balance:
print("""\033[42;1mNew Balance:%s\033[0m"""%(new_balance['balance']))
else:
print('\033[31;1m[%s] is not a valid amount,only accept integer!\033[0m' % repay_amount)
if repay_amount == 'b':
back_flag = True
def withdraw(acc_data):
"""
取款
:param acc_data: 变成了数据库数据,而且是字典的格式
:return:
"""
accout_data = accounts.load_current_balance(acc_data['account_id']) #再去数据库拿一遍数据,有点多余
current_balance = '''
------- BALANCE INFO -------
Credit: %s
Balance: %s
'''%(accout_data['credit'],accout_data['balance'])
print(current_balance)
back_flag = False
while not back_flag:
withdraw_amount = input("\033[33;1mInput withdraw amount:\033[0m").strip()
if len(withdraw_amount) > 0 and withdraw_amount.isdigit():
new_balance = transaction.make_transaction(trans_logger,accout_data,'withdraw',withdraw_amount)
if new_balance:
print('''\033[42;1mNew Balance:%s\033[0m'''%(new_balance['balance']))
else:
print('\033[31;1m[%s] is not a valid amount,only accept integer!')
if withdraw_amount == 'b':
back_flag = True
def transfer(acc_data):
"""
:param acc_data: 变成了数据库数据,而且是字典的格式
:return:
"""
account_data = accounts.load_current_balance(acc_data['account_id'])
current_balance = '''
------- BALANCE INFO -------
Credit: %s
Balance: %s'''%(account_data['credit'],account_data['balance'])
print(current_balance)
#钱数必须是数字
back_flag = False
while not back_flag:
tranfer_amount = input("\033[33;1mInput tranfer amount:\033[0m").strip()
#只支持整数
if len(tranfer_amount) > 0 and tranfer_amount.isdigit():
#调用transaction,传入账号信息,交易类型,交易金额,交易日志
new_account_data = transaction.make_transaction(trans_logger,account_data,'transfer',tranfer_amount)
if new_account_data:
print("\033[42;1mNew Balance:%s\033[0m"%new_account_data['balance'])
else:
print('\033[31;1m[%s] is not a valid amount,only accept integer!')
if tranfer_amount == 'b':
back_flag = True
def pay_check(acc_data): #账单功能
back_flag = False
while not back_flag:
Time_input = input("\033[33;1mplease input time(Y-M-D),输入b退出:\033[0m").strip()
if Time_input == 'b': back_flag = True continue else: try: select_time = time.mktime(time.strptime(Time_input,'%Y-%m-%d')) # print(select_time) except Exception as ValueError: print("清输入正确的日期格式") continue # log_file = "atm_test/log/transactions.log" log_file = "%s/log/%s" % (settings.BASE_DIR, settings.LOG_TYPES["transaction"]) # print(log_file) with open(log_file,"r",encoding="utf-8") as f: for i in f: # for i in f : 是和大文件读写,不用写f,readlines() i_times = i[0:10] account_id = i[55:59] #字符串相同还判断不出来,必须转换成数字才行,估计数字类型的得这样做 account_id = int(account_id) user_data['account_data']['id'] = int(user_data['account_data']['id']) # print("account_id:%s" %account_id) i_time = time.mktime(time.strptime(i_times,'%Y-%m-%d')) # print(i_time) if i_time >= select_time and account_id == user_data['account_data']['id']: print(i)def logout(acc_data): """ 退出登录 :param acc_data: :return: """ q = input("\033[33;1mIf you want to quit,please input q:\033[0m").strip() if q == 'q': exit() else: print("请输入q")def interactive(acc_data): """ :param acc_data: :return: """ # 加u是为了防止中文乱码 menu = u""" --------- Oldboy Bank --------- \033[32;1m1.账号信息 2.还款 3.取款 4.转账 5.账单 6.退出 \033[0m"""#这里比较牛的是写个字典,所以数字对应的是函数的内存地址 menu_dic = { '1':account_info, '2':repay, '3':withdraw, '4':transfer, '5':pay_check, '6':logout } exit_flag = False #这个标记为有很重要,是循环的前提 while not exit_flag: print(menu) user_option = input(">>:").strip() if user_option in menu_dic: # for i in menu_dic 显示出的是字典的key menu_dic[user_option](acc_data)def run(): """ 认证字段为真,用户数据为数据库的数据,信息补全了 :return: """ #经过执行acc_login之后,如果认证成功会改变user_data中的数据,认证成功字段置为真,赋予相应账号, acc_data = auth.acc_login(user_data,access_logger) #acc_data变成了数据库数据,而且是字典的格式 if user_data['is_authenticated']: user_data['account_data'] = acc_data interactive(user_data) else: print("\033[31;1mOption does not exist!\033[0m")# run()
settlement.py:
import os
import sys,time
BARE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BARE_DIR)
from core import auth
from core import logger
from core import transaction
from core import accounts
from conf import settings
#logger()函数执行,并且返回了logger,可以进行调用warn方法进行日志写入
trans_logger = logger.logger('transaction')
access_logger = logger.logger('access')
user_data = {
'account_id':None,
'is_authenticated':False,
'account_data':None
}
def account_info(acc_data):
print(user_data['account_data'])
def repay(acc_data):
account_data = user_data['account_data']
# for k,v in account_data.items():
# print(k,v)
current_balance = """
----------- BALANCE INFO --------
Credit:%s
Balance:%s
""" %(account_data['credit'],account_data['balance'])
print(current_balance)
back_flag = False #设置标志,输入b跳出while循环
while not back_flag:
repay_amount = input("\033[33;1mInput repay amount:\033[0m").strip()
if len(repay_amount) > 0 and repay_amount.isdigit():
# print('ddd 00')
new_balance = transaction.make_transaction(trans_logger,account_data,'repay',repay_amount)
# print(new_balance)
if new_balance:
print("""\033[42;1mNew Balance:%s\033[0m"""%(new_balance['balance']))
else:
print('\033[31;1m[%s] is not a valid amount,only accept integer!\033[0m' % repay_amount)
if repay_amount == 'b':
back_flag = True
def withdraw(acc_data):
"""
取款
:param acc_data: 变成了数据库数据,而且是字典的格式
:return:
"""
accout_data = accounts.load_current_balance(acc_data['account_id']) #再去数据库拿一遍数据,有点多余
current_balance = '''
------- BALANCE INFO -------
Credit: %s
Balance: %s
'''%(accout_data['credit'],accout_data['balance'])
print(current_balance)
back_flag = False
while not back_flag:
withdraw_amount = input("\033[33;1mInput withdraw amount:\033[0m").strip()
if len(withdraw_amount) > 0 and withdraw_amount.isdigit():
new_balance = transaction.make_transaction(trans_logger,accout_data,'withdraw',withdraw_amount)
if new_balance:
print('''\033[42;1mNew Balance:%s\033[0m'''%(new_balance['balance']))
else:
print('\033[31;1m[%s] is not a valid amount,only accept integer!')
if withdraw_amount == 'b':
back_flag = True
def transfer(acc_data):
"""
:param acc_data: 变成了数据库数据,而且是字典的格式
:return:
"""
account_data = accounts.load_current_balance(acc_data['account_id'])
current_balance = '''
------- BALANCE INFO -------
Credit: %s
Balance: %s'''%(account_data['credit'],account_data['balance'])
print(current_balance)
#钱数必须是数字
back_flag = False
while not back_flag:
tranfer_amount = input("\033[33;1mInput tranfer amount:\033[0m").strip()
#只支持整数
if len(tranfer_amount) > 0 and tranfer_amount.isdigit():
#调用transaction,传入账号信息,交易类型,交易金额,交易日志
new_account_data = transaction.make_transaction(trans_logger,account_data,'transfer',tranfer_amount)
if new_account_data:
print("\033[42;1mNew Balance:%s\033[0m"%new_account_data['balance'])
else:
print('\033[31;1m[%s] is not a valid amount,only accept integer!')
if tranfer_amount == 'b':
back_flag = True
def pay_check(acc_data): #账单功能
back_flag = False
while not back_flag:
Time_input = input("\033[33;1mplease input time(Y-M-D),输入b退出:\033[0m").strip()
if Time_input == 'b': back_flag = True continue else: try: select_time = time.mktime(time.strptime(Time_input,'%Y-%m-%d')) # print(select_time) except Exception as ValueError: print("清输入正确的日期格式") continue # log_file = "atm_test/log/transactions.log" log_file = "%s/log/%s" % (settings.BASE_DIR, settings.LOG_TYPES["transaction"]) # print(log_file) with open(log_file,"r",encoding="utf-8") as f: for i in f: # for i in f : 是和大文件读写,不用写f,readlines() i_times = i[0:10] account_id = i[55:59] #字符串相同还判断不出来,必须转换成数字才行,估计数字类型的得这样做 account_id = int(account_id) user_data['account_data']['id'] = int(user_data['account_data']['id']) # print("account_id:%s" %account_id) i_time = time.mktime(time.strptime(i_times,'%Y-%m-%d')) # print(i_time) if i_time >= select_time and account_id == user_data['account_data']['id']: print(i)def logout(acc_data): """ 退出登录 :param acc_data: :return: """ q = input("\033[33;1mIf you want to quit,please input q:\033[0m").strip() if q == 'q': exit() else: print("请输入q")def interactive(acc_data): """ :param acc_data: :return: """ # 加u是为了防止中文乱码 menu = u""" --------- Oldboy Bank --------- \033[32;1m1.账号信息 2.还款 3.取款 4.转账 5.账单 6.退出 \033[0m"""#这里比较牛的是写个字典,所以数字对应的是函数的内存地址 menu_dic = { '1':account_info, '2':repay, '3':withdraw, '4':transfer, '5':pay_check, '6':logout } exit_flag = False #这个标记为有很重要,是循环的前提 while not exit_flag: print(menu) user_option = input(">>:").strip() if user_option in menu_dic: # for i in menu_dic 显示出的是字典的key menu_dic[user_option](acc_data)def run(): """ 认证字段为真,用户数据为数据库的数据,信息补全了 :return: """ #经过执行acc_login之后,如果认证成功会改变user_data中的数据,认证成功字段置为真,赋予相应账号, acc_data = auth.acc_login(user_data,access_logger) #acc_data变成了数据库数据,而且是字典的格式 if user_data['is_authenticated']: user_data['account_data'] = acc_data interactive(user_data) else: print("\033[31;1mOption does not exist!\033[0m")# run()
transaction.py:
import os,sys,json
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import settings
from core import logger
from core import accounts
def make_transaction(log_obj,account_data,tran_type,amount,**others):
"""
处理所有的交易信息
:param log_obj:
:param account_data: 用户账号
:param tran_type: 交易类型
:param amount: 交易额度
:param others:日志等
:return:
"""
amount = float(amount)
if tran_type in settings.TRANSACTION_TYPE:
interest = amount * settings.TRANSACTION_TYPE[tran_type]['interest']
old_balance = account_data['balance']
if settings.TRANSACTION_TYPE[tran_type]['action'] == 'plus':
new_balance = old_balance + amount + interest
elif settings.TRANSACTION_TYPE[tran_type]['action'] == 'minus':
new_balance = old_balance - amount - interest
#check credit
if new_balance < 0:
print("""\033[31;1mYour credit [%s] is not enough for this transaction
\[-%s],your current balance is [%s]""" %(account_data['credit'],(amount + interest),old_balance))
return
account_data['balance'] = new_balance
#将更改完的数据写入到json文件中
account_data['balance'] = new_balance
accounts.dump_account(account_data) #保存新的账目到文件中
log_obj.info("account:%s action:%s amount:%s interest:%s" %
(account_data['id'], tran_type, amount, interest))
return account_data
else:
print("\033[31;1mTransaction type [%s] is not exist!\033[0m" % tran_type)
shopping.py:
import os,sys,json,time
BARE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BARE_DIR)
from core import accounts
from conf import settings
from core import db_handler
from core import logger
from core import settlement
"""
购物车功能,只能用来进行购物
"""
product_list = []
#此处是对txt文档进行处理,只能处理成列表格式,如果是对JSON格式,就是load成字典格式
product = "%s/shopping/product.txt" % (settings.BASE_DIR)
with open (product,'r',encoding='utf-8') as f:
for line in f:
line = line.strip() #去掉最后一个换行符
index,item = line.split(":")
product_list.append((index,item)) #以括号的格式变成一个元素,其实里边是一个个的元组
# print(product_list)
# print(len(product_list)) #元素的个数,以 1 开头
#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
#现在需要在ATM上写一个接口,提供用户输入用户名和账号,然后返回 用户的账号资金
def acc_auth(func): #函数里套函数就是高阶函数
def deco(*args,**kwargs):
log_obj = logger.logger('access')
retry_count = 0
while retry_count < 3:
account = input("\033[32;1m登录账号:\033[0m").strip()
password = input("\033[32;1m密码:\033[0m").strip()
db_path = db_handler.db_handler(settings.DATABASE)
account_file = "%s/%s.json" %(db_path,account)
if os.path.isfile(account_file):
with open(account_file,'r') as f:
account_data = json.load(f)
if account_data['password'] == password:
func(*args,**kwargs) #需要将认证完之后的数据传入到shopping中
else:
print("\033[31;1mAccount ID or password is incorrect!\033[0m")
else:
print("\033[31;1mAccount [%s] does not exist!\033[0m" % account)
retry_count +=1
else:
log_obj.error("shopping account [%s] too many login attempts" % account)
exit()
return deco
def print_product_list(): #可以显示小标和具体的数据
for index,item in enumerate(product_list):
print(index,item)
@acc_auth #进行装饰器的认证
def user_shopping():
print("--------------------------"
"--------------------------"
"\n"
" 欢迎进入购物菜单 "
"\n")
print_product_list() # 打印商品
salary_account =settlement.settlement() #需要调用settlement模块的settlement()方法
salary = salary_account['balance']
if salary > 0:
shopping_list = []
while True:
option = input("请输入你要购买的商品的编号:").strip()
if option.isdigit():
option = int(option)
if option >= 0 and option <= len(product_list):
p_item = product_list[option] #用户选择的商品,对应的是一个元组
c_num = int(p_item[1]) #对应的是商品价格
if salary >= c_num:
shopping_list.append(p_item)
salary -= c_num
print("添加购物车成功,你的余额还有%s" %salary)
else:
print("你的余额不足,只剩%s元" % (salary))
else:
print("输入错误,请重新输入!")
elif option == "q":
print("----------------购物清单---------------")
for s_list in shopping_list:
print(s_list)
print("你的余额为%s" % (salary))
salary_account['balance'] = salary
accounts.dump_account(salary_account) #将修改完的数据写到数据库中
print("..........exit.........")
exit() #退出程序
else:
exit('余额不足!')
user_shopping()