02 ATM+购物车

一. 目录架构


ATM/     

├── start.py (提示: 这里的start.py有两种放置的方法,一种是放到bin目录下。一种放到ATM这个文件夹下面。放到ATM这个文件夹下面。好处是不需要在当前文件中处理添加环境变量的问题, 也就是当我们调用别的目录下的模块功能的时候不会发生找不到到模块路径的问题。)

├── bin                  

│   ├── 

├── conf                  

│   ├── settings.py

├── core                 

│   ├── main.py

    └── admin.py

├── db                 

│   ├── user_data(用户与管理员之间的差距就在于"is_admin"是否为True, 普通用户默认为False)

│   ├── commodity(用于存放商品, 每个不同类别的商品都是一个单独的文件)

    └── db_hanlder.py


├── interface                 

│   ├── admin_interface.py

│   ├── user_interface.py

│   ├── shop_interface.py

    └── bank_interface.py

├── lib                 

│   ├── common.py

├── log               

├── README.md

二. README.md

# 一. 项目需求:模拟实现一个ATM + 购物商城程序
"""
1. 额度 15000或自定义 
2. 实现购物商城,买东西加入 购物车,调用信用卡接口结账
3. 可以提现,手续费5%
4. 支持多账户登录
5. 支持账户间转账	
6. 记录每月日常消费流水
7. 提供还款接口	 
8. ATM记录操作日志 
9. 提供管理接口,包括添加账户、用户额度,冻结账户等。
10. 用户认证用装饰器 	 
"""

# 二. 开发软件的公司, 客户, 用户三者之间的关系
"""
开发软件的公司: 帮甲方开发软件.
客户: 指的是某些服务行业的客户, 需要找人开发某些软件(甲方)
用户: 甲方的软件已经开始上线,提供给我们(用户)使用。
"""

# 三. 一个项目是如何从无到有的?
# 1. 需求分析 
"""
# 1-1. 需求分析流程
1. 拿到项目。会先在客户那里一起讨论需求商量项目的功能是否实现。周期与价格得到一个需求文档。
2. 最后在公司的内部需要开一次会议,最终得到一个开发文档,交给不同岗位的程序员进行开发。
	python: 后端, 爬虫
	不同的岗位:
		UI界面设计: 设计软件的布局会分布软件的外观,切成一张张图片交给前端。
		前端: 拿到ui交给他的图片,然后去搭建网页,设计一些页面中哪些位置需要接收数据进行,需要进行数据交互。
		后端: 直接核心的业务逻辑调度数据库进行数据的增删查改。
		测试: 会给代码进行全面测试,比如压力测试界面测试。
		运维: 部署项目。
		
# 1-2 需求文档分析示例	
1. 额度15000或自定义
	注册功能。(注册完毕以后用户有15000默认的额度):  用户只有注册了以后才有额度,用户注册了以后,我们还可以给用户设定默认的额度15000.
2. 实现购物商城,买东西加入 购物车,调用信用卡接口结账
	购物功能: 用户进入购物功能, 首先要打印购物的列表,清单,有什么东西可以购物,还有物品的价格, 以及物品的个数。用户需要买东西。用户选择完东西。对比用户的余额,用户可以可不可以买? 余额够不够?用户购买成功以后放入购物车并保存到用户的个人信息中。
	支付功能: 用户购完物需要付钱,付款的钱要从用户的余额里面去扣。
3. 可以提现,手续费5%
	提现功能: 用户可以提现,但是会收取手续费这里涉及到了计算用户提现手续费.
4. 支持多账户登录
	登录功能: 支持多账户,由注册功能体现,因为注册所以会有多个用户的账户信息。当用户登录以后,我们保存用户当前的状态。就是说用户暂时登陆以后不需要再输入验证。
5. 支持账户间转账
	转账功能: 用户提供转账, 首先看用户有没有这个人, 如果没有那不能转,如果有那么得看用户的余额,用户的余额够不够用用户的余额不够。那么他转就不能转。用户转账成功, 接着我们要跑到收钱的那个用户,我们要定位到收钱的用户,然后对收钱的用户进行加钱。对转钱的当前自己进行减钱。
6. 记录每月日常消费流水
	记录消费流水功能(设置金额): 记录用户每个月的消费流水, 以月为单位时间进行划分。统计用户购物支付的钱, 购物的清单, 个数, 统计用户提现, 转账的钱.
7. 提供还款接口
	还款功能: 对当前用户的余额, 进行增加操作。
8. ATM记录操作日志
	日志功能: 对用户登录以后的所有的操作进行记录。
9. 提供管理接口,包括添加账户、用户额度,冻结账户等
	管理员功能: 管理员可以添加账户。管理员可以设置用户的信用额度。管理员可以可以冻结用户账户。
10. 用户认证用装饰器
	登录认证装置器: 控制对于用户没有登录之前,操作功能都不能执行。
	
# 1-3. 提取功能
# 提取出来的功能
1. 注册功能
2. 登录功能
3. 购物功能
4. 支付功能
5. 提现功能
6. 转账功能
7. 还款功能
8. 日志功能
9. 管理员功能 (添加账户、用户额度,冻结账户)
10. 登录认证装饰器
11. 记录消费流水功能


# 提供给用户“选择与操作”的功能
1. 注册
2. 登录
3. 购物
4. 提现
5. 还款
6. 转账
7. 查看金额
8. 查看购物车
9. 查看消费流水
10. 管理员功能

# 提供给"管理员"的功能
1. 修改用户额度
2. 添加账户
3. 冻结账户(以时间作为冻结的依据)
"""

