ATM购物车项目总结
项目实现思路
ATM项目
ATM架构设计
三层架构
core目录下的src.py(浏览器) (展示层)
interface目录下的多个py文件(框架) (核心逻辑层)
db目录下db_handler.py(数据库服务) (数据处理层)
优先实现功能
在src.py的展示层写面条版的函数,先实现主题功能,暂时别想着优化
拆分函数
如下图是一个用户注册功能函数:
我们要做的是将其到interface、db构成三层架构
拆分到哪?
核心逻辑层 有user bank shop admin等文件,负责不同的功能, 将跟user相关的代码(登录、注册) 放入相应的user_innerface.py文件
然后如果在innerface层下需要进行数据操作,就调用db层的函数。
请注意:展示层只能访问逻辑层,不能访问数据层。
项目路径展示
项目启动文件 start.py
项目启动文件start.py可以放在项目根路径,也可以放在bin文件夹下。
该文件首先要做的第一件事就是使用os.path.dirname获取将当前项目路径,并将其导入sys.path。
然后就是调用展示层文件src.py。
使用if name == __name__:
标志这个文件是启动文件。
请无视飘红,这是pycharm的小bug,实际程序是可以运行的。
配置文件 setting.py
配置文件应该存放的是一些不会变动的信息,如项目的根路径、数据库的路径、日志的存放位置。
当其他模块要用这些路径的时候的时候,从配置文件导入即可。注意当数据库db不存在时,settings.py会进行创建,所以在第三层一定要导入settings.py。
日志配置字典
可以先别急着实现日志功能,中后期再添加日志。
关于日志模块的配置信息,应该写在settings.py文件内,并且封装成函数方便调用:
日志函数
日志函数属于公共功能,应该放在common.py。
在每个接口文件的开头都放一个logger,方便调用,下面拿bank接口文件举例:
所有这个文件内产生的日志,都是银行日志。
展示层 src.py
用户注册
获取用户输入
获取用户输入包括 获取用户名和获取密码
可自行添加:退出、输入两次密码比对
这两个功能逻辑简单,可以放在展示层
md5加密
使用hashlib对用户输入的密码进行md5加密,将密文保存到数据层,下次用户登录直接比对密文。
展示层只调用加密函数,加密函数应该存放于lib/common.py路径下:
调user功能接口
将用户名、密文输入user接口函数:
接口函数在核心逻辑层,在user_interface.py文件内:
逻辑层调用数据层的select函数,检查用户是否注册。再建立用户信息字典,最后调用数据层的save函数,输入用户信息字典,保存数据。
用户登录
基本流程
1.获取用户输入
2.md5加密
3.调user功能接口
调user功能接口:
接口返回值一致性
这个接口返回两个值:登录状态,信息
请注意之前注册接口的返回值只有一个值,这样我们每次调接口都要查看到底有几个返回值,容易混淆。为了保持接口返回值一致性,应该都改成返回两个值
将用户名和密文传入login_interface函数:
这里需要注意的是,每次传入的参数,都需要进行校验,保证传入干净的数据。
登录成功修改全局字典
当登录成功后,会修改全局字典:
将全局字典'username'键的值从空改成当前用户。
全局字典对应的是cookies。也就是你登录网站之后,网站会给你返回一个通行证,浏览器帮你把这个通行证存在本地。下一次登录就拿出来给网站看,也就不需要重新输入用户名密码了。
还有一个原因就是,使用的用户多了之后,如果放在第二层、第三层则会增大服务器的开销,所以放在第一层让用户承担开销,更为合理。
登录校验装饰器
由于我们希望只有登录的用户,才能使用更多功能,所以要给其他函数添加装饰器。
只有当全局字典是有值的,才会执行被装饰函数。还记得吗,登录成功会将全局字典的值改为当前用户。
循环导入
装饰器理论上应该放入common.py,因为登录检验的逻辑不是我们应该给用户展示的,但是放入common.py容易引起循环导入:
查看余额
从全局字典获取当前用户名
调bank功能接口
这个功能比较简单,先调用bank接口(之前是user接口),再调用select函数得到用户信息字典,返回用户的余额数据即可。
余额提现
从全局字典获取当前用户名
调bank功能接口
传入用户名、提现金额两个参数。
接口函数在逻辑层:
判断用户输入是否是整数或小数
对用户输入的数字,我们要进行校验,可以将校验的代码封装成函数,放在common.py。需要用的时候直接调用。这里建议在第二层做校验用户输入,第一层只能输入数据。
手续费 免费额度
在用户提现时收取一定手续费,手续费的数据应该放在settings.py下,便于管理。
免费额度的意思是:在免费额度内,用户提现不需要手续费,如果超过额度,就需要手续费。
以支付宝为例:每个用户有2万元的免费额度,超出后按照0.1%收取手续费。
实现思路:可以在用户信息字典中添加一个键值对,存放该用户的提现免费额度。
账户充值
与余额提现很相似,故省略。
repay:
repay_interface:
用户转账
转账相对来说,比前面复杂一点点,需要指名给谁转账:
转账逻辑
关于转账需要注意:
- 不能转账给自己
- 确认被转账的人存在
- 确认用户余额足够转账
- 确认转账之后当前用户金额减少
- 确认被转账用户金额增加
查看流水
在发生充值、提现、转账、购物车结算,这种涉及到账户余额变化的事件时,要将金额变动的信息加入用户信息字典。只要有钱的变动,就应该有流水信息。
所以应该在上述函数中,都添加一些代码,将流水信息添加到用户字典。
添加时间信息 转账流水
查看流水,就是将用户信息字典的流水信息拿出来展示,这个功能实现很简单,不再赘述。
也可以给流水添加时间信息:
注意转账功能的流水,既需要给当前用户添加流水,也需要给被转账的用户添加流水。
日志也可以实现这个功能,建议后期再添加日志。
添加购物车
核心逻辑
user_data = {'shop_car': {'印度飞饼': [20, 22]}} # 现在的字典
swap_dict = {'印度飞饼': [100, 22], '仿真玩偶': [1, 10000]} # 临时字典
for good in swap_dict: # 1.键一个一个取出来
if good in user_data.get('shop_car'): # 2.如果商品在user-data中
user_data.get('shop_car').get(good)[0] += swap_dict.get(good)[0] # #3.修改数量的值
else:
user_data.get('shop_car')[good] = [swap_dict.get(good)[0], swap_dict.get(good)[1]] # 如果不存在就新增键值对
print(user_data)
展示商品
用户需要先查看商品,才能添加购物车。可以在逻辑层写一个展示商品的函数。商品数据也可以放在逻辑层。商品数据也可以保存在文件,做成配置项,从settings导入。
使用临时字典
什么叫临时字典?在添加购物车时,我们运行用户循环添加,直到用户输入q则停止添加。如果不使用临时字典,则用户每次添加,都会进行一次数据层的文件写入。使用临时字典,则是将数据保存在字典中,等到用户输入q,再将字典中的数据一次性写入文件。总得来说就是减少了写入的次数。
展示临时字典
因为临时字典是在程序运行中临时存在的,所以无法通过查看文件中的用户信息,来看到临时字典里的东西。
只能及时展示,每次循环临时字典都会变动。
查看购物车
调用shop接口查看用户字典即可,很简单,不再赘述。购物车是空的时候,要进行提示。
清空购物车
主要逻辑
从用户字典读出当前购物车信息,对商品价格进行计算,比对余额是否充足,将余额减去商品价格,最后将购物车清空,记录金额变动的流水,最后写入用户信息字典。
管理员功能
校对用户信息字典
管理员用户也是用户,只不过他有一个键值对比较特殊is_admin:true
。已经登录的用户想使用管理员功能,就要先进行用户字典中数据值的校对,如果is_admin == true
则可以使用管理员功能。说明一点,对于普通的用户注册,它们的信息字典从创建开始默认:is_admin:false
。也可以设置只有管理员用户才有这个键值对。
调用接口函数查看当前用户是不是管理员:
确认is_admin这个键的值:
校对全局变量
给全局变量字典扩充一个键值对:
登录成功后,会将这两个键值对的值都进行修改。如果是管理员登录则会将'is_admin'修改为true。
此时获取当前全局变量字典的信息,就可以进行是否为管理员的校验了。
有参装饰器
装饰器要能区分哪些是普通函数,哪些是管理员函数。所以要外界传一个参数,告诉装饰器现在装饰的是普通函数还是管理员函数。如果是管理员函数就多加一个分支,如果是普通函数就按照原来进行。
先校验用户是否登录,再校验当前功能函数级别。
如果这个函数是普通函数(装饰器传参为normal),则直接执行。
如果这个函数是管理员函数(type=='admin'),则校验全局字典的is_admin键值对。如果是True,则继续执行。
如果is_admin == false
则是普通用户,就走else不然其使用管理员函数。
给函数装上有参装饰器:
这样看起来确实更加清晰推荐使用,但是装饰器获取的还是全局变量字典的值(cookies),全局变量在展示层,理论上用户可以修改,所以我觉得应该还是有安全隐患。但是也不钻牛角尖了,就这样了!!而且使用有参装饰器和全局字典就可以少写一个校验admin的接口函数。
冻结用户
冻结用户之前,管理员先要能查看所有的用户吧,所以要在数据层写一个查看所有用户的函数。
db目录大概是这样:
这个函数返回用户名的列表:
在接口层改用户的is_lock键值对:
在登录函数添加判断是否锁定的逻辑:
查看文件的函数
可以扩展这个查看文件的函数,给他传入参数(一个目录,文件后缀名),他给你返回一个此目录下所有你指定后缀名的文件。比如指定txt,就给你返回目录下所有文本文件。
解冻用户
会冻结难道不会解冻吗?
提示:获取db目录所有被冻结的用户名单
def unlock_user():
cold_user = []
user_list = db_handler.check_file()
# 1.获取所有被冻结的用户
for user in user_list:
user_data_dict = db_handler.select(user)
cold_massage = user_data_dict.get('is_lock')
if cold_massage:
cold_user.append(user)
else:
continue
# 2.展示被冻用户
for num, user in enumerate(cold_user):
print(f'编号{num},用户名:{user}')
while True:
# 3.解冻
admin_choice = input(f'请输入要冻结的用户编号>>>')
if admin_choice.lower() == 'q':
break
if not admin_choice.isdigit():
print('输入错误')
continue
admin_choice = int(admin_choice)
if admin_choice not in list(range(len(cold_user))):
print('超出范围')
continue
user_name = cold_user[admin_choice]
user_data_dict = db_handler.select(user_name)
user_data_dict['is_lock'] = False
print(f'解冻{user_name}成功')
db_handler.save(user_data_dict)
逻辑层 interface
对传入数据进行校验
逻辑层会接受到用户传入的数据,为了安全性,我们必须对传入数据进行校验。
数据层 db_handler.py
数据层的优化
数据层总是需要用到项目路径(base_path)、数据库路径(db_path),这两个路径都是不变的。
在每个函数内都写很麻烦,我们可以将其放入配置文件conf/setting.py
优化前:
放入配置文件:
(当db不存在就创建)
在数据层(db_hander.py)导入即可form conf import setting
数据层的select函数
注意:当用户存在,返回用户信息的字典,当用户不存在,返回None。
数据层的save函数
ensure_ascii参数保证写入的时候不会把中文转成unicode编码,增强可读性。
common文件到底有什么?
- 加密
- 用户登录装饰器
- 用户输入校验
- 日志函数