作业要求:

模拟实现一个ATM + 购物商城程序

    额度 15000或自定义
    实现购物商城,买东西加入 购物车,调用信用卡接口结账
    可以提现,手续费5%
    每月22号出账单,每月10号为还款日,过期未还,按欠款总额 万分之5 每日计息
    支持多账户登录
    支持账户间转账
    记录每月日常消费流水
    提供还款接口
    ATM记录操作日志
    提供管理接口,包括添加账户、用户额度,冻结账户等。。。
    用户认证用装饰器

首先分析需求:

1. 我们要分析这个项目有几个界面?

(1)从需求中发现我们需要一个购物商城,要买东西,因此项目中肯定要有一个购物商城的界面。

(2)从需求中可以发现还需要提供管理接口进行添加账户,用户额度之类的操作,因此肯定也需要一个管理员操作的界面。

(3)从需求中我们还可以发现一些提现,还款等操作,因此肯定需要一个atm操作的界面。

第15天作业ATM+购物商城的讲解和练习-LMLPHP

2. 当界面分析完成之后,每个界面下面需要什么操作呢?

 (1).购物商城

购物商城的操作
    添加物品到购物车(购物)
    查看购物车(因为第一个需求提到了购物车)
    清空购物车
    调用信用卡结算(结算)

 第15天作业ATM+购物商城的讲解和练习-LMLPHP

(2).管理员操作

管理员界面的操作
    添加账户
    冻结账户
    解冻账户(既然有冻结账户,肯定就需要解冻)
    更改额度(更改信用卡的额度)
   管理员登录(这个是隐式需求,既然操作肯定要登录对吧)

第15天作业ATM+购物商城的讲解和练习-LMLPHP

(3). ATM操作界面

ATM操作界面
    还款
    转账
    提现
    结算(这个是购物商城调用信用卡结算的接口)

 第15天作业ATM+购物商城的讲解和练习-LMLPHP

3. 当界面的需求分析完之后,大致的能够与用户交互的功能都已经包含进去了,剩下的就是一些后台操作 

后台操作,不需要用户看见的额
    ATM记录操作日志
        还款,提现,转账,结算
    记录流水操作(与atm记录的格式不一样,而且只是当运行成功的时候才会记录)
        还款,提现,转账,结算

通过这三步的分析之后这个项目基本上所有的需求就都已经分析出来了,只有出账单的暂时还没有做

需求分析完毕之后就开始写代码了

首先我们的程序需要一个入口,类似于这样的,可以通过用户选择分别进入不同的操作界面,这样的操作在python中可以通过一个功能字典的形式来表示。

第15天作业ATM+购物商城的讲解和练习-LMLPHP

什么是功能字典呢?

def shop():
    print('shop')

def atm():
    print('atm')

def admin():
    print('admin')

system_welcome_str = '''1.管理员界面
2.ATM操作界面
3.购物中心
4.退出'''
def run():
    # 打印输出信息
    print(system_welcome_str)
    # 设置功能字典,每个字符对应的都是函数名
    system_welcome_funcs = {'1': admin, '2': atm, '3': shop}
    # 用户输入
    choice = input().strip()
    # 判断用户输入的是否在功能字典中
    if choice in system_welcome_funcs:
        # 如果在就执行选中的那个函数
        system_welcome_funcs[choice]()
# 运行一个下就可以发现可以根据用户的输入分别执行上面的三个函数了
# 也就是进入分别不同的界面了,这就是功能字典
# 之后我们只需要把上面的三个函数按照这样的方式就可以做出同样的效果了
run()

数据的存储

当这些功能字典实现了之后我们就要考虑一下用户信息的存储方式了,也就是以后遇到问题非常重要的一步,就是数据库的设计,此处为了简单管理员账号和密码的存储设置没有做,只是从内存中去判断是否有等于admin和123而已,但是对于普通用户的持久化存储是通过|的形式存储的,在内存中我们是通过列表的形式存储的,如下:因此我们要写一些函数用来做这两者之间的转换


文件中的存储
''' hwt|123|15000|966983.85|0 zy|123|15000|2700|0 hhhh|123|15000|1500|0 ''' 内存中: ''' user_data = [ {'user': hu, 'pwd': 123, 'max': '15000', 'money': '500', 'lock': lock}, {'user': zy, 'pwd': 123, 'max': '15000', 'money': '500', 'lock': lock}, {'user': zyy, 'pwd': 123, 'max': '15000', 'money': '500', 'lock': lock}, ]'''

