ATM+购物车
ATM+购物车
一,项目开发流程
1.需求分析
产品经理与架构师,根据客户的需求,理出一套比较容易编写的流程
2.架构设计
架构师根据具体的业务需求选择 具体的开发编程语言与项目框架,所需要的数据库(主库,从库)。与开发目录规范,项目功能划分。项目的报价。
3.分组开发
将项目拆分成多个小项目交给不同开发部门下的多个程序员。
4. 项目测试
测试人员对项目进行全方面的测试。
'自己一定要多测试几遍,不要出现语法错误,不然提桶跑路'
5. 交付上线
打包给运维人员进行维护。
二,需求分析
# 项目大致需求
- 额度15000或自定义
- 支持多账户登录
- 可以查看账户余额
- 可以提现(可自定义手续费比例)
- 提供还款接口
- 支持账户间转账
- 记录每月日常消费流水
- 实现购物商城,买东西加入购物车,调用信用卡接口结账
- 提供管理接口,包括添加账户、用户额度,冻结账户等
- ATM记录操作日志
- 用户认证功能
# 提炼项目功能
1.用户注册
2.登录功能
3.查看余额
4.余额提现
5.账户充值
6.金额转账
7.查看流水
8.添加购物车
9.查看购物车
10.结算购物车
11.管理员功能
# 项目大致技术栈
1.python基础核心编程
2.函数(装饰器)
3.常见内置模块(os,sys,json)
三,架构设计
很多程序都是三层架构。
我们将ATM分为三层架构
第一层 - 展示层
只展示功能选项,用户交互
第二层 - 逻辑层
所有的业务逻辑
第三层 - 数据层
做数据的增删改查提供给逻辑层
""" 等我们学了前端之后
展示层cmd操作 —— 可以换成页面
逻辑层普通Python代码 —— 可以换成django框架
数据层json文件 —— 可以换成MySQL数据库"""
四,搭建项目目录
bin
start.py # 存放启动脚本
conf
settings.py # 存放配置文件
lib
common # 存放公共功能
log # 存放项目日志
core
src.py # 展示层
interface # 核心逻辑层
shop_interface # 购物车接口
bank_interface # 银行接口
user_interface # 用户接口
db
db_handlerl.py # 数据处理层
readme # 说明
搭建功能框架(src.py展示层)
is_login= {'username':''} # 全局变量 保存登录信息
# 用户注册
def register():
pass
# 用户登录
def login():
pass
# 查看余额
def check_balance():
pass
# 余额提现
def Withdraw():
pass
# 账户充值
def pay():
pass
# 转账
def tranfer():
pass
# 查看流水
def check_flow():
pass
# 添加购物车
def add_shop_car():
pass
# 查看购物车
def check_shop_car():
pass
# 结算
def pay_shop_car():
pass
# 管理员
def admin():
pass
# 功能字典
func_dict = {
'1': register,
'2': login,
'3': look_balance,
'4': Withdraw,
'5': pay,
'6': tranfer,
'7': cheak_water,
'8': add_shop_car,
'9': view_shop_car,
'10': clear_shop_car,
'11': admin
}
while True:
print("""
1.用户注册
2.登录功能
3.查看余额
4.余额提现
5.账户充值
6.金额转账
7.查看流水
8.添加购物车
9.查看购物车
10.结算购物车
11.管理员功能
""")
choice = input('请输入功能编号>>>:').strip()
if choice in func_dict:
func_name = func_dict.get(choice)
func_name()
else:
print('好好输!')
启动文件(start.py)
import os
import sys
from core import src
# 动态获取根目录 并添加进sys.path 兼容性强,别人拿去也能用
base_dir = os.path.dirname(os.path.dirname(__file__))
sys.path.append(base_dir)
# 简易使用脚本的方式
if __name__ == '__main__':
src.run()
配置文件(settings.py)
import os
import json
# 4. 拼接db文件夹路径 , 先 获取根目录
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DB_DIR = os.path.join(BASE_DIR, 'db')
# 5.判断是否不存在,不存在直接创建文件夹
if not os.path.exists(DB_DIR):
os.mkdir(DB_DIR)
"""方便后期需要db目录使用直接调用名字即可"""
# 定义日志输出格式 开始
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
'[%(levelname)s][%(message)s]' # 其中name为getlogger指定的名字
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
# 自定义文件路径
"""自定义创建日志文件目录"""
LOG_DIR = os.path.join(BASE_DIR, 'log')
if not os.path.isdir(LOG_DIR):
os.mkdir(LOG_DIR)
logfile_path = os.path.join(LOG_DIR, 'log.log')
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
},
'filters': {}, # 过滤日志
'handlers': {
# 打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集info及以上的日志
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': logfile_path, # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
},
},
'loggers': {
# logging.getLogger(__name__)拿到的logger配置
'': {
'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
}, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
# '购物车记录': {
# 'handlers': ['default','console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
# 'level': 'WARNING',
# 'propagate': True, # 向上(更高level的logger)传递
# }, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
},
}
公共功能(common.py)
import hashlib
from core import src
from conf import settings
import logging
import logging.config
# 12. 定义加密函数
def get_hash(msg):
md5 = hashlib.md5()
md5.update(msg.encode('utf8'))
hash_msg = md5.hexdigest()
return hash_msg
"用于用户密码明文转密文"
# 定义验证登录装饰器
def login_auth(func_name):
def inner(*args, **kwargs):
if src.is_login.get('username'):
res = func_name(*args, **kwargs)
return res
else:
print('请先登录')
src.login()
return inner
"用于验证登录信息,登录后不用再次登录可直接访问所有功能"
# 定义转浮点型 函数
def float_num(target_money):
try:
target_money = float(target_money)
except Exception as e:
return False, '必须是数字'
else:
return True, target_money
"用于对用户转账提现充值的金额转换成 浮点型带一个小数点。兼容性更强"
# 定义日志函数
def get_log(msg):
logging.config.dictConfig(settings.LOGGING_DIC) # 加载日志字典
logger1 = logging.getLogger(msg) # 根据自己需求比如说, 登录记录
return logger1
"用于给每个功能增加日志, 快捷方便"
数据处理层(db_hangler.py)
from conf import settings
import os
import json
# 数据层
# 6. 保存文件的操作
def save(user_dict):
# 获取用户名
username = user_dict.get('username')
# 拼接用户文件路径
user_dir = os.path.join(settings.DB_DIR, f'{username}.json')
# 打开文件 w模式不存在则直接新建
with open(user_dir, 'w', encoding='utf8') as f:
json.dump(user_dict, f, ensure_ascii=False)
# 7. 查询文件的操作
def select(username):
# 拼接用户文件
user_dir = os.path.join(settings.DB_DIR, f'{username}.json')
# 判断用户名是否存在
if os.path.exists(user_dir):
# 存在则直接返回用户字典
with open(user_dir, 'r', encoding='utf8') as f:
return json.load(f)
项目功能详解
注册功能详解
展示层:
def register():
# 1.获取用户名与密码
username = input('请输入用户名').strip()
password = input('请输入密码').strip()
# 2.二次确认用户密码
password1 = input('请确认密码').strip()
# 3.判断两次密码是否一致
if not password1 == password:
print('两次密码不一致')
return
# 4.调用接口层-注册接口
flag, msg = user_interface.register_interface(username, password) # 传参给接口层
# 5.获取返回值并打印
print(msg)
注册接口层:
"缺什么模块导入什么模块"
def register_interface(username,password): # 需要接收传参
# 12.调用公共功能 导入加密模块
hash_pwd = common.get_hash(password)
# 6 构造用户字典
user_dict = {
'username': username,
'password': hash_pwd,
'balance': 15000,
'shop_car': {},
'is_lock': False,
'water_flow':[]
}
# 7 调用数据处理层 查询用户
user = db_handler.select(username)
# 8.判断用户是否存在
if user:
# 有值则返回给展示层
return False, f'用户{username}已存在'
# 9.无值则保存用户文件
db_handler.save(user_dict)
# 10.添加日志
logging1.info(f'{username}用户注册了')
# 11返回给展示层
return True, f'{username}注册成功'
数据处理层:
"缺什么模块导入什么模块"
# 保存文件的功能
def save(user_dict):
# 获取用户名
username = user_dict.get('username')
# 拼接用户文件路径
user_dir = os.path.join(settings.DB_DIR, f'{username}.json')
# 打开文件 w模式不存在则直接新建
with open(user_dir, 'w', encoding='utf8') as f:
json.dump(user_dict, f, ensure_ascii=False)
# 查询文件的操作
def select(username):
# 拼接用户文件
user_dir = os.path.join(settings.DB_DIR, f'{username}.json')
# 判断用户名是否存在
if os.path.exists(user_dir):
# 存在则直接返回用户字典
with open(user_dir, 'r', encoding='utf8') as f:
return json.load(f)
"返回字典给用户方便后续各种 获取用户各种资料的操作"
登录功能详解
展示层:
def login():
# 1.获取用户名密码
username = input('请输入用户名').strip()
password = input('请输入密码').strip()
# 2.调用登录接口
flag, msg = user_interface.login_interface(username, password)
# 6 根据逻辑层的返回的结果判断用户是否登录成功
if flag:
# 记录登录状态
is_login['username'] = username
# 打印结果
print(msg)
逻辑层:
def login_interface(username,password):
# 3.调用数据层判断用户是否已存在
user_dict = db_handler.select(username)
# 判断已存在的情况
if not user_dict:
# 返回结果给 展示层
return False, '用户不存在'
# 4.给密码转成密文与 用户数据内的密文密码做比对
hash_pwd = common.get_hash(password)
# 5.判断密码是否一致
if user_dict.get('password') == hash_pwd:
# 一致则 添加日志并返回给展示层
logging1.info(f'{username}用户成功登录')
return True, f'用户{username}登陆成功'
# 不一致返回信息给 展示层
else:
return False, f'密码错误'
数据处理层:
3. 逻辑层调用了数据处理层 以用户名查询了 文件是否存在,存在则返回用户字典给逻辑层
查看余额功能
1.先用验证用户登录信息的装饰器
展示层:
@common.login_auth
def check_balance():
# 2.因已登录,直接调用查询接口 需要传参用户名(全局变量)
flag, msg = bank_interface.check_balance_interface(is_login.get('username'))
# 打印返回数据
print(msg)
逻辑层:
def check_balance_interface(username):
# 3.调用数据处理层 查询功能 获取用户字典
user_dict = db_handler.select(username)
# 4.获取用户余额
user_balance = user_dict.get('balance')
# 6.添加流水功能与添加日志
logging1.debug(f'{username}用户查询了余额')
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(f'{ctime}:{username}查询了余额{user_balance}元')
# 7 调用数据层,保存功能 保存流水
db_handler.save(user_dict)
# 5 返回数据给展示层
return True, f'尊敬的{username}, 您的余额{user_balance}元'
数据层:
3.接收逻辑层的用户名查找并返回用户字典给逻辑层
7.保存了用户 流水记录
提现功能详解
展示层:
@common.login_auth # 验证登录装饰器
def withdraw():
# 1.获取提现金额
target_money = input('请输入需要提现的金额').strip()
# 2.调用 提现接口
flag, msg = bank_interface.withdraw_interface(is_login.get('username'), target_money)
print(msg)
逻辑层:
def withdraw_interface(username, target_money):
# 3.调用数据层,获取用户字典
user_dict = db_handler.select(username)
# 4. 判断输入金额是否是数字,调用了公共功能 转浮点型
flag, msg = common.float_num(target_money)
if not flag:
return False, '必须是数字'
# 5. 获取用户余额
user_blance = user_dict.get('balance')
# 6.定义提现手续费
bank_charge = 0.038
# 7.判断余额是否充足 与 提现金额做比对
if user_blance <= msg * (1 + bank_charge): # (1 + 0.038) 提现金额包含手续费
return False, '用户余额不足'
# 8.扣除用户提现金额与手续费
user_dict['balance'] -= msg * (1 + bank_charge)
# 10.保存用户流水
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(
f'{ctime}:{username}提现了{msg}元,扣除手续费{msg * bank_charge}元,余额{user_dict.get("balance")}元')
# 11.添加日志
logging1.debug(f'{username}用户提现了{msg}元')
# 9.调用数据处理层保存
db_handler.save(user_dict)
return True, f'{username}您好, 你本次提现{msg}元,扣除手续费{msg * bank_charge}元,余额{user_dict.get("balance")}元'
数据层:
就不多说了,详看上述调用数据层代码
充值功能详解
展示层:
@common.login_auth
def pay():
# 1.获取充值金额
target_money = input('请输入您需要充值的金额').strip()
# 2 调用 充值接口
flag, msg = bank_interface.pay_intertace(is_login.get('username'), target_money)
print(msg)
逻辑层:
def pay_intertace(username, target_money):
# 3.获取用户字典
user_dict = db_handler.select(username)
# 4.调用公共功能 转成浮点型,并判断是否是数字
flag, msg = common.float_num(target_money)
if not flag:
return False, '请输入数字'
# 5.给用户充值
user_dict['balance'] += msg
# 7.添加流水记录
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(f'{ctime}:{username}本次充值{msg}元,余额{user_dict.get("balance")}元')
# 8 添加日志
logging1.debug(f'{username}用户充值了{msg}元')
# 6.调用数据层 保存文件
db_handler.save(user_dict)
return True, f'{username}您好,你本次充值{msg}元,充值后余额{user_dict.get("balance")}元'
转账功能详解
展示层:
@common.login_auth
def transfer():
# 1.获取转账目标用户
target_name = input('请输入您想要转账的用户').strip()
# 2. 获取转账金额
target_money = input('请输入你想要转多少金额').strip()
# 3. 调用转账接口
flag, msg = bank_interface.transfer_interface(is_login.get('username'), target_name, target_money)
print(msg)
逻辑层:
def transfer_interface(username, target_name, target_money):
# 4.调用查询接口 获取目标用户字典数据 判断用户是否不存在
target_name_dict = db_handler.select(target_name)
if not target_name_dict:
return False, '用户不存在'
# 5. 判断用户输入转账金额是否为数字
flag, msg = common.float_num(target_money)
if not flag:
return False, '必须输入数字'
# 6. 获取用户字典
user_dict = db_handler.select(username)
# 7 判断用户账号金额是否充足
if not user_dict['balance'] >= msg:
return False, '余额不足'
# 8. 转账者扣除 转账金额
user_dict['balance'] -= msg
# 9 被转账者 增加 转账金额 并添加流水
target_name_dict['balance'] += msg
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(f'{ctime}:{username}本次向{target_name}转账{msg}元,余额{user_dict.get("balance")}元')
target_name_dict['water_flow'].append(f'{ctime}:{username}向您转账了{msg}元')
# 11 添加日志
logging1.debug(f'{username}用户向{target_name}转账了{msg}元')
# 10. 保存双方数据
db_handler.save(user_dict)
db_handler.save(target_name_dict)
return True, f'{username}您本次向{target_name}转账{msg}元,余额{user_dict.get("balance")}元'
查看流水详解
展示层:
@common.login_auth
def check_flow():
# 1. 调用流水接口
flag, msg = bank_interface.check_flow_interface(is_login.get('username'))
if flag:
# 为True 的情况下 循环获取 并打印数据
for i in msg:
print(i)
else:
print(msg)
逻辑层:
def check_flow_interface(username):
# 2 调用数据层 获取用户字典
user_dict = db_handler.select(username)
# 3 判断流水是否没数据
if user_dict.get('water_flow'):
# 4.添加流水
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(f'{ctime}:{username}查看了流水')
# 5 保存流水
db_handler.save(user_dict)
# 6 添加日志
logging1.debug(f'{username}用户查看了流水')
# 7 返回数据给展示层
return True,user_dict.get('water_flow')
else:
return False, '没有流水'
添加购物车详解
展示层:
@common.login_auth
def add_shop_car():
# 调用购物车接口
flag, msg = shop_interface.add_shop_car_interface(is_login.get('username'))
print(msg)
逻辑层:
def add_shop_car_interface(username):
# 1.定义临时购物车
temp_shop_car = {}
while True:
# 2.获取商品信息
good_list = [
['挂壁面', 3],
['印度飞饼', 22],
['极品木瓜', 666],
['土耳其土豆', 999],
['伊拉克拌面', 1000],
['董卓戏张飞公仔', 2000],
['仿真玩偶', 10000]
]
for num, good_date in enumerate(good_list):
print(f'商品编号:{num} | 商品名称:{good_date[0]} | 商品单价:{1}')
# 3.获取用户购买的商品编号
choice_num = input('请输入您想要购买的商品编号(q)').strip()
if choice_num == 'q':
# 12.保存购物车,获取用户购物车
user_dict = db_handler.select(username)
real_shop_car = user_dict.get('shop_car')
# 13. 循环获取临时购物车数据判断是否已存在真实购物车内
for g_name, g_list in temp_shop_car.items():
if g_name in real_shop_car:
# 13.1 存在则 真实购物车商品数量+ 临时购物车商品数量
real_shop_car[g_name][0] += temp_shop_car[g_name][0]
else:
# 13.2 不存在则 真实购物商品名 = 临时购物车列表g_list = [数量,单价]
real_shop_car[g_name] = g_list
# 14 保存购物车
user_dict['shop_car'] = real_shop_car
# 15 打开文件保存数据
db_handler.save(user_dict)
# 16 添加日志
logging.warning(f'{username}添加了购物车')
return True, '购物车添加成功'
# 4. 判断是否是纯数字
if not choice_num.isdigit():
print('商品编号必须是纯数字')
# 5.转换类型
choice_num = int(choice_num)
# 6.判断是否超出范围
if choice_num not in range(len(good_list)):
print('商品编号不存在')
# 7 根据商品编号获取商品名
target_good = good_list[choice_num]
# 8. 获取用户想要购买的商品数量
good_num = input(f'请输入您想要购买{target_good[0]}的数量').strip()
# 9. 判断是否是数字
if not good_num.isdigit():
print('必须是全数字')
# 10 获取商品名
good_num = int(good_num)
good_name = target_good[0]
# 11.判断是否已存在临时购物车内
if good_name in temp_shop_car:
# 11.1 存在则 购物车内商品数量 + 购买商品数量
temp_shop_car[good_name][0] += good_num
else:
# 11.2 不存在则 商品名 = [数量,单价]
temp_shop_car[good_name] = [good_num, target_good[1]]
查看购物车详解
展示层:
@common.login_auth
def check_shop_car():
# 1.调用查看购物车接口
flag, msg = shop_interface.check_shop_car_interface(is_login.get('username'))
# 2.结果为True
if flag:
# 循环获取用户购物车 列表 打印购物车内商品信息
for i, j in msg.items():
print(f'商品名:{i} | 商品数量:{j[0]} | 商品单价:{j[1]}')
else:
print(msg)
逻辑层:
def check_shop_car_interface(username):
# 3 调用数据层,获取用户数据
user_dict = db_handler.select(username)
real_shop_car = user_dict.get('shop_car')
# 4.判断获取的用户字典是否没有数据
if not real_shop_car:
# 5 保存日志
logging.warning(f'{username}查看了购物车')
return False, '购物车是空的'
return True, real_shop_car
结算购物车详解
展示层:
@common.login_auth
def pay_shop_car():
# 1.调用结算购物车接口
flag, msg = shop_interface.pay_shop_car_interface(is_login.get('username'))
print(msg)
接口层:
def pay_shop_car_interface(username):
# 2 调用数据层获取用户数据
user_dict = db_handler.select(username)
# 3. 获取用户购物车
user_shop_car = user_dict.get('shop_car')
# 4. 计算购物总价
money = 0
for g_date in user_shop_car.values():
money += g_date[0] * g_date[1]
# 5.获取用户余额
user_balance = user_dict.get('balance')
if user_balance < money:
return False, '用户余额不足'
# 6 结算金额
user_dict['balance'] -= money
# 7 清空购物车
user_dict['shop_car'] = {}
# 8 添加流水
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(f'{username}结算了购物车商品。总价{money},余额{user_dict.get("balance")}元')
# 9 记录日志,调用接口保存数据
logging.warning(f'{ctime}:{username}结算了购物车商品。总价{money}')
# 10.保存流水与购物车
db_handler.save(user_dict)
return True, f'{username}, 您本次消费了{money}元,余额{user_dict.get("balance")}元'