# 2. 软件的架构设计
"""
# 2-1. 程序设计的好处。
1. 思路清晰
2. 不会出现写一半代码是推翻重写。
3. 方便自己或以后的同事更好维护。

# 2-2. 三层架构设计的好处。
1. 把每个功能都分成三部分,逻辑清晰。
2. 如果用户更换不同的用户界面或不同的数据存储机制都不会影响接口层的核心逻辑代码。扩展性强。
3. 可以在接口层,准确的记录日志与流水。

# 2-3. 三层架构
# 注意: 如果没有软件的架构时期到了后期你想进行维护或者扩展功能可能会很难扩展和维护
三层架构:
	1. 用户视图层(view): 提供给用户选择的功能界面。登录, 注册等等见界面(前端)
	2. 逻辑接口层(interface): 所有核心逻辑都放在接口中。提供给用户视图层来使用的。接收视图层传过来的用户名拿到第三层去做校验。
	3. 数据处理层: 接收接口层。传过来的参数。返回相应的数据给接口层或者保存数据。主要做一些数据的增删查改。
"""

# 3. 分任务开发
"""
分任务开发: 多个同步协同去开发项目, 实现高效开发项目。
"""

# 4. 测试
"""
手动测试. 
自动化测试。(使用自动化脚本测试)
"""

# 5. 上线运行

三. bin

1. stat.py

# -*- coding: utf-8 -*-

from core.main import run

# 执行脚本放到根目录ATM文件夹下。当启动脚本的时候。我们需要导入core.main, 这个时候我们就能找到。不需要处理环境变量的问题。
"""
import sys
import os
BASE_DIR = os.path.normpath(os.path.join(__file__, '..', '..'))
sys.path.append(BASE_DIR)
"""

# usjon的dump和load操作效率比json的dump和load执行效率高,我们使用猴子补丁。使用usjon中的dump和load将json中的dump和load替换.
"""
import json
import usjon
def monkey_patch_json():
    json.__name__ = usjon.__name__
    json.dump = usjon.dump
    json.load = usjon.load
    pass
"""

if __name__ == '__main__':
    run()

四. conf

1. settings.py

import os


def base_dir(*paths):
    # return os.path.normpath(os.path.join(__file__, '..', '..', *paths))
    return os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), *paths))


ACCOUNTS_PATH = base_dir('db', 'user_data')
COMMODITY_DIR = base_dir('db', 'commodity')
ADMIN_DIR = base_dir('db', 'admin_data')

# %(name)s             Logger的名字
# %(levelno)s          数字形式的日志级别(10, 20, 30, 40, 50)
# %(levelname)s        文本形式的日志级别(debug调试, info信息, warning警告, error错误, critical危险)
# %(pathname)s         调用日志输出函数的模块的完整路径名,可能没有
# %(filename)s         调用日志输出函数的模块的文件名
# %(module)s           调用日志输出函数的模块名
# %(funcName)s         调用日志输出函数的函数名
# %(lineno)d           调用日志输出函数的语句所在的代码行
# %(created)f          当前时间,用UNIX标准的表示时间的浮 点数表示
# %(relativeCreated)d  输出日志信息时的,自Logger创建以 来的毫秒数
# %(asctime)s          字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
# %(thread)d           线程ID。可能没有
# %(threadName)s       线程名。可能没有
# %(process)d          进程ID。可能没有
# %(message)s          用户输出的消息
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]'

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d][%(message)s]'

test_format = '%(asctime)s][%(message)s]'

# 3、日志配置字典
LOGGING_DIC = {
    # version 指定的是你的版本信息。
    'version': 1,

    # disable_existing_loggers 关闭已存在日志。默认False
    'disable_existing_loggers': False,

    # formatters 加s代表可以设置很多个不同的日志格式。(注意: 这个不能改,这个是固定的。)
    'formatters': {

        # standard, simple, test 通过自定义的这些名字拿到下面自定义的日志格式的表现形式(自定义的日志格式可以修改)。
        'standard': {
            # format (注意: 这个不能改,这是固定的格式。)  # standard_format 这里可以指定你自定义的日志格式表现的形式。
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
        'test': {
            'format': test_format
        },
    },
    'filters': {},

    # handlers 它是日志的接收者, 它用于控制日志的输出位置。
    'handlers': {
        # console, default, other 这是你自定义的handler

        # 输出位置: 打印到终端的日志
        'console': {
            'level': 10,
            'class': 'logging.StreamHandler',  # class 指定日志输出的形式。(注意不能改。)打印到屏幕
            'formatter': 'simple'  # formatter 指定日志格式输出的形式, 会跑到上面formatters找到你之前所定义的格式simple。
        },
        # 输出位置: 打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # RotatingFileHandler 指定日志轮转。
            'formatter': 'standard',
            # 可以定制日志文件路径
            # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
            # LOG_PATH = os.path.join(BASE_DIR,'a1.log')
            'filename': base_dir('log', 'access.log'),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 默认单位是字节Bytes。日志大小 5M
            'backupCount': 5,  # backupCount 指的是最多给你保存几份。
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
    },

    # loggers 它是日志的产生者, 负责产生不同级别的日志, 产生的日志会传递给handlers, 让handlers控制输出的位置。
    'loggers': {

        # logger
        # '' 执行logging.getLogger(key)时会判断,如果指定的key在下面的这些"logger"中都没找到, 就会把自定义key

        # logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console'],
            # handlers 这里指定你要交给的Handler路径, 交给"default"和"console"之前定义的日志handler.  这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕

            # 这里又设置了日志级别,而handler中又设置了日志级别,这里有两层关卡。当使用log_obj.info()等输入内容的时候. 会进行判断。如果日志级别满足,那么就会被收取到。满足以后会交给handlers中你自定义的"handler"来进行第二次筛选, 如果又满足,那么就会被你相应的"handler"功能, 进行处理。
            'level': 'DEBUG',  # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
            'propagate': False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
        },
    },
}

