ATM shopping cart project + three-tier architecture design

ATM shopping cart project

Simulation Implementation of an ATM + shopping mall program.

The program realizes the functions of login, registration, withdrawal, recharge and repayment of ordinary users, and supports the function of shopping in online shopping mall.

When the account balance is enough to pay the price of the goods, the payment will be deducted; when the balance is insufficient, the payment will not be made, and the goods will be stored in the personal shopping cart.

If the user has administrator function, it also supports administrator login. See the project requirements section for specific requirements.

Three tier architecture

Clear structure design is very important in project development. Its importance is embodied in three aspects at least: clear structure; strong maintainability; high scalability.

In the common project structure design, three-tier architecture design is very practical. This architecture design pattern divides the whole program into three layers:

  • User view layer: it is interactive and can accept user's input data and display displayed messages.
  • Logical interface layer: receive the parameters passed from the view layer, call the data layer according to the logical judgment to process and return a result to the user.
  • Data processing layer: accepts the parameters passed from the interface layer, and performs data addition, deletion, modification and query.
#Advantages: clear structure and clear responsibilities. Strong expansibility, easy to maintain. It is safe for data.
#Disadvantages: each function must cross the logical interface layer and cannot directly access the database, so the efficiency will be reduced.

Project requirements

1. Quota 15000 or user-defined -- > registration function
 2. Realize shopping mall, add shopping cart to shopping, and call credit card interface to checkout -- > shopping function and payment function
 3. Withdrawable, service charge 5% -- withdrawal function
 4. Support multi account login - > login function. If login fails, freeze the account three times
 5. Support inter account transfer - > transfer function
 6. Record daily consumption - > record flow function
 7. Provide repayment interface > repayment function
 8.ATM record operation log - > record log function
 9. Provide management interface, including adding account, user quota, freezing account, etc... --->Administrator function
 10. Decorator for user authentication login authentication decorator

Extraction function

#Functions displayed to the user for selection (user view layer)
1. Registration function
 2. Login function
 3. View balance
 4. Withdrawal function
 5. Repayment function
 6. Transfer function
 7. View flow
 8. Shopping function
 9. View Cart
 10. Administrator function

Implementation ideas

The last project summary is also about ATM, but all the functions in that project are in a py file; this project summary can't be done like that anymore, this time we need to standardize.

We know that the software development directory specification is to distribute the code in different files (folders) according to different functions of the program. This project also adopts this specification.

In addition, we learned the three-tier architecture design of the project, dividing a function into three levels and clarifying the responsibilities of each part.

Therefore, based on the specification of software development directory, this project adopts the principle of three-tier architecture to write the code of each specific function.

Project framework

The whole project adopts three-layer structure design. The user's direct contact is the user's view layer. By selecting different functions, the user can enter the user view layer with different functions.

In the user view layer, the user inputs the data; then the user view layer transmits the user's data to the logical interface layer, which calls the interface of the data processing layer to obtain the relevant data of the user, make a certain logical judgment, and then return the data and / or information after the logical judgment to the user view layer to display to the user.

Program structure: follow the software catalog specification

ATM&Shop/
|-- conf
|	|-- setting.py				# Project profile
|-- core
|	|-- admin.py				# Administrator view layer functions
|	|-- current_user.py			# Record the current login user information [username, is \
|	|-- shop.py					# Shopping related view layer functions
|	|-- src.py					# Main program (including user view layer function and atm main function)
|-- db
|	|-- db_handle.py			# Data processing layer function 
|	|-- goods_data.json			# Commodity information file
|	|-- users_data				# User information json folder
|	|	|-- xliu.json			# User information file: username | password | balance | my | flow | my | cart, etc
|	|	|-- egon.json
|-- interface					# Logical interface
|	|-- admin_interface.py			# Administrator logic interface layer functions
|	|-- bank_interface.py			# Functions of bank related logic interface layer
|	|-- shop_interface.py			# Functions of shopping related logic interface layer
|	|-- user_interface.py			# User related logic interface layer functions
|-- lib						
|	|-- tools.py		# Common functions: encrypt | login decorator permission verification | record pipeline | log, etc
|-- log					# Log folder
|	|-- operation.log
|	|-- transaction.log
|-- readme.md
|-- run.py				# Project start up file

Running environment

- windows10, 64 position
- python3.8
- pycharm2019.3

Analysis of three-tier structure of registration function

Registration function user view layer: core/src.py

from lib.tools import hash_md5, auto
from core.current_user import login_user
from interface.user_interface import register_interface


@auto('register')
def register():
    print('Registration page'.center(50, '-'))
    while 1:
        name = input('Please enter the user name:').strip()
        pwd = input('Please input a password:').strip()
        re_pwd = input('Please confirm the password:').strip()
        if pwd != re_pwd:
            print('The two passwords are not the same, please re-enter')
            continue
        flag, msg = register_interface(name, hash_md5(pwd))
        print(msg)
        if flag:
            break
            
