ATM + 购物车

该项目的核心不仅在于引领初学者快速入门python项目开发,更是站在项目架构的角度教你如何在程序开发之初合理且优雅地设计程序的架构,从而极大地提升程序的开发效率与可扩展性
"""

  • 额度15000或自定义
  • 支持多账户登录
  • 可以查看账户余额
  • 可以提现(可自定义手续费比例)
  • 提供还款接口
  • 支持账户间转账
  • 记录每月日常消费流水
  • 实现购物商城,买东西加入购物车,调用信用卡接口结账
  • 提供管理接口,包括添加账户、用户额度,冻结账户等
  • ATM记录操作日志
  • 用户认证功能
    1.注册功能
    2.登录功能
    3.查看余额
    4.提现功能
    5.充值功能
    6.转账功能
    7.查看流水
    8.添加购物车
    9.查看购物车
    10.结算购物车
    11.管理员功能
    10.1.冻结账户
    10.2.删除账户
    12.打印日志

项目架构

ATM架构设计
	三层架构
		core目录下的src.py(浏览器)
		interface目录下的多个py文件(框架)
		db目录下db_handler.py(数据库服务)
"""
第一层:只做数据展示和简单的数据获取
	cmd终端、浏览器页面、手机app页面
第二层:真正的核心业务逻辑处理(代码)
	编写代码的py文件、目录、框架
第三层:数据操作
	文件读写操作、py文件、数据库软件