五. core

1. main.py

from interface import admin_interface
from interface import user_interface
from interface import bank_interface
from interface import shop_interface
from lib import common

func_dic = {}

login_user = None


@common.function_dic(func_dic, description='注册')
def register():
    print("注册功能".center(100, '='))
    username = input('请输入用户名>>:').strip()
    password = input('请输入密码>>:').strip()
    confirm_password = input('请再次确认密码>>:').strip()
    if '' in [username, password, confirm_password]:
        print('对不起, 输入不能有空')
        return
    if password != confirm_password:
        print('对不起, 2次密码输入不一致')
        return

    msg = user_interface.register_interface(username, password)
    print(msg)


@common.function_dic(func_dic, description='登录')
def login(login_account='user'):
    print("登录功能".center(100, '='))
    username = input('请输入用户名>>:').strip()
    password = input('请输入密码>>:').strip()
    res = user_interface.login_interface(username, password)
    if not isinstance(res, tuple):
        print(res)
        return

    if login_account == 'user':
        global login_user
        login_user = username
    else:
        from .admin import change_login
        change_login(username)
    print(res[1])


@common.function_dic(func_dic, description='购物')
@common.auth
def shopping():
    print("购物功能".center(100, '='))
    commodity_list = shop_interface.get_commodities_type()  # 返回商品类别信息列表。
    shop_cart = shop_interface.check_shop_cart_interface(login_user)
    while True:
        # enumerate(可迭代对象, 默认为0索引): 枚举函数。第一个参数传入一个可迭代对象,第二个参数。可以指定一个int类型的数字, 不指定默认为零。
        # 返回值是一个可迭代对象。可迭代对象取值如右所示 --> (0, ['上海灌汤包', 30])
        for index, item in enumerate(commodity_list):
            print(f"({index}):{item.split('.')[0]}  ", end=' ')
        print()
        choice = input('[返回上一层: B/b]请根据编号选择相应的商品类别>>:').strip()
        if choice.lower() == 'b':
            break
        """
        if not choice.isdigit():
            print("对不起, 请输入整数")
            continue
        choice = int(choice)
        if choice > len(commodity_list):
            print("对不起, 输入范围有误!")
            continue
        """
        # 这里使用身份运算符+map隐射。减少了上面isdigit()的判断步骤。不过不容易理解变得比较复杂。
        if choice not in map(lambda i: str(i), range(len(commodity_list))):
            print("对不起, 输入范围有误!")
            continue
        choice = int(choice)

        # commodities = [{'name': '小南瓜干', 'price': 200, 'count': 54}, {'name': '中南瓜干', 'price': 300, 'count': 70}]
        commodities = shop_interface.get_commodities(
            commodity_list[choice])  # get_commodities: 更具商品类别的文件名, 拿到对应类别的所有商品的接口
        for index, commodities_dic in enumerate(commodities):
            name, price, count = commodities_dic.values()
            print(f'编号:{index} 商品:{name} 价格:{price}元 个数:{count}个')

        choice1 = input('[返回上一层: B/b]请根据编号选择相应的商品>>:').strip()
        if choice1.lower() == 'b':
            break
        if choice1 not in map(lambda i: str(i), range(len(commodities))):
            print('对不起, 输入范围超出!')
            continue
        choice1 = int(choice1)

        add_number = input("[返回上一层: B/b]请添加商品数量>>:").strip()
        if not add_number.isdigit():
            print("对不起, 请输入整数")
            continue

        add_number = int(add_number)
        name, price, count = commodities[choice1].values()
        if add_number > count:
            print("对不起, 商品数量不足!")
            continue

        # 加入购物车
        while True:
            is_add_cart = input("是否加入购物车Y/N:").strip()
            # 只有在用户加入购物车以后,才把商品被加入购物车的记录保存. 且只有用户购买以后,商品的数量才真实的被减除.
            if is_add_cart.lower() == 'y':
                # 加入购物车的数据结构: {商品名: {价格:值, 数量:值, 总价:值}}
                shop_cart_value = shop_cart.setdefault(name, {'price': price, 'count': add_number,
                                                              'total_price': price * add_number})
                if shop_cart_value:
                    shop_cart[name]['count'] += add_number
                    shop_cart[name]['total_price'] += add_number * price
                shop_interface.add_shop_cart_interface(login_user, shop_cart)
                break
            elif is_add_cart.lower() == 'n':
                break

        # 结算购物车商品
        while True:
            print("您当前购物车目前含有的商品".center(100, '='))
            name_list = list(shop_cart.keys())
            for index, key in enumerate(name_list):
                price, count, total_price = shop_cart[key].values()
                print(f'编号:{index} 商品名称:{key} 价格:{price}元 数量:{count}个 总价:{total_price}元')

            is_settlement = input("是否结算购物车中的商品Y/N:").strip()  # 结算: settlement
            if is_settlement.lower() == 'n':
                break
            elif is_settlement.lower() == 'y':
                choice = input("请根据编号选择结算的商品>>:").strip()
                if choice not in map(lambda i: str(i), range(len(name_list))):
                    print("对不起, 输入范围超出")
                    continue

                choice = int(choice)
                pay_number = input("请输入需要结算的商品个数>>:").strip()
                if not pay_number.isdigit():
                    print("对不起, 请输入数字")
                    continue

                pay_number = int(pay_number)
                name = name_list[choice]
                count = shop_cart[name]['count']
                if pay_number > count:
                    print('对不起, 结算个数, 超出你的购物车中的商品数量')
                    continue

                flag, msg = shop_interface.settlement_shop_interface(login_user, name, pay_number,
                                                                     commodity_list[choice])
                print(msg)
                if flag:
                    break
            else:
                continue