文件的操作

当我们的关于用户的一些显式的要求做完了之后,我们还需要在后台去操作日志文件和流水文件,为了减少日志文件的打开和关闭次数,我们最好在全局定一个变量去直接打开日志文件,然后当程序结束的时候再去关闭这个文件,但是对于流水文件就不一样了,我们对于每个用户都有一个单独的流水文件,因此我们只能在登录用户成功之后再打开文件,当退出当前界面的时候再去关闭相应的文件就是了。不仅如此,当我们文件还没有结束的时候还是希望强制的把缓冲区的内容刷到文件上才好!

# 定义一个变量指向日志文件句柄
atm_logger_file = open(r'DB\atm_logger.txt', 'at', encoding='utf-8')

# 定义一个流水文件
flow_file = None  # 以用户名的方式存的文件



def atm_login():
    '''atm账号用户登录'''
    while True:
        user = input('请输入用户名(q退出)>>').strip()
        atm_logger_file.write('[{time}] user:{user} function:{function}\n'.format(
            time=datetime.datetime.now(),
            user=user,
            function='atm_login'
        ))
        atm_logger_file.flush()
        if user == 'q':
            return True
        if not user:
            print('用户名不能为空!')
            continue
        usr = get_user(user)
        if usr:
            pwd = input('请输入密码>>').strip()
            if not pwd:
                print('密码不能为空!')
                continue
            if pwd == usr['pwd']:
                print('登录成功!')
                global current_atm_user, flow_file
                # 当前登录用户状态信息
                current_atm_user = usr
                # 登录成功之后打开流水文件
                flow_file = open(r'DB\{filename}.txt'.format(filename=user), mode='at', encoding='utf-8')
                return True
        else:
            print('输入的用户名不存在!')
            continue

装饰器

装饰器就是在不改变源代码的情况下添加功能,无疑,代码中主要设置了三个装饰器,一个就是管理员登录的装饰器,一个就是用户登录的装饰器,一个就是操作日志的装饰器。

源代码

第15天作业ATM+购物商城的讲解和练习-LMLPHP第15天作业ATM+购物商城的讲解和练习-LMLPHP
import datetime

user_data = []
'''
user_data = [
    {'user': hu, 'pwd': 123, 'max': '15000', 'money': '500', 'lock': lock},
    {'user': zy, 'pwd': 123, 'max': '15000', 'money': '500', 'lock': lock},
    {'user': zyy, 'pwd': 123, 'max': '15000', 'money': '500', 'lock': lock},
]'''

# 定义一个变量指向日志文件句柄
atm_logger_file = open(r'DB\atm_logger.txt', 'at', encoding='utf-8')

# 定义一个流水文件
flow_file = None  # 以用户名的方式存的文件

# 当前登录atm的用户状态信息
current_atm_user = None  # {'user': user, 'pwd': pwd, 'max': max, 'money': money, 'lock': lock}

# 当前admin用户的登录状态信息
current_admin_user = None  # current_admin_user = 'admin'

# 购物车的信息
car = {}  # car = {'小米mix2': {'price': 100, 'count': 11}}

products = [
    {'name': 'iPhone', 'price': 6888},
    {'name': '锤子TNT', 'price': 10888},
    {'name': '小米mix2', 'price': 2888},
]

system_welcome_str = '''1.管理员界面
2.ATM操作界面
3.购物中心
4.退出'''

admin_welcome_str = '''1.添加账户
2.冻结账户
3.解冻账户
4.更改额度
5.退出'''

atm_welcome_str = '''1.登录
2.转账
3.提现
4.还款
5.退出'''

shop_welcome_str = '''1.购物
2.查看购物车
3.结算
4.清空
5.退出'''


# 装饰器============================


def atm_login():
    '''atm账号用户登录'''
    while True:
        user = input('请输入用户名(q退出)>>').strip()
        atm_logger_file.write('[{time}] user:{user} function:{function}\n'.format(
            time=datetime.datetime.now(),
            user=user,
            function='atm_login'
        ))
        atm_logger_file.flush()
        if user == 'q':
            return True
        if not user:
            print('用户名不能为空!')
            continue
        usr = get_user(user)
        if usr:
            pwd = input('请输入密码>>').strip()
            if not pwd:
                print('密码不能为空!')
                continue
            if pwd == usr['pwd']:
                print('登录成功!')
                global current_atm_user, flow_file
                # 当前登录用户状态信息
                current_atm_user = usr
                # 登录成功之后打开流水文件
                flow_file = open(r'DB\{filename}.txt'.format(filename=user), mode='at', encoding='utf-8')
                return True
        else:
            print('输入的用户名不存在!')
            continue