# Registration function user view layer receives user registration information: user name | password | confirm password
# First, make a small logical judgment to determine whether the password is consistent with the confirmation password? If it is inconsistent, the user will be prompted to input the new password
# If the passwords are the same, the user name and password will be handed over to the logical interface layer through the registration interface
# Then accept the return data and information of logic interface layer, print display and next step judgment.

Register function logic interface layer: Interface / user interface.py

from conf.settings import INIT_BALANCE
from core.current_user import login_user
from db import db_handle
from lib.tools import save_log


def register_interface(name, pwd):
    """
    //Registration interface
    :param name:
    :param pwd: Ciphertext
    :return:
    """
    user_dict = db_handle.get_user_info(name)
    if user_dict:
        return False, 'User name already exists'
    user_dict = {
        'username': name,
        'password': pwd,
        'balance': INIT_BALANCE,
        'is_admin': False,
        'is_locked': False,
        'login_failed_counts': 0,
        'my_cart': {},
        'my_flow':{}
    }
    save_log('Daily operation').info(f'{name}Account registration succeeded')
    db_handle.save_user_info(user_dict)
    return True, 'login was successful'
# The registration function logic interface layer receives the user name and password from the user view layer,
# By calling the get? User? Info function of the data processing layer, read the user file and obtain the user's information dictionary
# If the user information dictionary exists, the user name has been registered for use, and the information that cannot be registered in the user view layer will be returned
# If the user information dictionary does not exist, it can be registered.
# Create a new user information dictionary, initialize the relevant data, hand it to the save ﹣ user ﹣ info function in the data processing layer, and return the information that can be registered in the user view layer.

Data processing layer: db/db_handle.py

import os, json
from conf.settings import USER_DB_DIR


def get_user_info(name):
    user_file = os.path.join(USER_DB_DIR, f'{name}.json')
    if os.path.isfile(user_file):
        with open(user_file, 'rt', encoding='utf-8') as f:
            return json.load(f)
    else:
        return {}


def save_user_info(user_dict):
    user_dict['balance'] = round(user_dict['balance'], 2)
    user_file = os.path.join(USER_DB_DIR, f'{user_dict.get("username")}.json')
    with open(user_file, 'wt', encoding='utf-8') as f:
        json.dump(user_dict, f, ensure_ascii=False)

# Data processing layer function: obtain the user information dictionary through the user name; if the user exists, return the user information dictionary; if the user does not exist, return the empty dictionary
# The save? User? Info function receives the interface of the logical interface layer, serializes and saves the user information dictionary to an independent file, and names the file name with the user name

Three layer structure analysis of withdrawal function

Withdrawal function user view layer: core/src.py

from lib.tools import auth, is_number, auto
from core.current_user import login_user
from interface.bank_interface import withdraw_interface


@auto('Cash withdrawal')
@auth
def withdraw():
    print('Presentation page'.center(50, '-'))
    while 1:
        amounts = input('Please enter the reflected amount:').strip()
        if not is_number(amounts):
            print('Please enter the legal amount')
            continue
        flag, msg = withdraw_interface(login_user[0], float(amounts))
        print(msg)
        if flag:
            break
 
# Presentation function user view layer: can only be used after user login (use function decorator auth to realize login verification)
# Receive the withdrawal amount input by the user, first make small logic to determine whether the amount input by the user is a number (decimal number is supported), and implement it through the tool function is u number
# Then transfer the legal withdrawal amount to floating-point number and hand it to the withdrawal logic interface layer through the withdrawal interface
# Print data returned by logic interface layer and make judgment

Interface / bank? Interface.py

from db import db_handle
from conf.settings import SERVICE_FEE_RATIO
from lib.tools import save_flow, save_log


def withdraw_interface(name, amounts):
    user_dict = db_handle.get_user_info(name)
    amounts_and_fee = amounts * (1 + SERVICE_FEE_RATIO)
    if amounts_and_fee > user_dict.get('balance'):
        save_log('Cash withdrawal').info(f'{name}Cash withdrawal{amounts}Failed to withdraw due to insufficient balance')
        return False, 'Insufficient account balance'

    user_dict['balance'] -= amounts_and_fee
    msg = f'{name}Cash withdrawal{amounts}element'
    save_flow(user_dict, 'Cash withdrawal', msg)
    save_log('Cash withdrawal').info(msg)
    db_handle.save_user_info(user_dict)
    return True, f'Cash withdrawal amount{amounts}Yuan, account balance:{user_dict["balance"]}element'