"""

以下代码每层内还包括每个层模块的调用 以及公共模块 下面会把公共模块和配置模块单独再写一个以下代码每层内还包括每个层模块的调用 以及公共模块 下面会把公共模块和配置模块单独再写一个

1.注册功能

基于面条版本代码的基础上 把代码按照三层架构给拆分

第一层 用户层代码:
def register():
    # 1.获取用户相关信息
    username = input('请输入您的用户名>>>:').strip()
    password = input('请输入您的密码>>>:').strip()
    confirm_pwd = input('请确认您的密码>>>:').strip()
    # 2.判断两次密码是否一致
    if password == confirm_pwd:
        # 调用加密功能 完成密码加密
        hash_pwd = common.get_hash(password)
        # 3.调用第二层注册接口完成注册
        msg = user_interface.register_interface(username, hash_pwd)
        print(msg)
    else:
        print('两次密码不一致')
第二层  接口层代码:
def register_interface(username, password):
    # 3.校验用户名是否已存在
    user_dict = db_handler.select(username)
    if user_dict:
        return False, f'用户名{username}已存在'
    # 密码加密处理
    hash_pwd = common.get_hash(password)
    # 4.构造用户字典
    user_dict = {
        'username': username,
        'password': hash_pwd,
        'balance': 15000,
        'shop_car': {},
        'is_lock': False,
        'water_flow': []
    }
    db_handler.save(user_dict) # 调用封装好的保存数据的函数
    logger.info(f'用户{username}注册成功')  # 添加的日志功能 info为五种日志文件其中一种代表 日志等级
    return True, f'用户{username}注册成功'

插入一个证明登录的全局变量

# 存储用户是否登录的字典
is_login = {
    'username': '',
    'is_admin': ''
}

2.登录功能

用户层代码:
def login():
    # 1.获取用户数据
    username = input('请输入您的用户名>>>:').strip()
    password = input('请输入您的密码>>>:').strip()
    # 调用加密功能 完成密码加密
    hash_pwd = common.get_hash(password)
    # 2.调用第二层登录接口
    flag, msg = user_interface.login_interface(username, hash_pwd)
    if flag:  # 第二层返回两个参数 是登陆函数时有两种结果,用于判断 其他功能返回两个参数是为了接口规范
        is_login['username'] = username  # 此处是把用户登录用一个空字典保存,防止重复登陆 提高效率
        is_admin['is_admin'] = msg[1]
        print(msg[0])
    else:
        print(msg)
第二层代码:
def login_interface(username, password):
    # 1.先查询是否有当前用户名对应的用户数据
    user_dict = db_handler.select(username)
    if not user_dict:
        return False, f'当前用户{username}不存在 无法完成登录'
    # 2.用户名正确 比对密码是否一致
    hash_pwd = common.get_hash(password)
    if user_dict.get('password') == hash_pwd:
        if user_dict['is_lock']:
            return False, f'用户{name}已被冻结,无法登录!!!'
        else:
            logger.info(f'{username}登录成功')
             # 这里传两个参数为了 记录管理员的登录记录
            return True, ('登录成功',user_dict['is_admin'])
    return False, '密码错误'

以下功能都需要用到用户登陆才能进行 所以把公共模块在此处先添加上

这个为公共模块:
import hashlib
import logging
import logging.config
from ATM.conf import settings


# 加密明文数据
def get_hash(msg):
    md5 = hashlib.md5()
    md5.update(msg.encode('utf8'))
    hash_msg = md5.hexdigest()
    return hash_msg

# 校验用户是否登录装饰器
def login_auth(type):
    def outer(func_name):
        def inner(*args,**kwargs):
            if src.is_login.get('username'):
                if type == 'normal':
                    res = func_name(*args,**kwargs)
                    return res
                elif type == 'admin':
                    if src.is_login.get('is_admin'):
                        res = func_name(*args, **kwargs)
                        return res
                    else:
                        print('你不是管理员 没有执行的权限')
            else:
                print('你还没有登录  请先登录!!!!')
                src.login()
        return inner
    return outer


def get_num(target_money):
    try:
        target_money = float(target_money)
    except ValueError:
        return False, '请输入整数或者小数'
    else:
        return True, target_money

def get_logger(msg):
    logging.config.dictConfig(settings.LOGGING_DIC)  # 自动加载字典中的配置
    logger1 = logging.getLogger(msg)
    return logger1

配置模块

import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DB_DIR = os.path.join(BASE_DIR, 'db')
if not os.path.exists(DB_DIR):
    os.mkdir(DB_DIR)

# 提现手续费
MONEY_RATE = 0.05

配置模块 变量名一般为大写  以下没有大写 
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, 'ATM.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配置
    },
}

3.查看余额

此功能比较简单 直接读取用户数据进行返回即可 用户层用于接收数据结果

用户层:
@common.login_auth('normal')
def check_balance():
    # 调用接口程序
    Flag,msg = bank_interface.check_balance_interface(is_login.get('username'))
    if Flag:
        print(msg)
核心逻辑层:
def check_balance_interface(username):
    # 1.查询当前登录用户的字典数据
    user_dict = db_handler.select(username)
    # 2.从字典中获取余额信息并返回给第一层
    user_balance = user_dict.get('balance')
    logger.debug(f'用户{username}查看了自己的账户余额')
    return True, f'尊敬的{username},您目前的账户余额为{user_balance}'

4.提现功能

用户层:
@common.login_auth('normal')
def withdraw():
    # 1.获取用户想要提现的具体金额即可
    target_money = input('请输入您想要提现的金额>>>:').strip()
    # 2.判断用户输入的是否是整数或者小数 100 123.23  正则表达式、代码处理
    '''转数字的操作 也可以封装成函数放到common.py中'''
    # 3.调用银行接口完成提现操作
    flag, msg = bank_interface.withdraw_interface(is_login.get('username'), target_money)
    print(msg)
核心逻辑层:
def withdraw_interface(username, target_money):
    flag, value = common.get_num(target_money)
    if not flag:
        return False, '请输入符合要求的金额数字'
    # 1.获取当前登录用户的字典数据
    user_dict = db_handler.select(username)
    # 2.获取用户余额并判断是否满足提现要求  还应该收取手续费
    user_balance = user_dict.get('balance')
    if user_balance >= value * (1 + settings.MONEY_RATE):
        user_dict['balance'] -= value * (1 + settings.MONEY_RATE)
        # 添加账户流水信息
        ctime = time.strftime('%Y-%m-%d %H:%M:%S')
        user_dict['water_flow'].append(f'时间{ctime}:账户提现了{value},收取了手续费{value * settings.MONEY_RATE}')
        db_handler.save(user_dict)

        logger.debug(f'用户{username}提现了{value}')

        return True, f'尊敬的客户{username},您已经提现了{value},手续费{value * settings.MONEY_RATE},账户余额{user_dict.get("balance")}'
    return False, f'尊敬的客户{username}您的账户不够提现哦,请先充值!'

5.充值功能

用户层代码:
@common.login_auth('normal')
def pay_back():
    # 1.直接获取用户想要充值的钱数
    target_money = input('请输入您想要充值的金额>>>:').strip()
    # 2.还需要判断是否是整数或者小数
    flag, msg = bank_interface.pay_back_interface(is_login.get('username'), target_money)
    print(msg)
核心逻辑层代码:
def pay_back_interface(username, target_money):
    flag, value = common.get_num(target_money)
    if not flag:
        return False, '请输入符合要求的金额数字'
    # 1.获取用户字典数据
    user_dict = db_handler.select(username)
    # 2.直接添加余额
    user_dict['balance'] += value
    # 添加流水记录
    ctime = time.strftime('%Y-%m-%d %H:%M:%S')
    user_dict['water_flow'].append(f'时间{ctime}:账户充值了{value},该阶段账户余额{user_dict.get("balance")}')
    # 3.保存用户数据
    db_handler.save(user_dict)
    logger.debug(f'用户{username}充值了{value}')
    return True, f'尊敬的用户{username}小姐姐,您充值了{value},目前账户余额{user_dict.get("balance")}'

6.转账功能

用户层代码:
@common.login_auth('normal')
def transfer():
    # 1.获取想要转账的用户名
    target_user = input('请输入您想要转账的用户>>>:').strip()
    if target_user == is_login.get('username'):
        print('无法给自己转账')
        return  此处加了一个小判断 让用户无法给自己转账
    # 2.获取想要转账的金额
    target_money = input('请输入你想要转账的金额>>>>:').strip()
    # 3.只节调用转账的接口
    flag,msg = bank_interface.transfer_interface(is_login.get('username'),target_user,target_money)
    print(msg)
核心逻辑层代码:
def transfer_interface(current_user, target_user, targget_money):
    # 1.先校验目标用户是否存在
    target_user_dict = db_handler.select(target_user)
    if not target_user_dict:
        return False, f'转账用户{target_user}不存在'
    # 2.再校验用户输入的金额是否是整数或者小数
    flag, value = common.get_num(targget_money)
    if not flag:
        return False, '请输入符合要求的金额数字'
    # 3.获取当前用户余额是否充足
    current_user_dict = db_handler.select(current_user)
    # 4.判断当前登录用户余额是否充足
    if current_user_dict.get('balance') >= value:
        current_user_dict['balance'] -= value
        ctime = time.strftime('%Y-%m-%d %X')
        current_user_dict['water_flow'].append(f'时间{ctime}:给{target_user}账户转了{value}')
        target_user_dict['balance'] += value
        # 记录账户加钱的流水
        target_user_dict['water_flow'].append(f'时间{ctime}:收{current_user}账户转过来的钱{value}')
        db_handler.save(current_user_dict)
        db_handler.save(target_user_dict)
        logger.debug(f'用户{current_user}{target_user}账户转了{value}')
        return True, f'尊敬的{current_user},您给{target_user}账户转了{value},您目前账户余额为{current_user_dict["balance"]}'
    return False, '您当前余额不够转账'

7.查看流水

用户层:
@common.login_auth('normal')
def check_flow():
    # 直接调用查看流水的接口即可
    flag,msg = bank_interface.check_flow_interface(is_login.get('username'))
    if flag:
        for data in msg:
            print(data)
    else:
        print(msg)
核心逻辑层代码:
def check_flow_interface(username):
    # 1.获取用户名对应的用户字典
    user_dict = db_handler.select(username)
    if user_dict.get('water_flow'):
        logger.debug(f'用户{username}流水打印完成')
        return True, user_dict.get('water_flow')
    return False, '该用户暂无流水记录'

数据层代码

这个其实就是第三层的部分代码,牵扯一些数据保存和数据读取 和公共模块、配置同时属于第三层 把所有功能模块连接到一起

import os
import json
from ATM.conf import settings


# 数据保存功能
def save(user_dict):
    username = user_dict.get('username')
    # 5.拼接各种路径完成数据写入
    user_file_path = os.path.join(settings.DB_DIR, f'{username}.json')
    with open(user_file_path, 'w', encoding='utf8') as f:
        json.dump(user_dict, f, ensure_ascii=False)


# 数据查询功能
def select(username):
    user_file_path = os.path.join(settings.DB_DIR, f'{username}.json')
    if os.path.exists(user_file_path):  # 当路径存在 则直接返回该路径下用户数据 return '用户已存在'
        with open(user_file_path, 'r', encoding='utf8') as f:
            return json.load(f)

8.添加购物车

此功能为前面购物车代码拆分版

用户层代码:
@common.login_auth('normal')
def add_shop_car():
    """
    1.先获取商品数据
    2.打印商品数据并让用户选择
    3.用户一旦退出 调用接口完成购物车数据更新
    :return:
    """
    # 1.直接调用添加购物车接口
    flag, msg = shop_interface.add_shop_car_interface(is_login.get('username'))
    print(msg)
核心逻辑层代码:
def add_shop_car_interface(username):
    # 8.构造临时小字典存储商品信息
    temp_shop_car = {}
    while True:
        # 1.获取商品信息(目前是写死的 后期可以动态获取)
        good_list = [
            ['挂壁面', 3],
            ['印度飞饼', 22],
            ['极品木瓜', 666],
            ['土耳其土豆', 999],
            ['伊拉克拌面', 1000],
            ['董卓戏张飞公仔', 2000],
            ['仿真玩偶', 10000]
        ]
        # 2.循环打印商品信息供用户选择
        for num, good_data in enumerate(good_list):  # 0 []
            print(f"商品编号:{num}      |       商品名称:{good_data[0]}     |     商品单价:{good_data[1]}")
        # 3.获取用户输入的商品编号
        choice_num = input('请输入您想要购买的商品编号(q)>>>:').strip()
        '''10.添加结束标志 用于保存购物车数据'''
        if choice_num == 'q':
            # 11.获取当前登录用户的字典数据
            user_data_dict = db_handler.select(username)
            old_shop_car = user_data_dict.get('shop_car')  # {'印度飞饼':[10, 22]}
            # 12.保存购物车数据
            for g_name, g_list in temp_shop_car.items():
                if g_name in old_shop_car:
                    old_shop_car[g_name][0] += temp_shop_car[g_name][0]
                else:
                    old_shop_car[g_name] = g_list
            user_data_dict['shop_car'] = old_shop_car
            db_handler.save(user_data_dict)
            logger.info(f'{username}用户添加购物车成功')
            return True, '添加购物车成功 欢迎下次再来'
        # 4.判断编号是否是纯数字
        if not choice_num.isdigit():
            print('商品编号必须是纯数字')
            continue
        choice_num = int(choice_num)
        # 5.判断数字是否超出范围
        if choice_num not in range(len(good_list)):
            print('商品编号不在已存在的商品编号内 无法选择购买')
            continue
        # 6.根据商品编号获取商品信息
        target_good_list = good_list[choice_num]  # ['印度飞饼', 22]
        # 7.获取想要购买的商品个数
        good_num = input(f'请输入您想要购买的{target_good_list[0]}的商品数量>>>:').strip()
        if not good_num.isdigit():
            print('商品数量必须是纯数字')
            continue
        good_num = int(good_num)
        # 9.写入临时小字典中
        good_name = target_good_list[0]

        if good_name in temp_shop_car:
            temp_shop_car.get(good_name)[0] += good_num
        else:
            temp_shop_car[good_name] = [good_num, target_good_list[1]]

9.查看购物车

第一层代码:
@common.login_auth('normal')
def check_shop_car():
    # 1.直接调用查看购物车接口
    flag, msg = shop_interface.check_shop_car_interface(is_login.get('username'))
    if flag:  # msg = {'商品名称':[个数,单价]}
        for name, data_list in msg.items():
            print(f"{name}:{data_list[0]}个 | 单价:{data_list[1]}")
    else:
        print(msg)
核心逻辑层代码:
def check_shop_car_interface(username):
    # 1.获取当前登录用户的字典数据
    user_dict = db_handler.select(username)
    # 2.获取当前用户购物车数据
    shop_car = user_dict.get('shop_car')
    if shop_car:
        logger.info(f'用户{username}查看了购物车数据')
        return True, shop_car
    return False, '暂无购物车数据'

10.结算购物车

用户层:
@common.login_auth('normal')
def pay_shop_car():
    flag, msg = shop_interface.pay_shop_car_interface(is_login.get('username'))
    print(msg)
核心逻辑层:
def pay_shop_car_interface(username):
    user_data_dict = db_handler.select(username)
    # 3.获取当前用户购物车数据及账户余额
    shop_car = user_data_dict.get('shop_car')  # {'印度飞饼':[10, 22],'公仔':[100, 100]}
    if not shop_car:
        return False, '您的购物车空空如也'
    current_balance = user_data_dict.get('balance')
    # 4.统计购物车商品总价
    total_money = 0
    for g_list in shop_car.values():  # [10, 22]     [100, 100]
        total_money += g_list[0] * g_list[1]
    # 5.比较余额是否充足
    if total_money > current_balance:
        return False, '您的账户余额不足!!!'
    user_data_dict['balance'] -= total_money
    ctime = time.strftime('%Y-%m-%d %X')
    user_data_dict['water_flow'].append(f'时间{ctime}:购物消费了{total_money}')
    # 6.清空购物车
    user_data_dict['shop_car'] = {}
    logger.info(f'{username}疯狂消费了{total_money}')
    db_handler.save(user_data_dict)
    return True, f'尊敬的{username} 您本次消费{total_money} 卡上余额剩余{user_data_dict.get("balance")} 欢迎下次再来挥霍!!!'

11.管理员功能

第一层代码:
@common.login_auth('admin')
def admin():
    while True:
        print("""
        1.冻结账户
        2.解封账户
        """)
        choice_num = input('请输入想要执行的功能>>>:').strip()
        if choice_num == 'q':
            break
        if choice_num == '1':
            _,name_list = admin_interface.get_all_name_list_inteface()
            name_list = [name for name in name_list if name != is_login.get('username')]
            for num,name in enumerate(name_list):
                print(f'姓名编号:{num}  姓名:{name}')
            choice_name_num = input('请输入你想要冻结的姓名编号>>>:').strip()
            if not choice_name_num.isdigit():
                print('姓名编号必须是纯数字!!!')
                continue
            choice_name_num = int(choice_name_num)
            if choice_name_num not in range(len(name_list)):
                print('输入的编号超出范围!!!')
                continue
            target_name = name_list[choice_name_num]
            flag, msg = admin_interface.tack_lock_name_interface(target_name,is_login.get('username'))
            print(msg)
        if choice_num == '2':
            _,name_list = admin_interface.get_all_lock_name_list_inteface()
            name_list = [name for name in name_list if name != is_login.get('username')]
            for num,name in enumerate(name_list):
                print(f'姓名编号:{num}  姓名:{name}')
            choice_name_num = input('请输入你想要解封的姓名编号>>>:').strip()
            if not choice_name_num.isdigit():
                print('姓名编号必须是纯数字!!!')
                continue
            choice_name_num = int(choice_name_num)
            if choice_name_num not in range(len(name_list)):
                print('输入的编号超出范围!!!')
                continue
            target_name = name_list[choice_name_num]
            flag, msg = admin_interface.tack_unlock_name_interface(target_name,is_login.get('username'))
            print(msg)
第二层逻辑层代码
from db import db_handler
from lib import common
admin_logger = common.get_logger('管理员模块')
def get_all_name_list_inteface():
    name_list = db_handler.get_name_list()
    return True, name_list

def tack_lock_name_interface(lock_name,name):
    user_dict = db_handler.select(lock_name)
    if user_dict['is_lock']:
        return False,f'用户{lock_name}已经被冻结!!!'
    user_dict['is_lock'] = True
    db_handler.save(user_dict)
    admin_logger.info(f'用户{lock_name}被管理员{name}冻结!!!')
    return True, f'用户{lock_name}被管理员{name}冻结!!!'

def get_all_lock_name_list_inteface():
    name_list = db_handler.get_name_list()
    temp_list = []
    for name in name_list:
        if db_handler.select(name)['is_lock']:
            temp_list.append(name)
    return True, temp_list

12.打印日志

第一层代码:
def print_log_data():
    flag,msg = user_interface.print_log_data_interface()
    print(msg)
核心逻辑层:
def print_log_data_interface():
    # 拼接路径把数据读出来 打印
    log_lj = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'log\ATM.log')
    with open(log_lj, 'r', encoding='utf8') as f:
        for data in f:
            print(data.strip('\n'))
        return True, data

ATM+购物车总结:

	本次项目主要是锻炼代码逻辑、熟悉项目三层架构、锻炼思维逻辑能力、对于初学者来说 有一点点难度、只是为了适应项目结构和锻炼对python基础数据的处理和模块的使用、逻辑思维很重要、虽然现在写不出来、不过可以进行项目的熟悉、尽快适应python项目的简单三层结构、多敲多练、购物车挺麻烦、不也写的现在感觉简简单单的、慢慢来!!!!!

image
事关一周后又敲了一遍,还是很多东西没有很熟悉,比如下午遇到一个问题报错 一直找不到原因后来崔大佬看了下 发现是一个变量名重复了 找了一个多小时 下次一定不能再犯这样的错误, 还要一个没有调用管理员的问题,是因为记录管理员登录的字典没有记录 所以装饰器没有执行函数,没有登录上,虽然自己找出来了 但是跟自己的粗心有关 这就是死记硬背代码的弊端 很多东西根本没有理解 这样的死东西很害人 学习要能举一反三 但是现在抄还抄不利索,老师留的作业还是要多看 多思考 不能摆烂了 还是努力一点 为了自己也能成大佬 努力是不能少的!!!!!

posted @   缀月  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示