def atm_login_auth(func):
    '''atm用户登录装饰器'''

    def wrapper(*args, **kwargs):
        # 判断admin是否已经登录
        if current_atm_user:
            return func(*args, **kwargs)
        else:
            atm_login()
            if current_atm_user:
                return func(*args, **kwargs)

    return wrapper


def atm_logger(func):
    '''atm记录日志装饰器'''

    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        # 程序执行完成之后写入操作日志文件
        # 并且刷新缓冲区的内容
        atm_logger_file.write('[{time}] user:{user} function:{function}\n'.format(
            time=datetime.datetime.now(),
            user=current_atm_user['user'],
            function=func.__name__
        ))
        atm_logger_file.flush()
        return res

    return wrapper


def admin_login():
    '''管理员用户登录'''
    while True:
        user = input('请输入管理员账号(q退出)>>').strip()
        if user == 'q':
            print('退出!')
            return True
        if not user:
            print('用户名不能为空!')
            continue
        pwd = input('请输入密码>>').strip()
        if not pwd:
            print('密码不能为空!')
            continue
        if pwd == '123' and user == 'admin':
            print('登录成功!')
            global current_admin_user
            current_admin_user = 'admin'
            return True
        else:
            print('账号密码有误!')


def admin_login_auth(func):
    '''admin用户登录装饰器'''

    def wrapper(*args, **kwargs):
        # 判断admin是否已经登录
        if current_admin_user:
            return func(*args, **kwargs)
        else:
            admin_login()
            if current_admin_user:
                return func(*args, **kwargs)
            return True

    return wrapper


# 文件操作方法============================

def load_data():
    '''加载保存文件的数据到内存中'''
    with open('DB/userInfo.txt', 'rt', encoding='utf-8') as f:
        for line in f:
            if line:
                user, pwd, max, money, lock = line.strip(' \n').split('|')
                user_data.append({'user': user, 'pwd': pwd, 'max': max, 'money': money, 'lock': lock})


def save_to_file():
    '''将内存中的数据写入文件中'''
    with open('DB/userInfo.txt', 'wt', encoding='utf-8') as f:
        for user in user_data:
            f.write('|'.join(user.values()) + '\n')


def get_user(user):
    '''根据用户名获得用户相应的信息'''
    for account in user_data:
        if user in account.values():
            return account


def welcome_interface(welcome_str, decorate_str):
    '''
    打印欢迎界面
    :param welcome_str: 当前界面的功能介绍
    :param decorate_str: 前后的修饰符
    :return:
    '''
    print('welcome to {decorate_str}!'.format(decorate_str=decorate_str).center(50, '*'))
    print(welcome_str)
    print('welcome to {decorate_str}!'.format(decorate_str=decorate_str).center(50, '*'))


def user_choice_func(func_dict, func_str, decorate_str):
    '''根据功能字典执行相应的函数'''
    temp_info = '请输入'
    res = True
    while True:
        # 当从下级返回到上级的时候就打印一次功能
        if res:
            welcome_interface(func_str, decorate_str)
        index = input(temp_info + '你要选择的操作>>')
        # 找到功能字典中key的最大值再加一就是退出
        if index == str(int(max(func_dict.keys())) + 1):
            print('返回!')
            return True
        if index not in func_dict:
            temp_info = '输入有误!请重新输入'
            res = False
            continue
        if index in func_dict:
            res = func_dict[index]()


# 管理员接口===========================

def admin():
    return user_choice_func(admin_welcome_funcs, admin_welcome_str, 'admin')


def show_users_and_select():
    '''打印当前用户并且选择一个用户'''
    for usr in user_data:
        print('{user}   {max}  {money}  {lock}'.format(user=usr['user'], max=usr['max'], money=usr['money'],
                                                       lock=usr['lock']))
    while True:
        choice_usr = input('请输入你要选择的用户(q退出)>>').strip()
        if choice_usr == 'q':
            return
        if not choice_usr:
            print('输入的用户不能为空!')
            continue
        for usr in user_data:
            if choice_usr in usr.values():
                return usr
        else:
            print('您输入的用户名不存在,请重新输入!')


