1. 程式人生 > >第15天作業ATM+購物商城的講解和練習

第15天作業ATM+購物商城的講解和練習

作業要求:

模擬實現一個ATM + 購物商城程式

    額度 15000或自定義
    實現購物商城,買東西加入 購物車,呼叫信用卡介面結賬
    可以提現,手續費5%
    每月22號出賬單,每月10號為還款日,過期未還,按欠款總額 萬分之5 每日計息
    支援多賬戶登入
    支援賬戶間轉賬
    記錄每月日常消費流水
    提供還款介面
    ATM記錄操作日誌 
    提供管理介面,包括新增賬戶、使用者額度,凍結賬戶等。。。
    使用者認證用裝飾器

首先分析需求:

1. 我們要分析這個專案有幾個介面?

(1)從需求中發現我們需要一個購物商城,要買東西,因此專案中肯定要有一個購物商城的介面。

(2)從需求中可以發現還需要提供管理介面進行新增賬戶,使用者額度之類的操作,因此肯定也需要一個管理員操作的介面。

(3)從需求中我們還可以發現一些提現,還款等操作,因此肯定需要一個atm操作的介面。

2. 當介面分析完成之後,每個介面下面需要什麼操作呢?

 (1).購物商城

購物商城的操作
    新增物品到購物車(購物)
    檢視購物車(因為第一個需求提到了購物車)
    清空購物車
    呼叫信用卡結算(結算)

 

(2).管理員操作

管理員介面的操作
    新增賬戶
    凍結賬戶
    解凍賬戶(既然有凍結賬戶,肯定就需要解凍)
    更改額度(更改信用卡的額度)   管理員登入(這個是隱式需求,既然操作肯定要登入對吧)

(3). ATM操作介面

ATM操作介面
    還款
    轉賬
    提現
    結算(這個是購物商城呼叫信用卡結算的介面)

 

3. 當介面的需求分析完之後,大致的能夠與使用者互動的功能都已經包含進去了,剩下的就是一些後臺操作 

後臺操作,不需要使用者看見的額
    ATM記錄操作日誌
        還款,提現,轉賬,結算
    記錄流水操作(與atm記錄的格式不一樣,而且只是當執行成功的時候才會記錄)
        還款,提現,轉賬,結算

通過這三步的分析之後這個專案基本上所有的需求就都已經分析出來了,只有出賬單的暫時還沒有做

需求分析完畢之後就開始寫程式碼了

首先我們的程式需要一個入口,類似於這樣的,可以通過使用者選擇分別進入不同的操作介面,這樣的操作在python中可以通過一個功能字典的形式來表示。

什麼是功能字典呢?

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

裝飾器

裝飾器就是在不改變原始碼的情況下新增功能,無疑,程式碼中主要設定了三個裝飾器,一個就是管理員登入的裝飾器,一個就是使用者登入的裝飾器,一個就是操作日誌的裝飾器。

原始碼

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