动机
小伙伴们最近迷恋上羽毛球,组织了个小群,办了公用的运动卡用于开场,考虑不是每次活动都是全员参与,需要一个计费的系统来计算每个人需要交的费用。商讨后决定采用“预充-扣费”的方式,则需要一个系统进行计费和扣费。
技术路线规划
模块名 | 语言 | 备注 |
---|---|---|
管理核心 | Python | 使用JSON存储信息 |
Web后端 | Python | Flask框架 |
Web前端 | HTML | Jinja框架渲染 |
实现
核心模块——用户状态管理
该部分是整个计费系统的核心,用于管理每个用户的余额。使用一个类表示用户,需要的属性为
- 状态列表(用户名,ID,使用次数,余额)
需要的方法有:
- 创建用户(创建新的JSON文件)
- 读取用户状态(从已有的JSON文件中)
- 扣费(使用次数增加1,余额减小)
- 充值(余额增加)
- 保存状态(将现有的状态写入JSON文件)
代码如下
# -*- coding: utf-8 -*-
import json
import os
class UserHanlde(object):
"""docstring for UserHanlde"""
def __init__(self, UserID, UserName=""):
super(UserHanlde, self).__init__()
if self.UserExsist(UserID):
self.UserInfo = self.LoadUserInfo(UserID)
else:
self.UserInfo = self.CreateNewUser(UserName, UserID)
构造函数,若该用户ID存在则读取状态,否则创建
def UserExsist(self, UserID):
return os.path.exists("./Users/%s.json" % UserID)
判断该ID的JSON文件是否存在
def CreateNewUser(self, UserName, UserID):
UserInfo = {
"name": UserName,
"id": UserID,
"num": 0,
"balance": 50
}
with open("./Users/%s.json" % UserID, "w") as jsonfile:
json.dump(UserInfo, jsonfile, ensure_ascii=False, indent=4)
return UserInfo
创建新用户,将初始余额设为50并保存JSON文件
def LoadUserInfo(self, UserID):
with open("./Users/%s.json" % UserID, "r") as jsonfile:
return json.load(jsonfile)
从JSON文件中载入用户状态
def PlayOneTime(self, Pay):
self.UserInfo["num"] += 1
self.UserInfo["balance"] = self.UserInfo["balance"] - Pay
扣费,扣除指定的费用并在将扣费次数+1
def Recharge(self, Pay):
self.UserInfo["balance"] += Pay
充值,费用加上指定值
def DeleteUser(self):
os.remove("./Users/%s.json" % self.UserInfo["id"])
删除用户,删除指定的JSON文件
def SaveInfo(self):
with open("./Users/%s.json" % self.UserInfo["id"], "w") as jsonfile:
json.dump(self.UserInfo, jsonfile, ensure_ascii=False, indent=4)
保存状态,将当前状态写入对应的JSON文件
Web后端
web后端使用Python的Flask框架构造,代码如下
from flask import Flask, render_template, request
from UserHanlde import UserHanlde
import os
app = Flask(__name__)
def GetUserIDList():
return [x[:-5] for x in os.listdir("./Users") if ".json" in x]
def GetUserInfoList():
UserInfoList = dict()
for UserID in GetUserIDList():
UserData = UserHanlde(UserID)
UserInfoList[UserID] = UserData.UserInfo
return UserInfoList
常用部分的封装:
-
GetUserIDList()
:返回已经存在的用户ID列表 -
GetUserInfoList()
:返回已经存在的用户状态列表
@app.route("/index")
def ViewInfo():
return render_template("index.html", user_list=GetUserInfoList())
@app.route("/recharge")
def GetReChargeInfo():
return render_template("recharge.html", user_list=GetUserInfoList())
@app.route("/recharge_handle", methods=["GET", "POST"])
def Recharge():
UserID = request.values.get("id")
UserRecharge = request.values.get("pay")
if UserRecharge.isdigit() is True:
UserHanlder = UserHanlde(UserID)
UserHanlder.Recharge(int(UserRecharge))
UserHanlder.SaveInfo()
return render_template("back.html")
else:
return "fail"
@app.route("/register")
def GetRegisterInfo():
return render_template("register.html")
@app.route("/register_handle", methods=["GET", "POST"])
def Register():
UserID = request.values.get("id")
UserName = request.values.get("name")
UserHanlder = UserHanlde(UserID, UserName=UserName)
return render_template("back.html")
@app.route("/pay")
def GetPayName():
return render_template("pay.html", user_list=GetUserInfoList())
@app.route("/pay_handle", methods=["GET", "POST"])
def Pay():
UserIDList = request.values.getlist("vehicle")
UserIDPay = request.values.get("pay")
if UserIDPay.isdigit() is True:
PayNum = int(UserIDPay) / len(UserIDList)
for UserID in UserIDList:
UserHanlder = UserHanlde(UserID)
UserHanlder.PlayOneTime(PayNum)
UserHanlder.SaveInfo()
return render_template("back.html")
else:
return "fail"
路由部分
-
/index
:主页,包括导航和状态显示,所有用户的消费次数和余额将在这里显示 -
/recharge
和/recharge_handle
:充值页面,/recharge
为操作页面,用户在这里填写表单数据,随后表单数据被提交到/recharge_handle
处理充值业务 -
/register
和/register_handle
:注册页面,与/recharge
和/recharge_handle
关系相同 -
/pay
和/pay_handle
:扣费页面,与/recharge
和/recharge_handle
关系相同
app.run(host="0.0.0.0")
运行,监听所有IP,这样在局域网就可以访问了
Web前端
Web使用HTML代码提供GUI,使用Jinja框架分离数据与模板
- index界面
index
羽毛球运动管理系统
用户
次数
余额
{% for user_id in user_list -%}
{{user_list[user_id]["name"]}}
{{user_list[user_id]["num"]}}
{{user_list[user_id]["balance"]}}
{%- endfor %}
用户状态显示,使用for循环生成表格
超链接部分,用于导航
- register界面
register
羽毛球运动管理系统--注册
back to index
使用两个文本输入框表单输入用户名与用户ID
- recharge界面
recharge
羽毛球运动管理系统--充值
back to index
使用下拉菜单提供可供选择的用户名,文本输入充值金额
- pay界面
pay
羽毛球运动管理系统--消费
back to index
使用复选框列出所有用户提供选择,文本输入总输入金额,复选框这种表单数据在后端使用request.values.getlist("name")
获取为一个列表
- back界面
back
back to index
用户完成充值/注册/消费时用于返回主页