@admin_login_auth
def create_user():
    '''添加账户'''
    while True:
        user = input('请输入要添加用户名(q退出)>>').strip()
        if user.lower() == 'q':
            return True
        if not user:
            print('用户名不能为空!')
            continue
        usr = get_user(user)
        if usr:
            print('用户名已经存在!')
            continue
        pwd = input('请输入密码>>').strip()
        if not pwd:
            print('密码不能为空!')
            continue
        if pwd != input('请确认您的密码>>').strip():
            print('密码不一致!')
            continue
        # 刷入内存中
        user_data.append({'user': user, 'pwd': pwd, 'max': '15000', 'money': '1500', 'lock': '0'})
        save_to_file()
        print('创建成功!')
        return True


@admin_login_auth
def lock_user():
    '''冻结账户'''
    res = show_users_and_select()
    if res:
        res['lock'] = '1'
        save_to_file()
        print('成功锁定账户!')
    return True


@admin_login_auth
def unlock_user():
    '''解冻账户'''
    usr = show_users_and_select()
    if usr:
        usr['lock'] = '0'
        save_to_file()
        print('成功解冻账户!')
    return True


@admin_login_auth
def modify_amount():
    '''更改账户的额度信息'''
    usr = show_users_and_select()
    if usr:
        while True:
            max_money = input('更改的额度为(q退出)>>').strip()
            if max_money.lower() == 'q':
                return True
            if not max_money.isdigit():
                print('输入有误!')
                continue
            usr['max'] = max_money
            save_to_file()
            print('更改额度成功')
            return True
    return True


# 购物中心=============================
def shop():
    return user_choice_func(shop_welcome_funcs, shop_welcome_str, 'shop')


def shopping():
    '''添加商品到购物车中'''
    # 打印商品的信息
    count = 1
    for p in products:
        print('序号:%-3s 名称:%-10s  价格:%-10s' % (count, p['name'], p['price']))
        count += 1
    # 根据商品信息加入购物车
    while True:
        select = input('请输入商品序号(q退出)>>').strip()
        if select == 'q':
            return True
        if select.isdigit() and 1 <= int(select) <= len(products):
            product_name = products[int(select) - 1]['name']
            # 加入购物车
            if product_name in car:
                car[product_name]['count'] += 1
            else:
                car[product_name] = {'price': products[int(select) - 1]['price'], 'count': 1}
            print('%s商品已经成功加入购物车!' % (product_name))
        else:
            print('输入有误!请重新输入')


def show_cars():
    '''查看购物车信息'''
    # 判断购物车是否为空
    if car:
        print('您的购物车信息:'.center(50, '-'))
        for product_name, value in car.items():
            print('名称:{product_name} 价格:{money} 个数:{count} 总价{price}'.format(
                product_name=product_name,
                money=value['price'],
                count=value['count'],
                price=value['price'] * value['count']
            ))
        input('按任意键退出!')
    else:
        print('您的购物车为空!请先购物!')
        shopping()
    return True


@atm_login_auth
def pay_cars():
    '''结算购物车里面的内容'''
    if car:
        sum = 0
        for value in car.values():
            sum += value['price'] * value['count']
        print('您的订单总价为{sum}'.format(sum=sum))
        # 调用信用卡接口
        if payment(sum):
            print('购物车购买成功!')
            clear()
            flow_file.write('{time} {user} 花了 {money} 余额为:{current_money}\n'.format(
                time=datetime.datetime.now(),
                user=current_atm_user['user'],
                money=sum,
                current_money=current_atm_user['money']
            ))
            flow_file.flush()
    else:
        print('您的购物车为空!请先购物!')
        shopping()
    return True


def clear():
    '''清空购物车'''
    car.clear()
    print('购物车已经清空!')
    return True


# atm接口==============================

def atm():
    return user_choice_func(atm_welcome_funcs, atm_welcome_str, 'ATM')


def str_to_num(input_str):
    '''将输入的值转换成int或者float输入的值必须为字符串'''
    if input_str.isdigit():
        return int(input_str)
    temp = input_str.split('.')
    # 以点分割字符串长度为2,并且左右都是整数
    if len(temp) == 2 and temp[0].isdigit() and temp[1].isdigit():
        return float(input_str)