@common.function_dic(func_dic, description='查看购物车')
@common.auth
def check_shop_cart():
    print("查看购物车".center(100, '='))
    shop_cart = shop_interface.check_shop_cart_interface(login_user)  # res = user_dic['shopping_cart']
    if shop_cart:
        # {商品名: {价格:值, 数量:值, 总价:值}}
        name_list = list(shop_cart.keys())
        for index, key in enumerate(name_list):
            price, count, total_price = shop_cart[key].values()
            print(f'编号:{index} 商品名称:{key} 价格:{price}元 数量:{count}个 总价:{total_price}元')
    else:
        print("对不起, 你的购物车是空的, 快去购物吧")


@common.function_dic(func_dic, description='提现')
@common.auth
def withdraw():
    print("提现功能".center(100, '='))
    get_money = input('请输入提现金额>>:').strip()
    if not get_money.isdigit():
        print("对不起, 请输入整数")
        return

    msg = bank_interface.withdraw_interface(login_user, get_money)
    print(msg)


@common.function_dic(func_dic, description='还款')
@common.auth
def refund():
    print("还款功能".center(100, '='))
    add_money = input("请输入还款金额>>:").strip()
    if not add_money.isdigit():
        print("对不起, 请输入整数")
        return

    msg = bank_interface.refund_interface(login_user, add_money)
    print(msg)


@common.function_dic(func_dic, description='转账')
@common.auth
def transfer():
    print("转账功能".center(100, '='))
    give_people = input("请输入您要转账的用户>>:").strip()
    give_money = input("请输入与您要转账的额度>>:").strip()
    if not give_money.isdigit():
        print('对不起, 请输入数字')
        return

    msg = bank_interface.transfer_interface(login_user, give_people, give_money)
    print(msg)


@common.function_dic(func_dic, description='查看金额')
@common.auth
def check_balance():
    print("查看余额".center(100, '='))
    msg = bank_interface.check_balance_interface(login_user)
    print(msg)


@common.function_dic(func_dic, description='查看消费流水')
@common.auth
def check_consumption_water():
    print("查看消费流水".center(100, '='))
    res = bank_interface.check_flow_interface(login_user)  # res = user_dic['flow']
    if res:
        for item in res:
            print(item)
    else:
        print("你当前没有流水!")


@common.function_dic(func_dic, description='管理员')
@common.auth
def admin():  # (添加账户、用户额度,冻结账户)
    print("管理员功能".center(100, '='))
    from .admin import admin_run
    admin_run()


def run():
    common.func_view(func_dic)

六. db

1. user_data

2. commodity

3. db_hanlder.py

import json
import os
from conf import settings


def select(username):
    path = os.path.join(settings.ACCOUNTS_PATH, f'{username}.json')
    if not os.path.isfile(path):
        return
    with open(path, 'rt', encoding='utf-8') as f:
        return json.load(f)


def save(user_dic):
    path = os.path.join(settings.ACCOUNTS_PATH, f'{user_dic["username"]}.json')
    with open(path, 'wt', encoding='utf-8') as f:
        # ensure_ascii: 这里指定了以后在json保存文件的时候,查看文件可以直观的看到文件中的中文字符。默认为True, json默认保存中文是以一种unicode编码, 方便我们查看
        json.dump(user_dic, f, ensure_ascii=False)


def select_shopping(commodity_name):
    path = os.path.join(settings.COMMODITY_DIR, commodity_name)
    with open(path, 'rt', encoding='utf-8') as f:
        return json.load(f)


def save_shopping(commodity_name, commodities):
    path = os.path.join(settings.COMMODITY_DIR, commodity_name)
    with open(path, 'wt', encoding='utf-8') as f:
        json.dump(commodities, f, ensure_ascii=False)

