Detailed explanation of ATM + shopping cart project in Python
First of all, we need to know how a project grows from nothing
README.py
Operation requirements: # I Project requirements: simulate and implement an ATM + shopping mall program """ 1. Quota 15000 or customized 2. Realize the shopping mall, add shopping to the shopping cart, and call the credit card interface to check out 3. You can withdraw cash and the handling fee is 5% 4. Support multi account login 5. Support inter account transfer 6. Record monthly daily consumption flow 7. Provide repayment interface 8. ATM Record operation log 9. Provide management interface, including adding account, user quota, freezing account, etc. 10. Decorator for user authentication """ # II The relationship among software development company, customer and user """ Software development company: Help Party A develop software. customer: It refers to customers in some service industries, We need someone to develop some software(first party) user: Party A's software has been launched and provided to us(user)use. """ # III How does a project grow out of nothing? # 1. Demand analysis """ # 1-1. Requirements analysis process 1. Get the project. We will first discuss the requirements with the customer and discuss whether the functions of the project are realized. Cycle and price to get a demand document. 2. Finally, we need to hold a meeting inside the company, and finally get a development document, which can be handed over to programmers in different positions for development. python: back-end, Reptile Different positions: UI Interface design: The layout of the design software will distribute the appearance of the software, cut into pictures and give them to the front end. front end: Get ui Give him the picture, and then go to build a web page, design some places in the page that need to receive data and interact with data. back-end: Directly the core business logic scheduling database for data addition, deletion, query and modification. test: The code will be fully tested, such as stress test and interface test. Operation and maintenance: Deploy the project. # 1-2 requirements document analysis example 1. Quota 15000 or customized Registration function.(After registration, the user has a default quota of 15000): Users can only have a quota after they register. After they register, we can also set a default quota for users. 2. Realize the shopping mall, add shopping to the shopping cart, and call the credit card interface to check out Shopping function: User enters shopping function, First, print the shopping list, list, what can be purchased, and the price of the items, And the number of items. Users need to buy things. The user has selected something. Compared with the user's balance, can the user buy? Is the balance enough? After successful purchase, the user will put it into the shopping cart and save it to the user's personal information. Payment function: Users need to pay after purchasing goods, and the money paid should be deducted from the user's balance. 3. You can withdraw cash and the handling fee is 5% Withdrawal function: Users can withdraw cash, but will charge a handling fee. Here, it involves calculating the user withdrawal handling fee. 4. Support multi account login Login function: It supports multiple accounts, which is reflected by the registration function. Because of registration, there will be account information of multiple users. After the user logs in, we save the user's current status. In other words, the user does not need to enter authentication after logging in temporarily. 5. Support inter account transfer Transfer function: User provided transfer, First, see if the user has this person, If not, it cannot be transferred. If yes, it depends on the user's balance. Whether the user's balance is enough or not is not enough. Then he can't turn. User transfer succeeded, Then we need to go to the user who receives the money. We need to locate the user who receives the money, and then add money to the user who receives the money. Reduce the current amount of money transferred. 6. Record monthly daily consumption flow Record consumption flow function(Set amount): Record the monthly consumption flow of users, Time is divided in months. Count the money paid by users for shopping, Shopping list, number, Statistics user withdrawal, Money transferred. 7. Provide repayment interface Repayment function: Balance to current user, Add. 8. ATM Record operation log Log function: Record all operations after user login. 9. Provide management interface, including adding account, user quota, freezing account, etc Administrator function: Administrators can add accounts. The administrator can set the user's credit limit. Administrators can freeze user accounts. 10. Decorator for user authentication Login authentication device: The control function cannot be executed until the user logs in. # 1-3. Extraction function # Extracted functions 1. Registration function 2. Login function 3. Shopping function 4. Payment function 5. Withdrawal function 6. Transfer function 7. Repayment function 8. Log function 9. Administrator function (Add account, user quota and freeze account) 10. Login authentication decorator 11. Record consumption flow function # The function of "selection and operation" provided to the user 1. register 2. Sign in 3. shopping 4. Withdrawal 5. repayment 6. transfer accounts 7. View amount 8. View shopping cart 9. View consumption flow 10. Administrator function # Functions provided to administrators 1. Modify user quota 2. Add account 3. Freeze account(Take time as the basis for freezing) """ # 2. Software architecture design """ # 2-1. Benefits of programming. 1. have a lucid brain 2. It doesn't appear that writing half the code is overturning rewriting. 3. It is convenient for yourself or future colleagues to maintain better. # 2-2. Benefits of three-tier architecture design. 1. Each function is divided into three parts with clear logic. 2. If users change different user interfaces or different data storage mechanisms, it will not affect the core logic code of the interface layer. Strong scalability. 3. It can accurately record logs and pipelining at the interface layer. # 2-3. Three tier architecture # Note: if there is no software architecture, it may be difficult to expand and maintain if you want to maintain or expand functions at the later stage Three tier architecture: 1. User view layer(view): Provide the function interface for users to select. Sign in, See the interface for registration, etc(front end) 2. Logical interface layer(interface): All core logic is placed in the interface. Provided to the user view layer for use. Receive the user name from the view layer and take it to the third layer for verification. 3. Data processing layer: Receive interface layer. Parameters passed. Return the corresponding data to the interface layer or save the data. Mainly do some data addition, deletion, query and modification. """ # 3. Sub task development """ Sub task development: Multiple synchronous collaborative development projects, Achieve efficient development projects. """ # 4. Test """ Manual test. Automated testing.(Testing with automated scripts) """ # 5. Online operation
1, Detailed explanation of three-tier architecture
2, Development directory structure design
After mastering the three-tier architecture, requirements documents and software architecture, the next step is to establish the development directory
The bin directory is the execution directory of the software
The config directory is the configuration directory of the software
The core directory is the core code directory of the software
db directory is the data storage and data processing directory of the software
The interface directory is the interface layer directory of the software
lib directory is the common module directory of the software
Log directory is the log directory of software execution
README is a text file that introduces and explains a project. It usually needs to contain information about the content of the project
1.bin directory
satrt.py
Run program file, also known as program entry file
import sys,os DER = os.path.dirname(os.path.dirname(__file__)) sys.path.append(DER) # Add environment variable from core import ATM if __name__ == '__main__': # Shopping_center.choice() ATM.run() # pass
2.config directory
Configuration file, which is usually used to place the configuration necessary for starting the program so that it can be called easily in the next step. I also put two files under this folder to record the variable name and its meaning
settings.py
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__))
ATM variable name and its meaning
dis = { 'account': input_add_account, 'account_pwd': input_add_account_pwd, 'limit': 15000, 'state': False, 'bank_flower':[], 'shopping_cart':{} }---------------------------------Initialize dictionary parameters user_data = { 'account':None, Default to None 'is_outh':False Default to False }---------------------------------global variable input_add_account-----------------Registered name input_add_account_pwd-------------Initial password input_again_account_pwd-----------Enter the password again input_account_name----------------Account login name input_account_pwd-----------------Account login password input_withdrawal_money------------Withdrawal amount withdrawal_money------------------%5 Handling charges residue_money---------------------Account balance input_to_user---------------------Account login name of transfer counterpart withdraw_money--------------------Transfer amount to_user_dis-----------------------Opposite account details dictionary from_user_dis---------------------Own account details dictionary payment_dis-----------------------Dictionary of data read by account repayment interface bill_interface_dis----------------Get the dictionary of data read by the bill flow interface repayment_money-------------------repayment/Recharge amount bill_bank_flower------------------Bill flow bank_flower List of account_name----------------------User name in file
Name and meaning of Shopping variable
dic = { '1': ['apple',10], '3': ['tesla',1000], '4': ['mac',3000], '5': ['lenovo',30000], '6': ['chicken',10] }------------------------------------Initial items in the mall input_num----------------------------The serial number of the item being purchased input_count--------------------------Number of goods purchased result1------------------------------Dictionary obtained by reading user data input_name---------------------------Trade name input_price--------------------------Total price of goods dis = {}-----------------------------A dictionary used to store details of a user's purchase result-------------------------------Dictionary obtained by reading user data
core directory
Core directory stores the core code of the software function implementation
ATM.py
""" ATM: Quota 15000 or customized You can withdraw cash and the handling fee is 5% The bill shall be issued on the 22nd of each month, and the 10th of each month shall be the repayment date. If it is overdue, the interest shall be calculated according to 5 / 10000 of the total amount owed per day Support inter account transfer Record monthly daily consumption flow Provide repayment interface ATM Record operation log Provide management interface, including adding account, user quota, freezing account, etc... Shopping function Get mall things from the dictionary (can be added) Put the shopping proceeds into the shopping cart file after purchase shopping_cart in You can add, delete, modify and check the shopping cart files Calculate the accumulated money for shopping (for subsequent interface calls) and deduct it from the amount """ """ User layer """ import os from config import settings from interface import user from lib import common from db.db_handler import operate_file user_data = { 'account':None, 'is_outh':False } """ function: Register function name: Zhang* time: 2021/07/08 param: None return: None """ def add_account(): """ ATM Add account password, user quota and user status Logic: 1.Determine whether the registered name exists in the file 2.Judge whether the passwords entered twice are consistent :return: """ while True: input_add_account = input('Please enter your registered name:').strip() # First judge whether its account exists path = settings.BASE_DIR file_path = os.path.join(path, 'db', 'ATM_userdb', '%s.json' % input_add_account) if os.path.exists(file_path): print('User already exists') continue input_add_account_pwd = input('Please enter the initial password:').strip() input_again_account_pwd = input('Please enter the password again:').strip() if input_add_account_pwd == input_again_account_pwd: # Store user data in json format user.register(input_add_account,input_add_account_pwd) common.get_info('%s login was successful'%input_add_account) print('login was successful') break else: print('The two passwords are inconsistent') return add_account """ function: Login function name: Zhang* time: 2021/07/08 param: None return: None """ def blocked_account(): """ Sign in ATM account Logic: 1.Determine whether this person exists in the file 2.If the password is entered incorrectly three times, freeze the account 3.The initial state determines whether the user is in the locked state, and the locked state does not log in :return: """ while True: input_account_name = input('Please enter your account login name:').strip() # First judge whether its account exists path = settings.BASE_DIR file_path = os.path.join(path, 'db', 'ATM_userdb', '%s.json' %input_account_name) if not os.path.exists(file_path): print('user does not exist') continue # Then judge whether its account is locked res = user.check(input_account_name) # print(res) if res['state'] == True: print('The account has been locked') return count = 0 input_account_pwd = input('Please enter the account login password:').strip() # res = operate_file.read(input_account_name) res = user.check(input_account_name) if input_account_pwd == res['account_pwd']: user_data['account'] = input_account_name user_data['is_outh'] = True common.get_info('%s Login succeeded' % input_account_name) print('Login succeeded') break else: count += 1 print('Incorrect password input') if count == 3: user.change_state(input_account_name) common.get_info('%s Account locked' % input_account_name) print('Account locked') break return blocked_account """ function: Withdrawal function name: Zhang* time: 2021/07/08 param: None return: None """ @common.login def withdraw_deposit(): """ Withdrawal, handling fee 5% Logic: 1.Withdrawal amount cannot be blank 2.The withdrawal amount is greater than zero, 3.Withdrawal amount is less than the remaining amount :return: """ while True: input_withdrawal_money = input('Please enter withdrawal amount:').strip() if not input_withdrawal_money:continue if not input_withdrawal_money.isdigit(): print('Illegal amount entered!') return input_withdrawal_money = int(input_withdrawal_money) # if input_withdrawal_money < 0: # print('input amount is less than 0! ') # continue account_dic = user.check(user_data['account']) if input_withdrawal_money + (input_withdrawal_money *0.05) > account_dic['limit']: print('The withdrawal amount is greater than the remaining amount!') continue user.withdrawal_interface(user_data['account'],input_withdrawal_money) common.get_info('%s Withdrawal succeeded,Withdrawal amount%s' % (user_data['account'],input_withdrawal_money)) print('Withdrawal succeeded') break return withdraw_deposit """ function: Inter account transfer function name: Zhang* time: 2021/07/08 param: None return: None """ @common.login def transfer_accounts(): """ Inter account transfer logic 1. Enter the name of the person to transfer the account and judge whether there is this person 2. Enter the amount of the transfer 3. Judge whether the transfer amount is greater than 0 4. Judge whether the amount transferred exceeds your principal 5. Subtract your own money and increase the other party's money 6. Record flow :return: """ while True: input_to_user = input('Please enter the opposite account name to transfer:').strip() # user.check(input_to_user) path = settings.BASE_DIR file_path = os.path.join(path, 'db', 'ATM_userdb', '%s.json' % input_to_user) if not os.path.exists(file_path): print('Account does not exist, re-enter') continue if input_to_user == user_data['account']: print('You can't transfer money yourself!!!!!') continue withdraw_money = input('Please enter the transfer amount:').strip() if not withdraw_money.isdigit(): print('Enter non numeric') continue withdraw_money = int(withdraw_money) # if withdraw_money < 0: # print('amount input is less than 0 ') # continue to_user_dis = user.check(input_to_user) if withdraw_money > to_user_dis['limit']: print('Transfer amount greater than principal') continue # file_path = os.path.join(path, 'db', 'ATM_userdb', '%s.json' %user_data['account']) # from_user_dis = user.check(user_data['account']) # from_user_dis['limit'] -= withdraw_money # to_user_dis['limit'] += withdraw_money # user.register(input_to_user,to_user_dis['account_pwd']) # user.register(user_data['account'],from_user_dis['account_pwd']) user.account_transfer(withdraw_money,user_data,to_user_dis,input_to_user) common.get_info('Transfer succeeded,%s towards%s transfer accounts%s element' % (user_data['account'],input_to_user,withdraw_money)) print('Transfer succeeded!') break return transfer_accounts """ function: Daily flow record function name: Zhang* time: 2021/07/08 param: None return: None """ @common.login def daily_water(): """ Daily flow record logic: Call the interface to get the bill Get all the flow of the bill :return: """ result = user.bill_interface(user_data) common.get_info('%s View bill flow%s' % (user_data['account'],result)) print('The flow of all your bills is shown below\n',result) return daily_water """ function: Repayment recharge function name: Zhang* time: 2021/07/08 param: None return: None """ @common.login def repayment(): """ Repayment recharge interface logic: The repayment amount must be a pure number The repayment amount shall not be less than 0 Write the repayment amount to the file :return: """ while True: repayment_money = input('Please enter your repayment/Recharge amount:').strip() if not repayment_money.isdigit(): print('Illegal repayment amount entered') continue repayment_money = int(repayment_money) user.payment_interface(user_data,repayment_money) common.get_info('%s repayment/Recharge succeeded,repayment/Recharge amount%s' % (user_data['account'], repayment_money)) print('repayment/Recharge succeeded') break return repayment """ function: ATM Record operation log function name: Zhang* time: 2021/07/08 param: None return: None """ # @common.login. def ATM_Operation_Log(): """ ATM Record operation log Log files placed in log Under folder :return: """ print('This feature has not been developed,Please wait') return ATM_Operation_Log """ function: Shopping function name: Zhang* time: 2021/07/08 param: None return: None """ @common.login def shopping(): """ Shopping function Logic: The format of shopping is dictionary format Shopping cannot be empty Show the shopping list and let them choose Judge whether something exists before purchase, add times if it exists, and add if it does not exist Once you exit, write the shopping log to the log file :return: """ result1 = user.check(user_data['account']) dis = {} while True: dic = { 'T': ['sign out',exit], '1': ['apple',10], '2': ['tesla',1000], '3': ['mac',3000], '4': ['lenovo',30000], '5': ['chicken',10] } for i in dic: print(i,'\t',dic[i][0],dic[i][1]) input_num = input('Please enter the serial number of the item you want to buy:').strip() if not input_num:continue # if input_num not in dic.keys(): # print('wrong or nonexistent serial number ') # continue if input_num.isdigit(): # input_num = int(input_num) input_count = input('Please enter the number of you want to buy:') input_name = dic[input_num][0] input_price = dic[input_num][1] * int(input_count) # Judge whether the goods exist in the file. If they exist, add the number directly. If they do not exist, add them if input_name not in result1['shopping_cart'].keys(): dis[input_name] = {'price':dic[input_num][1],'count':int(input_count)} result1['limit'] -= input_price result1['shopping_cart'].setdefault(input_name, dis[input_name]) operate_file.write(result1) else: result = user.check(user_data['account']) result['shopping_cart'][input_name]['count'] += int(input_count) result['limit'] -= input_price operate_file.write(result) print('Please continue shopping') elif input_num == 'T': # path = settings.BASE_DIR # shopping_file = os.path.join(path,'db','ATM_userdb','%s.json'%user_data['account']) common.get_info('%s Shopping succeeded, purchase details%s' % (user_data['account'], dis)) print('Shopping task program exit') dic[input_num][1]() break else: print('Illegal input') continue def run(): while True: dis = { 'T': ['sign out',exit], '0':['ATM Account registration',add_account], '1':['ATM Account login',blocked_account], '2':['ATM Withdrawal from account',withdraw_deposit], '3':['ATM Account transfer',transfer_accounts], '4':['ATM Account flow',daily_water], '5':['ATM Account repayment',repayment], '6':['ATM Account operation log',ATM_Operation_Log], '7':['shopping',shopping] } for i in dis: print(i,'\t',dis[i][0]) print('-'*20) input_num = input('Please enter the command you want to execute:').strip() if input_num not in dis.keys(): print('Incorrect command input!Please re-enter') continue else: print('You have chosen%s'%dis[input_num][0]) dis[input_num][1]()
db directory
db is the interface for storing user data and data processing
ATM_userdb
- User data location
bobo.json
{ "account": "bobo", "account_pwd": "123456", "limit": 11900, "state": false, "bank_flower": [], "shopping_cart": { "apple": {"price": 20, "count": 2}, "mac": {"price": 3000, "count": 1}, "chicken": {"price": 10, "count": 7} } }
egon.json
{ "account": "egon", "account_pwd": "123456", "limit": 16840.0, "state": false, "bank_flower": ["egon repayment/Recharge 4440"], "shopping_cart": { "apple": {"price": 10, "count": 10}, "mac": {"price": 3000, "count": 2} } }
db_handler
Operate under this directory_ File is the underlying read-write interface for operating user data
operate_file.py
import os,json from config import settings """ Data processing layer """ """ function: Write data underlying function name: Zhang* time: 2021/07/08 param: dis dict Initialize dictionary parameters return: None """ def write(dis):# Write data to file / hard disk for calling path = settings.BASE_DIR file_path = os.path.join(path, 'db', 'ATM_userdb', '%s.json' % dis['account']) with open(file_path, 'w', encoding='utf-8') as f: json.dump(dis, f,ensure_ascii=False) f.flush() """ function: Read data underlying function name: Zhang* time: 2021/07/08 param: account_name string User name in file return: res dict User data dictionary """ def read(account_name): # Read the input from the hard disk for calling path = settings.BASE_DIR file_path = os.path.join(path,'db','ATM_userdb','%s.json' % account_name) with open(file_path, 'r', encoding='utf-8') as f: f.flush() res = json.load(f) return res
interface directory
The interface directory has only one user Py file, which is the logical interface layer, used to undertake the part between the data processing layer and the user layer
user. The PY code is as follows
from db.db_handler import operate_file """ Logical interface layer """ """ function: User registration write data interface call function name: Zhang* time: 2021/07/08 param: input_add_account string Registered name param: input_add_account_pwd string Initial password return: None """ def register(input_add_account,input_add_account_pwd): """ User registration write data call interface :param input_add_account: :param input_add_account_pwd: :return: """ dis = { 'account': input_add_account, 'account_pwd': input_add_account_pwd, 'limit': 15000, 'state': False, 'bank_flower':[], 'shopping_cart':{} } operate_file.write(dis) """ function: Read user data interface call function name: Zhang* time: 2021/07/08 param: input_account_name string Account login name return: None """ def check(account_name): """ Read user data call interface :param input_account_name: :return: """ return operate_file.read(account_name) """ function: Change user state interface call function name: Zhang* time: 2021/07/08 param: input_account_name string Account login name return: None """ def change_state(input_account_name): """ Change user state call interface :param input_account_name: :return: """ res = operate_file.read(input_account_name) res['state'] = True operate_file.write(res) """ function: Withdrawal interface calling function name: Zhang* time: 2021/07/08 param: input_account_name string Account login name param: input_withdrawal_money string Withdrawal amount return: None """ def withdrawal_interface(input_account_name,input_withdrawal_money): """ Withdrawal interface :param input_account_name: :return: """ res = operate_file.read(input_account_name) withdrawal_money = input_withdrawal_money*0.05 residue_money= res['limit'] - withdrawal_money -input_withdrawal_money res['limit'] = residue_money operate_file.write(res) print('Your withdrawal amount is%s'%input_withdrawal_money,'The handling fee is%s'%withdrawal_money,'The remaining amount is%s'%residue_money) res['bank_flower'].append('%s Cash withdrawal%s, The handling fee is%s,The remaining amount is%s' % (res['account'],input_withdrawal_money,withdrawal_money,residue_money)) operate_file.write(res) """ function: Transfer between accounts interface call function name: Zhang* time: 2021/07/08 param: withdraw_money string Transfer amount param: user_data dict global variable param: to_user_dis string Opposite account details dictionary param: input_to_user string Account login name of transfer counterpart return: None """ def account_transfer(withdraw_money,user_data,to_user_dis,input_to_user): """ Transfer interface between accounts :return: """ from_user_dis = operate_file.read(user_data['account']) # print(type(from_user_dis),from_user_dis) from_user_dis['limit'] -= withdraw_money to_user_dis['limit'] += withdraw_money from_user_dis['bank_flower'].append('%s towards%s transfer accounts%s'%(user_data['account'],input_to_user,withdraw_money)) to_user_dis['bank_flower'].append('%s Transfer to you%s'%(user_data['account'],withdraw_money)) operate_file.write(from_user_dis) operate_file.write(to_user_dis) # register(user_data['account'], from_user_dis['account_pwd']) # register(input_to_user, to_user_dis['account_pwd']) """ function: Account repayment interface calling function name: Zhang* time: 2021/07/08 param: user_data dict global variable param: repayment_money int repayment/Recharge amount return: None """ def payment_interface(user_data,repayment_money): """ Account repayment interface :return: """ payment_dis= check(user_data['account']) payment_dis['limit'] += repayment_money payment_dis['bank_flower'].append('%s repayment/Recharge%s'%(user_data['account'],repayment_money)) operate_file.write(payment_dis) """ function: Interface calling function for obtaining bill flow name: Zhang* time: 2021/07/08 param: user_data dict global variable return: bill_bank_flower list Bill flow bank_flower List of """ def bill_interface(user_data): """ Interface for obtaining bill flow :param user_data: :return: """ bill_interface_dis = check(user_data['account']) bill_bank_flower = bill_interface_dis['bank_flower'] return bill_bank_flower
lib directory
This directory stores some public modules, which can be called directly when needed. There is no need to write duplicate code to avoid code redundancy. Common. In this directory Py file, I wrote the login as a decorator, because whether it is transfer or withdrawal, recharge needs to log in first. Therefore, under this premise, logging in and using the decorator is undoubtedly the best choice. The following function functions use the decorator syntax sugar before execution, which is very convenient.
I also added the log function in this file, and every ATM operation will be recorded in the log file
common.py
from core import ATM from config import settings def login(func): def wrapper(*args,**kwargs): """ Login decorator: decorate the login function to facilitate transfer and withdrawal :param args: :param kwargs: :return: """ if ATM.user_data['is_outh']: print('You are already logged in. Please continue') return func(*args,**kwargs) else: print('You are not signed in,Please login') ATM.blocked_account() return wrapper import logging import os # 1: Log configuration logging.basicConfig( # 1. Log output location: 1. Terminal 2. File # filename='access.log', # If not specified, print to the terminal by default filename=os.path.join(settings.BASE_DIR,'log','add_account.log'), # 2. Log format format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', # 3. Time format datefmt='%Y-%m-%d %H:%M:%S %p', # 4. Log level # critical => 50 # error => 40 # warning => 30 # info => 20 # debug => 10 level=10, # Here, I set the log level to "debug" ) def get_info(info): return logging.info(info)
log directory
Under this directory is the log recording each step of ATM operation. The log recording results are as follows
add_account.log
2021-07-09 10:21:58 AM - root - INFO -common: bobo login was successful 2021-07-09 14:53:35 PM - root - INFO -common: bobo Login succeeded 2021-07-09 14:55:04 PM - root - INFO -common: bobo Login succeeded 2021-07-09 14:55:27 PM - root - INFO -common: bobo Shopping succeeded, purchase details{'apple': {'price': 20, 'count': 2}} 2021-07-09 15:46:57 PM - root - INFO -common: bobo Login succeeded 2021-07-09 15:47:52 PM - root - INFO -common: bobo Shopping succeeded, purchase details{'mac': {'price': 3000, 'count': 1}} 2021-07-09 15:50:44 PM - root - INFO -common: egon Login succeeded 2021-07-09 15:54:53 PM - root - INFO -common: egon Login succeeded 2021-07-09 15:55:47 PM - root - INFO -common: egon Shopping succeeded, purchase details{'apple': {'price': 10, 'count': 3}} 2021-07-09 15:59:47 PM - root - INFO -common: liyang Login succeeded 2021-07-09 16:02:16 PM - root - INFO -common: liyang Login succeeded 2021-07-09 16:02:29 PM - root - INFO -common: liyang Shopping succeeded, purchase details{'chicken': {'price': 10, 'count': 7}} 2021-07-09 16:11:32 PM - root - INFO -common: egon Login succeeded 2021-07-09 16:13:22 PM - root - INFO -common: bobo Login succeeded 2021-07-09 16:19:37 PM - root - INFO -common: egon Login succeeded 2021-07-09 16:19:57 PM - root - INFO -common: egon Shopping succeeded, purchase details{'apple': {'price': 10, 'count': 5}} 2021-07-09 16:20:42 PM - root - INFO -common: egon Login succeeded 2021-07-09 16:22:31 PM - root - INFO -common: egon Login succeeded 2021-07-09 16:28:40 PM - root - INFO -common: egon Login succeeded 2021-07-09 16:33:37 PM - root - INFO -common: liyang Login succeeded 2021-07-09 16:33:54 PM - root - INFO -common: liyang Shopping succeeded, purchase details{'mac': {'price': 3000, 'count': 1}} 2021-07-09 16:50:23 PM - root - INFO -common: egon Login succeeded 2021-07-09 16:59:10 PM - root - INFO -common: egon Login succeeded 2021-07-09 17:06:06 PM - root - INFO -common: EGON Login succeeded 2021-07-09 17:06:32 PM - root - INFO -common: EGON Shopping succeeded, purchase details{} 2021-07-09 21:17:04 PM - root - INFO -common: egon Login succeeded