# Call the data processing layer function get ﹣ user ﹣ info to get the user information dictionary amount to get the user's account balance
# Calculate the principal and handling fee of the user's withdrawal amount, and judge whether the principal and handling fee are greater than the account balance
# If it is greater than the account balance, the withdrawal cannot be made, and the prompt information will be returned to the withdrawing user according to the layer
# Otherwise, the withdrawal amount and handling fee shall be deducted from the account balance
# Call the data processing layer save ﹣ user ﹣ info to save the user's information
# Return the withdrawal success information to the user's view layer

Analysis of three-tier architecture of shopping function

Shopping function user view layer: core/shop.py

from core.current_user import login_user
from lib.tools import auth, auto
from conf.settings import GOODS_CATEGOTY
from interface.shop_interface import get_goods_interface, shopping_interface
from interface.shop_interface import put_in_mycart_interface


@auto('E-Shop')
@auth
def shopping():
    print('E-Shop'.center(50, '-'))
    username = login_user[0]
    new_goods = []      # Store the selected products
    while 1:
        for k, v in GOODS_CATEGOTY.items():
            print(f'({k}){v}')

        category = input('Please select item type number(Settlement Y/Sign out Q): ').strip().lower()
        if category == 'y':
            if not new_goods:
                print('You haven't selected any goods this time, so you can't settle')
                continue
            else:
                flag, msg = shopping_interface(username, new_goods)
                print(msg)
                if not flag:
                    put_in_mycart_interface(username, new_goods)
                break

        elif category == 'q':
            if not new_goods: break
            put_in_mycart_interface(username, new_goods)
            break

        if category not in GOODS_CATEGOTY:
            print('The number you selected does not exist, please re select')
            continue

        goods_list = get_goods_interface(GOODS_CATEGOTY[category])
        while 1:
            for index, item in enumerate(goods_list, 1):
                name, price = item
                print(f'{index}: {name}, {price}element')
            choice = input('Please enter the item number(Return B): ').strip().lower()
            if choice == 'b':
                break
            if not choice.isdigit() or int(choice) not in range(1, len(goods_list)+1):
                print('The product number you entered does not exist, please re-enter')
                continue
            name, price = goods_list[int(choice)-1]
            counts = input(f'Please enter purchase{name}Number:').strip()
            if not counts.isdigit() and counts == '0':
                print('The number of goods is a number and cannot be zero')
                continue
            new_goods.append([name, price, int(counts)])

# Shopping function user view layer: users need to log in before using
# Print the commodity classification table, let the user select the classification number, and then pass the classification number to the logical interface layer to obtain the commodity list under the classification and display it to the user.
# The user continues to select the item number and the number of items purchased under the category. Small logic will be used here to determine whether the user's input is legal.
# After selecting goods and the number of goods, the selected results will be temporarily stored in the list new goods for settlement when the user exits.
# If the user chooses to pay, the user name and the product selected by the user will be handed over to the shopping logic interface layer through the shopping structure.
# If the result returned by the logic interface layer is successful in payment, the shopping will be exited; if the result returned is failed in payment, the new goods will be handed over to the put in my cart interface and put into the shopping cart interface.
# If the user chooses to exit, the products of new goods will be directly handed over to the put in MyCart interface and put into the shopping cart interface

Shopping function logic interface layer: Interface / shop menu interface.py

from db import db_handle
from interface.bank_interface import pay_interface
from lib.tools import save_log


def get_goods_interface(category):
    """
    //Get goods by category
    :param category: 
    :return: 
    """
    return db_handle.get_goods_info(category)


def shopping_interface(name, new_goods):
    total_cost = 0
    for item in new_goods:
        *_, price, counts = item
        total_cost += price * counts
    flag = pay_interface(name, total_cost)
    if flag:
        return True, 'Payment succeeded, goods are being delivered....'
    else:
        return False, 'Insufficient account balance, payment failed'


def put_in_mycart_interface(name, new_goods):
    user_dict = db_handle.get_user_info(name)
    my_cart = user_dict.get('my_cart')
    for item in new_goods:
        goods_name, price, counts = item
        if goods_name not in my_cart:
            my_cart[goods_name] = [price, counts]
        else:
            my_cart[goods_name][-1] += counts
    save_log('Daily operation').info(f'{name}Shopping cart items updated')
    db_handle.save_user_info(user_dict)

    
# The shopping interface layer function calculates the total price of the received goods, then calls and summarizes the payment to the bank payment interface.
# The payment interface returns the payment success / failure information. If the payment is successful, the payment success information will be returned to the user view layer. Otherwise, the payment failure information will be returned

# Put it into the shopping cart interface: save the commodities passed from the user stone coating to my cart dictionary in the user information dictionary, and call save user info of the data processing layer to save the user information.

# Get goods interface to receive the goods classification from user view layer. Then return the classification information to the user's view layer

Data processing layer of shopping function: db/db_handle.py