七. interface

1. admin_interface.py

import time
from db import db_hanlder
from lib import common

logger = common.logger('admin')


def add_admin_interaface(username):
    """
    添加管理员功能接口:
    1. 判断管理员输入想操作的用户是否存在
        1) 如果不存在返回错误提示信息给管理员视图层打印。
        2) 不存在则拿到该用户的用户配置字典。修改该用户"is_admin"为True
    2. 更新数据, 保存操作数据后的结果。
    """
    user_dic = db_hanlder.select(username)
    if not user_dic:
        return '对不起, 你要添加的用户不存在'
    user_dic['is_admin'] = True
    db_hanlder.save(user_dic)
    info = f'{username}添加为管理员用户'

    logger.info(info)

    return info


# 修改用户额度: 修改用户配置文件"balance"
def modify_amount_interface(username, balance: int):
    """
    修改用户额度功能接口:
    1. 拿到管理员输的用户名是否存在
        1) 如果不存在则返回错误提示信息给管理视图层打印
        2) 如果存在则通过用户名拿到用户的配置字典。
    2. 拿到用户配置字典, 将管理员输入的金额初始化给用户。
    3. 为该用户记录流水。
    3. 更新数据, 保存操作数据后的结果。
    """
    user_dic = db_hanlder.select(username)
    if not user_dic:
        return '对不起, 你要锁定的用户不存在'

    user_dic['balance'] = balance
    info = f'管理员用户修改{username}额度为{balance}元'

    logger.info(info)

    user_dic['flow'].append(f'时间:{time.strftime("%Y-%m-%d %X %p")} {info}')
    db_hanlder.save(user_dic)
    return f'修改额度成功! {info} '


# 锁定账户: 用户配置文件"lock_time", 在当前时间小于这个时间的时候, 表示用户在锁定的时间状态
def blocked_account(username, lock_time):
    """
    锁定账户功能接口:
    1. 拿到管理员输入的用户名, 交给数据处理层, 通过数据处理层返回的结果。判断用户名是否存在
        1) 如果用户不存在返回错误提示信息,给管理视图层打印。
        2) 如果用户存在,则拿到用户的配置字典。
    2. 通过修改用户的配置字典中的"lock_time"来达到动态的给用户设置锁定时间。
    3. 调用数据处理层, 更新数据并保存数据操作后的结果。
    """
    user_dic = db_hanlder.select(username)
    if not user_dic:
        return '对不起, 你要锁定的用户不存在'
    user_dic['lock_time'] = lock_time
    import time
    info = f'锁定用户{username}锁定截止时间:{time.strftime("%Y %m %d %x %p", time.localtime(lock_time))}'
    logger.info(info)

    db_hanlder.save(user_dic)
    return info

2. user_interface.py

import time
from db import db_hanlder
from lib import common

"""
强调!!!: 接口层是服务器这边, 用户那边不需要设计到数据的, 那些小逻辑, 我们就不需要放到interface中, 如果放到我们这里, 数据来回交互, 效率慢. 因此我们不能这样做
"""
logger = common.logger('user')


def register_interface(username, password, balance=15000):
    """
    注册功能接口:
    1. 拿到用户视图层传入的用户,判断用户是否存在
        1) 如果存在则返回用户已经存在的提示信息,给用户视图层打印。
        2) 如果不存在,用户可以继续注册。
    2. 定义用户配置字典。
    3. 调用数据处理层,将用户的配置字典传入。让用户视图层保存用户的数据。
    4. 返回用户注册成功的提示信息给用户视图层打印。
    """
    # 这里的False或者True,是与用户视图层的While循环呼应, 如果没有will循环,那么这里可以不加。只传入一个值,返回值就可以.
    # 如果有个循环我们要返回2个值,只有在判断为True的条件下再让用户视图层退出。不是的话,那么就让用户继续继续。
    res = db_hanlder.select(username)
    if res:
        return '对不起, 用户名已经存在'
    # 对于用户的注册,我们要设置用户的数据结构。要明白一点,存不是目的,取才是是. 要方便我们以后的取用户的信息进行校验及操作。
    user_dic = {'username': username, 'password': common.encryption(password), 'balance': balance, 'lock': 0,
                'flow': [],
                'shop_cart': {}, 'lock_time': 0, 'is_admin': False}

    db_hanlder.save(user_dic)

    info = f'{username}注册成功!'
    logger.info(info)

    return info