@atm_login_auth  # withdraw = atm_login_auth(withdraw)  withdraw ===>login的wrapper
@atm_logger  # withdraw = atm_logger(login的wrapper)  withdraw ====> logger的wrapper
def withdraw():
    '''根据当前额度进行提现'''
    print('当前可用额度为{money}'.format(money=current_atm_user['money']))
    while True:
        money = input('输入你要提现的金额(q退出)>>').strip()
        if money == 'q':
            return True
        money = str_to_num(money)
        if not money:
            print('输入有误!')
            continue
        # 手续费
        opt = money * 0.05
        print('提现所需手续费为{opt}'.format(opt=opt))
        if money + opt > str_to_num(current_atm_user['money']):
            print('很尴尬,余额不足!')
            continue
        # 修改内存中的值并写入文件
        current_atm_user['money'] = str(str_to_num(current_atm_user['money']) - opt - money)
        save_to_file()
        print('提现成功')
        # 写入流水文件
        flow_file.write('{time} {user} 取了 {money} 余额为:{current_money}\n'.format(
            time=datetime.datetime.now(),
            user=current_atm_user['user'],
            money=money,
            current_money=current_atm_user['money']
        ))
        flow_file.flush()
        return True


@atm_login_auth
@atm_logger
def transfer():
    '''转账'''
    while True:
        to_user = input('输入您要转账的用户(q退出)>>').strip()
        if to_user.lower() == 'q':
            return True
        if not to_user:
            print('用户名不能为空!')
            continue
        to_user = get_user(to_user)
        money = input('输入您要转账的金额(q退出)>>').strip()
        if money.lower() == 'q':
            return True
        money = str_to_num(money)
        # 修改内存中的money值
        current_atm_user['money'] = str(str_to_num(current_atm_user['money']) - money)
        to_user['money'] = str(str_to_num(to_user['money']) + money)
        save_to_file()
        print('转账成功!')
        # 写入流水文件
        flow_file.write('{time} {user} 转给{to_user}了{money}元 余额为:{current_money}\n'.format(
            time=datetime.datetime.now(),
            user=current_atm_user['user'],
            to_user=to_user['user'],
            money=money,
            current_money=current_atm_user['money']
        ))
        flow_file.flush()
        return True


@atm_login_auth
@atm_logger
def repayment():
    '''还款'''
    print('当前需要还款{money}'.format(
        money=str(str_to_num(current_atm_user['max']) - str_to_num(current_atm_user['money']))
    ))
    while True:
        money = input('输入您要还款金额(q退出)>>').strip()
        if money.lower() == 'q':
            return True
        money = str_to_num(money)
        # 修改内存中的money值
        current_atm_user['money'] = str(str_to_num(current_atm_user['money']) + money)
        save_to_file()
        print('还款成功!')
        flow_file.write('{time} {user} 还了 {money} 余额为:{current_money}\n'.format(
            time=datetime.datetime.now(),
            user=current_atm_user['user'],
            money=money,
            current_money=current_atm_user['money']
        ))
        flow_file.flush()
        return True


@atm_logger
def payment(price):
    '''结算接口,用户购买商品的时候调用此接口进行结算'''
    if price < str_to_num(current_atm_user['money']):
        current_atm_user['money'] = str(str_to_num(current_atm_user['money']) - price)
        print('结算成功')
        save_to_file()
        return True
    else:
        print('当前账户的额度不足!')
        return False


# 定义的系统功能以及功能字典

system_welcome_funcs = {'1': admin, '2': atm, '3': shop}

admin_welcome_funcs = {'1': create_user, '2': lock_user, '3': unlock_user, '4': modify_amount}

atm_welcome_funcs = {'1': atm_login, '2': transfer, '3': withdraw, '4': repayment}

shop_welcome_funcs = {'1': shopping, '2': show_cars, '3': pay_cars, '4': clear}


def run():
    '''程序的执行入口'''
    # 加载数据到内存中
    load_data()
    # 根据欢迎字典去执行相应的函数
    user_choice_func(system_welcome_funcs, system_welcome_str, 'system')
    # 关闭操作系统打开的日志文件
    atm_logger_file.close()
    # 关闭操作系统打开的流水文件
    if flow_file:
        flow_file.close()


run()
View Code
10-09 20:17