动机
小伙伴们最近迷恋上羽毛球,组织了个小群,办了公用的运动卡用于开场,考虑不是每次活动都是全员参与,需要一个计费的系统来计算每个人需要交的费用。商讨后决定采用“预充-扣费”的方式,则需要一个系统进行计费和扣费。
技术路线规划
模块名 | 语言 | 备注 |
---|---|---|
管理核心 | Python | 使用JSON存储信息 |
Web后端 | Python | Flask框架 |
Web前端 | HTML | Jinja框架渲染 |
实现
核心模块——用户状态管理
该部分是整个计费系统的核心,用于管理每个用户的余额。使用一个类表示用户,需要的属性为
- 状态列表(用户名,ID,使用次数,余额)
需要的方法有:
- 创建用户(创建新的JSON文件)
- 读取用户状态(从已有的JSON文件中)
- 扣费(使用次数增加1,余额减小)
- 充值(余额增加)
- 保存状态(将现有的状态写入JSON文件)
代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14# -*- 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存在则读取状态,否则创建1
2def UserExsist(self, UserID):
return os.path.exists("./Users/%s.json" % UserID)
判断该ID的JSON文件是否存在1
2
3
4
5
6
7
8
9
10def 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文件1
2
3def LoadUserInfo(self, UserID):
with open("./Users/%s.json" % UserID, "r") as jsonfile:
return json.load(jsonfile)
从JSON文件中载入用户状态1
2
3def PlayOneTime(self, Pay):
self.UserInfo["num"] += 1
self.UserInfo["balance"] = self.UserInfo["balance"] - Pay
扣费,扣除指定的费用并在将扣费次数+11
2def Recharge(self, Pay):
self.UserInfo["balance"] += Pay
充值,费用加上指定值1
2def DeleteUser(self):
os.remove("./Users/%s.json" % self.UserInfo["id"])
删除用户,删除指定的JSON文件1
2
3def 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框架构造,代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from 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()
:返回已经存在的用户状态列表路由部分1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54@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
关系相同运行,监听所有IP,这样在局域网就可以访问了1
app.run(host="0.0.0.0")
Web前端
Web使用HTML代码提供GUI,使用Jinja框架分离数据与模板- index界面用户状态显示,使用for循环生成表格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<!DOCTYPE html>
<html>
<head>
<title>index</title>
</head>
<body>
<div>
<h1>羽毛球运动管理系统</h1>
</div>
<div>
<table border="1">
<thead>
<tr>
<th>用户</th>
<th>次数</th>
<th>余额</th>
</tr>
</thead>
<tbody>
{% for user_id in user_list -%}
<tr>
<td>{{user_list[user_id]["name"]}}</td>
<td>{{user_list[user_id]["num"]}}</td>
<td>{{user_list[user_id]["balance"]}}</td>
</tr>
{%- endfor %}
</tbody>
</table>
</div>超链接部分,用于导航1
2
3
4
5
6
7<div>
<a href="register">register</a>
<a href="recharge">recharge</a>
<a href="pay">pay</a>
</div>
</body>
</html> - register界面使用两个文本输入框表单输入用户名与用户ID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!DOCTYPE html>
<html>
<head>
<title>register</title>
</head>
<body>
<h1>羽毛球运动管理系统--注册</h1>
<div>
<form action="register_handle" method="post" accept-charset="utf-8">
name<input type="text" name="name">
id<input type="text" name="id">
<input type="submit" name="Submit">
</form>
</div>
<a href="/index">back to index</a>
</body>
</html> - recharge界面使用下拉菜单提供可供选择的用户名,文本输入充值金额
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!DOCTYPE html>
<html>
<head>
<title>recharge</title>
</head>
<body>
<div>
<h1>羽毛球运动管理系统--充值</h1>
</div>
<div>
<form action="recharge_handle" method="post" accept-charset="utf-8">
<select name="id">
{% for userid in user_list -%}
<option value="{{userid}}">{{user_list[userid]["name"]}}</option>
{%- endfor %}
</select>
recharge¥<input type="text" name="pay">
<input type="submit" name="Submit">
</form>
</div>
<a href="/index">back to index</a>
</body>
</html> - pay界面使用复选框列出所有用户提供选择,文本输入总输入金额,复选框这种表单数据在后端使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<!DOCTYPE html>
<html>
<head>
<title>pay</title>
</head>
<body>
<h1>羽毛球运动管理系统--消费</h1>
<div>
<form action="pay_handle" method="post" accept-charset="utf-8">
<div>
{%for userid in user_list%}
<input type="checkbox" name="vehicle" value="{{userid}}">{{user_list[userid]["name"]}}<br>
{% endfor %}
</div>
pay¥<input type="text" name="pay">
<input type="submit" name="Submit">
</form>
</div>
<a href="/index">back to index</a>
</body>
</html>request.values.getlist("name")
获取为一个列表 - back界面用户完成充值/注册/消费时用于返回主页
1
2
3
4
5
6
7
8
9<!DOCTYPE html>
<html>
<head>
<title>back</title>
</head>
<body>
<a href="/index">back to index</a>
</body>
</html>