def login_interface(username, password, lock_second=20):
    """
    登录功能接口:
    1. 拿到用户视层传入的用户名。再把用户名交给数据处理层。通过数据处理层处理的返回结果判断用户是否存在。
        1) 如果不存在则返回用户不存在的提示信息交给用户视图层打印。
        2) 如果存在,则拿到用户的配置字典。
    2. 通过用户的配置试点拿到用户的锁定时间,判断用户的锁定时间是否大于当前时间。
        1) 如果大于当前时间则代表用户还在锁定中, 此时用户的锁定次数已经达到了3, 那么将用户的锁定次数修改为0, 并调用数据处理层保存用户字典。
    3. 如果小于当前时间,则判断用户当前状态是否被锁定。
        1) 如果用户当前的错误是数等于3,那么然后将当前时间加上锁定时间。存入用户的锁定时间中.要用数据处理层保存用户字典。
    4. 如果用服务不是在锁定状态下拿到用户字读成传入的。用户密码对比用户配置字典中的用户密码。
        1) 如果密码一致,则代表用户登录成功。初始化用户的错误次数为零。并调用数据处理层保存用户的配置字典。
    """
    user_dic = db_hanlder.select(username)
    if not user_dic:
        return '对不起, 该用户不存在'

    # 注意!!!: 每个地方涉及到修改数据的操作,我们立马就要把数据保存写回文件。且不要把用户信息字典保存到内存中。涉及到数据的操作有需要的说我们读出来,不需要的时候我们要立马关闭
    # 第一步: 我们拿到用户的锁定时间,还有当前时间,把用户的当前时间减去用户的锁定时间。我们看看是不是小于是还在锁定时间内。
    lock_time = user_dic['lock_time']
    current_time = time.time()
    if current_time < user_dic['lock_time']:
        # 我们控制这个功能只执行一次。在用户锁定以后,我们直接通过这种方式来动态打印用户的解锁时间。
        if user_dic['lock'] != 0:
            user_dic['lock'] = 0
            db_hanlder.save(user_dic)
        return f'对不起, 您当前还在锁定时间内. 解锁时间截至: {time.strftime("%Y-%m-%d %X %p", time.localtime(lock_time + 10))}'

    # 第二步: 如果用户输入了三次,三次都错误,那么用户就需要锁定。那我们把当前时间存入进去。已锁定这种动态的时间为准。
    if user_dic['lock'] == 3:
        user_dic['lock_time'] = time.time() + lock_second
        db_hanlder.save(user_dic)

        logger.warn(f'{username}被锁定')

        return f'共计输入3次错误, 输入次数过多, {lock_second}秒以后才能进行登录!'

    # 第三步: 校验密码是否一致,如果用户是一致,那么我们就登陆. 然后保存用户登录状态。如果不是,那么我们记录用户的当前错误次数。
    pwd = user_dic['password']
    if common.encryption(password) == pwd:
        user_dic['lock'] = 0
        db_hanlder.save(user_dic)

        info = f'{username}登录成功!'
        logger.info(info)

        return True, info
    else:
        user_dic['lock'] += 1
        db_hanlder.save(user_dic)
        return f'{username}当前登录密码输入错误[{user_dic["lock"]}]次'

3. shop_interface.py

import time
import os
from conf import settings
from lib import common
from db import db_hanlder

"""
强调!!!: 接口层是服务器这边, 用户那边不需要设计到数据的, 那些小逻辑, 我们就不需要放到interface中, 如果放到我们这里, 数据来回交互, 效率慢. 因此我们不能这样做
"""
logger = common.logger('shop')


def get_commodities_type():
    """
    拿到所有商品类别的文件名。
    """
    return os.listdir(settings.COMMODITY_DIR)


def get_commodities(commodity_name):
    """
    拿到商品类别的文件名。交给数据处理层。通过数据处理成返回该商品类别的所有的商品配置信息, 返回给用户视图层。
    """
    return db_hanlder.select_shopping(commodity_name)


def add_shop_cart_interface(login_user, shop_cart):
    """
    添加购物车功能接口:
    1. 通过用户视图层传入的当前用户名, 把该用提交给数据处理层, 通过数据处理层返回结果, 拿到当前用户的配置字典。
    2. 通过用户配置字典拿到用户的购物车, 为用户的购物车。更新商品信息。
    3. 把用户的配置点传入给。数据处理成更新, 并保存操作后的数据。
    """
    user_dic = db_hanlder.select(login_user)
    # {商品名: {价格:值, 数量:值, 总价:值}}
    user_dic['shop_cart'] = shop_cart

    info = f'用户:{login_user} 添加购物车商品:{shop_cart}'
    logger.info(info)

    db_hanlder.save(user_dic)


def check_shop_cart_interface(login_user):
    """
    查看购物车功能接口:
    1. 通过用户视图层传入的当前用户名。把该用户提交给数据处理层,通过数据处理层返回的结果, 拿到当前用户的配置字典。
    2. 通过用户配置字典拿到用户的购物车。并返回给用户视图层打印。
    """
    user_dic = db_hanlder.select(login_user)
    return user_dic['shop_cart']