......

from conf.settings import GOODS_DB_FILE

def get_goods_info(category):
    with open(GOODS_DB_FILE, 'rt', encoding='utf-8') as f:
        all_goods_dict =  json.load(f)
        return all_goods_dict.get(category)

# This function is mainly used to receive the commodity classification requested by the get goods interface function of the shopping function logic interface layer, obtain all commodities under the classification and return them to the logic interface layer and then to the user's view layer.

Summary of small knowledge points

Chinese character display of json file

import json
with open(user_file, 'wt', encoding='utf-8') as f:
    json.dump(user_dict, f, ensure_ascii=False)

# Because json serialization is readable serialization, that is, json files store string type data (unlike pickle, which is binary unreadable data).
# In addition, the json file stores unic0de text. That is, if the saved character is noon character, it will be stored as unicode binary data, which looks very uncomfortable in the json file.
# This problem can be solved by using the parameter ensure ﹣ ASCII = false in json.dump, that is, Chinese characters will not be converted to binary bytes

On the decimal point reservation of funds

# This project involves the decimal point retention of user amount data. For accounting and finance, we need to pay great attention to the problem of decimal point reservation. We can't simply use int to transform
# float can't be used to keep floating point because it's not accurate enough and decimal places can't be controlled
# You may say that round(1.2312, 2) can set the decimal point precision, but round(0.00001, 2), the desired result is 0.01 and the result is indeed 0.0

# At this point, you can import the decision module
import decimal
s = decimal.Decimal('0.00001')
print(s, type(s))		# 0.00001 <class 'decimal.Decimal'>
print(s.quantize(decimal.Decimal('0.01'), 'ROUND_UP'))	# 0.01

# Unfortunately, json file is used in this project. It seems that data of type decimal cannot be saved. Get it and turn it into a string. Come back and try again.

re module matching digital application in project

import re
def is_number(your_str):    
    res = re.findall('^\d+\.?\d*$', your_str)
    if res:
        return True
    else:
        return False
    
# Match numbers to determine whether the input string is a non negative number

Password encryption in hash module project

import hashlib

def hash_md5(info):
    m = hashlib.md5()
    m.update(info.encode('utf-8'))
    m.update('Seeing because of believing'.encode('utf-8'))	# Salt treatment
    return m.hexdigest()
# For password encryption

Log in the logging module project

# Use process:
-1 In profile settings.py Configure log dictionary in LOGGING_DIC
-2 stay lib/tools.py Encapsulating logging functions in files,Return logger
def save_log(log_type):
    from conf.settings import LOGGING_DIC
    from logging import config, getLogger

    config.dictConfig(LOGGING_DIC)
    return getLogger(log_type)
-3 Call in logical interface layer save_log Function return logger,Use logger.info(msg)Log

Module import - avoid circular import

#Two ways to avoid circular import
 -Mode 1: if only one function needs to import a custom module, import the module in the function local scope
 -Mode 2: the latter importer uses import instead of from... Import... Import

The bug of adding dictionary automatically to function object

This bug was found in later thinking. This project avoided this bug in the right way. Refer to this blog for specific bug s

#Automatically add function functions to the func ABCD dict dictionary in core.src.
#If you put the func dict dictionary in a separate py file, you can easily avoid this bug
 #The main reason for this bug is: the order of module import and the order of module search

summary

Software development catalog specification

  • Everyone creates catalog specifications in different styles. It doesn't matter. The key is that the organizational structure of the whole project procedure is clear.
  • The directory specification follows the way most people use it, so that your code is readable.

Three level architecture design of the project

  • The three-tier architecture design is an idea of project development. Once this development pattern is established, different levels of functions are distinguished when writing code.
  • In strict accordance with the functions of each level, the codes of different functions are placed at different levels, and do not mess up. In this way, management and maintenance will be very convenient.
  • Sometimes a function is too simple to access the data processing layer directly. But it's better to follow the three-tier architecture design and not cross the logical interface layer.

Storing data is not the purpose, fetching is the purpose

  • Data storage is not the purpose, so the convenience of data retrieval must be considered when data storage.
  • A good data storage structure and way to verify the impact of data access when the function code is concise and beautiful.
  • Program = data structure + algorithm. Therefore, a good data structure makes it difficult and easy to get data function.

Encapsulate code and reuse it as much as possible

  • In the program, the reuse of code should be considered as much as possible without losing the function clarity.
  • Write function tools with general functions, and call them when they are used in the program.

Project source file

Project source file in Baidu online disk, interested friends can download reference.

Links: https://pan.baidu.com/s/1GTL081h64tW2SwsHU8kTGw
Extraction code: fn6e

Tags: Python JSON encoding Database ascii

Posted on Tue, 07 Apr 2020 01:56:46 -0400 by dankstick