def settlement_shop_interface(login_user, name, pay_number: int, commodity_name):
    """
    结算购物车功能接口:
    1. 用户视图层传入的当前用户名。再把该用户名交给数据处理成,通过数据处理成。处理完毕以后返回的结果拿到当前用户的配置字典。
    2. 通过用户的配置字典拿到用户的购物车。再通过用户视图层传入的商品名和购买个数。判断购物车中的个数是否满足用户所需的购买个数。
        1) 如果不满足,返回提示信息。给用户视图层打印。
    3. 如果满足,通过用户传入的商品类别名, 并转交给数据处理层, 拿到该商品类别下的所有商品的配置信息。通过用户购买的个数与对应商品库存进行效验
        1) 如果不满足。返回库存不足的提示信息,给用户视图层打印。
    4. 如果满足, 用银行支付接口。把用户所需支付的总额, 提交给银行支付接口。以银行支付接口的返回信息作为购买结果。
        1) 如果不满足,返回提示信息。给用户视图层打印。
    5. 如果满足代表用户支付成功。扣除用户配置字典中购物车中所被买掉的商品的信息。
    6. 调用数据处理层保存用户数据。并返回。购物成功的提示信息给用户视图层打印。
    """
    user_dic = db_hanlder.select(login_user)
    # commodities = [{'name': '小南瓜干', 'price': 200, 'count': 54}, {'name': '中南瓜干', 'price': 300, 'count': 70}]
    commodities = db_hanlder.select_shopping(commodity_name)
    shop_cart = user_dic['shop_cart']

    # {商品名: {价格:值, 数量:值, 总价:值}}
    index = 0
    for commodity_dic in commodities:
        if name not in commodities:
            continue
        commodities_count = commodity_dic['count']
        index = commodities.index(commodity_dic)
        if pay_number > commodities_count:
            info = f'商品不足! 目前库存:{commodities_count}个'
            logger.warn(info)
            return False, info

    shop_cart[name]['count'] -= pay_number

    # 计算
    price = shop_cart[name]['price']
    total_price = pay_number * price
    shop_cart[name]['total_price'] -= total_price

    # 调用银行接口
    from .bank_interface import payment_interface
    flag, balance = payment_interface(login_user, total_price)
    if not flag:
        info = f'{login_user}余额不足无法购买商品! 目前余额:{balance}元 购买总额:{total_price}元'
        logger.warn(info)
        return False, info

    # 扣款成功以后, 商品个数减少
    commodities[index]['count'] -= pay_number
    db_hanlder.save_shopping(commodity_name, commodities)

    # 记录日志
    info = f'购买商品:{name} 商品单价:{price}元 购买数量:{pay_number}个 消费总额:{total_price}元 '
    logger.info(f'用户:{login_user} {info}')

    if shop_cart[name]['count'] == 0:
        del shop_cart[name]
    user_dic['shop_cart'] = shop_cart
    db_hanlder.save(user_dic)
    return True, '购物成功!正在迅速发货中....'




4. bank_interface.py

import time
from db import db_hanlder
from lib import common

"""
强调!!!: 接口层是服务器这边, 用户那边不需要设计到数据的, 那些小逻辑, 我们就不需要放到interface中, 如果放到我们这里, 数据来回交互, 效率慢. 因此我们不能这样做
"""
logger = common.logger('bank')


def withdraw_interface(login_user, get_money, service_charge=0.05):  # 手续费5%
    """
    提现接口:
    1. 拿到自己的金额。判断用户金额是否大与手续费加提现的金额。
        1) 条件不满足。返回提示信息给用户视图层打印
        2) 条件满足。对用户金额进行操作。
    2. 记录流水, 更新用户数据。返回提现成功的信息返回给用户视图层打印。
    """
    # get_money 用户提现金额   # procedure_money用户手续费(minus减)
    user_dic = db_hanlder.select(login_user)
    get_money = int(get_money)
    procedure_money = get_money * service_charge
    balance = user_dic['balance']
    if get_money > balance + procedure_money:
        return '对不起, 提现失败, 你的余额不够!'
    else:
        user_dic['balance'] -= (procedure_money + get_money)
        user_dic['flow'].append(f'时间:{time.strftime("%Y-%m-%d %X %p")} 提现:{get_money}元')  # 记录流水

        info = f'{login_user}提现:{get_money}元 手续费:{procedure_money}元 余额:{user_dic["balance"]}元'
        logger.info(info)

        db_hanlder.save(user_dic)
        return info

def refund_interface(login_user, add_money):
    """
    还款功能接口:
    1. 拿到用户金额, 直接对用户金额进行操作。
    2. 记录流水。更新数据。返回还款成功的提示信息给用户视图层打印。
    """
    user_dic = db_hanlder.select(login_user)
    add_money = int(add_money)
    user_dic['balance'] += add_money
    user_dic['flow'].append(f'时间:{time.strftime("%Y-%m-%d %X %p")} 还款:{add_money}元')  # 记录流水

    info = f'{login_user} 还款:{add_money}元 余额:{user_dic["balance"]}元'
    logger.info(info)

    db_hanlder.save(user_dic)
    return info


def check_balance_interface(login_user):
    """
    查看余额功能接口:
    1. 拿到用户余额, 记录流水。并返回给用户视图层进行打印。
    """
    user_dic = db_hanlder.select(login_user)

    info = f'{login_user}查询余额'
    logger.info(info)

    return f'{login_user}余额:{user_dic["balance"]}元'

def check_flow_interface(login_user):
    """
    查看用户流水接口:
    1. 通过用户视图层传入的当前登录的用户名, 到数据处理层拿到用户配置字典。通过该用户的配置字典。返回该用户的流水账单。
    """
    user_dic = db_hanlder.select(login_user)
    return user_dic['flow']

def transfer_interface(login_user, give_people, give_money):
    """
    转账功能接口:
    1. 拿到收款方用户, 查看收款方用户是否存在。
    2. 转账金额, 自己金额是否满足转账额度
    3. 记住流水。更新自己的金额数据和收款方的金额数据。
    """
    other_user_dic = db_hanlder.select(give_people)
    if not other_user_dic:
        info = f'用户:{login_user} 转账:{give_people}失败 {give_people}用户不存在'
        logger.warn(info)
        return info

    give_money = int(give_money)
    my_user_dic = db_hanlder.select(login_user)
    balance = my_user_dic['balance']
    if give_money > balance:
        return '对不起, 余额不足'

    # 转账成功以后,自己的用户余额减钱, 别人的用户余额加钱。
    my_user_dic['balance'] -= give_money
    other_user_dic['balance'] += give_money

    # 记录一下流水。自己要知道给谁转钱, 转多少钱. 别人要知道是谁给我转了钱, 转了多少钱。
    info = f'{login_user}转账:{give_money}元 收款用户:{give_people}'
    logger.info(info)
    my_user_dic['flow'].append(f'时间:{time.strftime("%Y-%m-%d %X %p")} {info} ')

    info1 = f'{give_people}收款:{give_money}]元 转账用户:{login_user}'
    logger.info(info1)
    other_user_dic['flow'].append(f'时间:{time.strftime("%Y-%m-%d %X %p")} {info1}')

    db_hanlder.save(my_user_dic)
    db_hanlder.save(other_user_dic)
    return f'用户:{login_user} {info}'


def payment_interface(login_user, total_price):
    """
    支付功能:
    1. 拿到用户余额判断用户余额是否满足支付的金额
        1) 如果不满足,返回错误提示信息。给用户视图层打印。
        2) 如果满足,对用户的余额进行操作。
    2. 记录流水保存数据。返回支付成功的提示信息,给用户是涂层打印。
    """
    user_dic = db_hanlder.select(login_user)
    balance = user_dic['balance']
    if total_price > balance:
        return False, balance
    user_dic['balance'] -= total_price

    info = f'{login_user}支付:{total_price}元'
    logger.info(info)

    user_dic['flow'].append(f'时间:{time.strftime("%Y-%m-%d %x %p")} 支付:{total_price}元')

    db_hanlder.save(user_dic)
    return True, balance

八. lib

import hashlib
import logging
from logging import config
from functools import wraps
from conf import settings

config.dictConfig(settings.LOGGING_DIC)


def function_dic(func_dic, description):
    def deco(func):
        if not func_dic:
            func_dic[0] = (description, func)
        else:
            max_num = max(func_dic)
            func_dic[max_num + 1] = (description, func)

        @wraps(func)
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res

        return wrapper

    return deco


def auth(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        from core.main import login_user
        if login_user:
            res = func(*args, **kwargs)
            return res

        print("对不起, 您当前没有登陆, 请先登录! !")

    return wrapper


def logger(log_name):
    logger_obj = logging.getLogger(log_name)
    return logger_obj


def encryption(password):
    m = hashlib.md5()
    m.update("密码".encode('utf-8'))
    m.update(password.encode("utf-8"))
    m.update('加盐'.encode('utf-8'))
    return m.hexdigest()


def admin_auth(func):
    from core.main import login_user
    from db.db_hanlder import select

    def wrapper(*args, **kwargs):
        user_dic = select(login_user)
        if user_dic['is_admin']:
            res = func(*args, **kwargs)
            return res
        print('对不起, 你不是管理员用户, 不能此操作')
    return wrapper


def func_view(func_dic):
    while True:
        for key, value in func_dic.items():
            print(f'({key}):{value[0]}  ', end='')
        print()
        choice = input('[返回上一层: B/b]根据编号选择相应功能>>:').strip()
        if choice.lower() == 'b':
            break
        if not choice.isdigit():
            print("请输入纯数字")
            continue
        choice = int(choice)
        if choice not in func_dic:
            print("对不起, 输入范围不正确")
            continue
        func_dic[choice][1]()

九. log

十. 图示

十一. 用户视图层<--> 逻辑处理层 <--> 数据处理层4步走策略

# 1. 用户处理层提供给用户的交互界面, 用户传入的数据会交给接口层
# 2. 接口层对用户的数据进行判断, 如果用户的判断过程中设计涉及到操作数据,那么我们要从接口层要把这种用户传入的数据传入给数据处理层
# 3. 数据处理层拿到接口层的数据, 在数据处理层中数据处理的方式有增删改查用户的数据等等操作, 且这些操作按接口层传过来要求, 数据层操作数据并反馈不同的结果给接口层(例如: 当需要读出数据时数据处理层会把数据读出, 反交给接口层)
# 4. 接口层拿到这种数据处理层反馈的结果, 并与用户视图层传过来的数据进行核对以后将结果反馈给用户试图层, 用户处理层接收逻辑接口层传递过来的结果, 打印给用户展示, 让用户知道当前输入的状态.(如果错误返回一种提示信息。如果正确则也返回用户可能需要的结果。拿到了结果无非有两种形式,一种是效验成功,一种是没效验成功, 更具所需情况把这两种形式的结果的反馈给用户) (注意: 还有一些小的逻辑的处理也放到用户视图层,比如不涉及到数据处理的操作,我们就放到用户视图层. 如果用户的数据涉及到数据处理涉及到数据,或者与数据的计算操作我们就放在逻辑接口层)。
posted @ 2020-04-02 23:32  给你加马桶唱疏通  阅读(174)  评论(0编辑  收藏  举报