完成实验5报告
BIN
Assignments/Assignment5/source/主页.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
527
Assignments/Assignment5/source/作业5_21281280_柯劲帆.md
Normal file
@ -0,0 +1,527 @@
|
||||
<h1><center>课程作业</center></h1>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">课程名称</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">数据库系统原理</span></div>
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">作业次数</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">作业#5</span></div>
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">学号</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">21281280</span></div>
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">姓名</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">柯劲帆</span></div>
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">班级</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">物联网2101班</span></div>
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">指导老师</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">郝爽</span></div>
|
||||
<div><span style="display: inline-block; width: 65px; text-align: center;">修改日期</span><span style="display: inline-block; width: 25px;">:</span><span style="display: inline-block; width: 210px; font-weight: bold; text-align: left;">2024年5月12日</span></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[TOC]
|
||||
|
||||
# 1. 实验案例描述
|
||||
|
||||
本实验我实现的案例是12306系统的用户注册、修改账号信息。
|
||||
|
||||
注册需要用户填写:
|
||||
|
||||
- **用户名**:设置成功后不可修改
|
||||
- **登陆密码**:6-20位字母数字或符号
|
||||
- **确认密码**:须与登陆密码一致
|
||||
- **姓名**
|
||||
- **身份证号**:需满足身份证号格式
|
||||
- **手机号码**:需满足手机号码格式
|
||||
|
||||
支持**注销账号**及修改:
|
||||
|
||||
- **登陆密码**
|
||||
- **手机号码**
|
||||
|
||||
|
||||
|
||||
# 2. 实验过程
|
||||
|
||||
## 2.1. 项目架构设计
|
||||
|
||||
对于前端,我使用 `B/S架构` 和原生的 `HTML5` + `CSS` + `JavaScript` 代码实现。
|
||||
|
||||
对于后端,我使用 `Python` + `Flask`作为Web服务器和应用服务器,并且调用相同服务器中的 `MySQL` 实现任务。
|
||||
|
||||
## 2.2. 项目实现
|
||||
|
||||
对于前后端的代码实现,请见附件。
|
||||
|
||||
配置环境方面,我新建了 `conda` 环境,并使用 `pip` 安装了项目所需依赖,包括 `Flask` 、`PyMySQL` 等。依赖列表我已导出至附件中的 `requirements.txt`。
|
||||
|
||||
另外,为了快速启动服务,我编写了启动脚本 `run.sh`,见附件。
|
||||
|
||||
最终目录树如下:
|
||||
|
||||
```sh
|
||||
$ tree
|
||||
.
|
||||
├── db.sql
|
||||
├── init_db.py
|
||||
├── main.py
|
||||
├── requirements.txt
|
||||
├── run.sh
|
||||
├── static
|
||||
│ ├── css
|
||||
│ │ └── style.css
|
||||
│ └── js
|
||||
│ ├── checkInfo.js
|
||||
│ └── modify.js
|
||||
└── templates
|
||||
├── index.html
|
||||
├── modify.html
|
||||
└── signup.html
|
||||
|
||||
4 directories, 11 files
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 3. 实验结果
|
||||
|
||||
## 3.1. 前端
|
||||
|
||||
本实现实现了 3 个前端页面:
|
||||
|
||||
- 主页
|
||||
- 注册
|
||||
- 修改账号信息
|
||||
|
||||
### 3.1.1. 主页
|
||||
|
||||
主页主要提供 *注册* 和 *修改账号信息* 两个超链接的跳转。
|
||||
|
||||
<img src="主页.png" alt="主页" style="zoom:67%;" />
|
||||
|
||||
### 3.1.2. 注册页面
|
||||
|
||||
<img src="注册页面.png" alt="注册页面" style="zoom: 67%;" />
|
||||
|
||||
该页面将对输入进行如下检查:
|
||||
|
||||
- **检查身份证格式**
|
||||
|
||||
```javascript
|
||||
checkInfo.checkCardCode = function() {
|
||||
let cardCode = document.getElementById('cardCode').value
|
||||
let regexCardCode = /^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
|
||||
if(!regexCardCode.test(cardCode)) {
|
||||
alert('身份证号格式有误')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
如果格式不正确,将弹出警告:
|
||||
|
||||
<img src="身份证格式警告.png" alt="身份证格式警告" style="zoom: 67%;" />
|
||||
|
||||
- **检查手机号格式**
|
||||
|
||||
```javascript
|
||||
checkInfo.checkMobileNo = function() {
|
||||
let mobileNo = document.getElementById('mobileNo').value
|
||||
let regexMobileNo = /^1[3-9]\d{9}$/
|
||||
if (!regexMobileNo.test(mobileNo)) {
|
||||
alert('手机号格式有误')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
如果格式不正确,将弹出警告:
|
||||
|
||||
<img src="手机号格式警告.png" alt="手机号格式警告" style="zoom: 67%;" />
|
||||
|
||||
- **检查密码格式**
|
||||
|
||||
如果格式不正确,将弹出警告:
|
||||
|
||||
<img src="密码格式警告.png" alt="密码格式警告" style="zoom: 67%;" />
|
||||
|
||||
- **检查确认密码是否与第一次输入的密码匹配**
|
||||
|
||||
```javascript
|
||||
checkInfo.checkPassword = function() {
|
||||
let password = document.getElementById('password')
|
||||
let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/
|
||||
if (!regexPassword.test(password.value)) {
|
||||
alert("密码须为长度为6-20位字母、数字或符号")
|
||||
return false
|
||||
}
|
||||
let confirmPassword = document.getElementById('confirmPassword')
|
||||
if (password.value !== confirmPassword.value) {
|
||||
alert("两次输入的密码不一致")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
如果不匹配,将弹出警告:
|
||||
|
||||
<img src="密码不匹配警告.png" alt="密码不匹配警告" style="zoom:67%;" />
|
||||
|
||||
页面会对密码使用 `md5码` 加密传输至后端(效果见 `3.2.1.2. 注册页面请求处理函数` 部分的数据库查询结果截图)。
|
||||
|
||||
```javascript
|
||||
document.getElementById('encryptedPassword').value = md5(
|
||||
document.getElementById('password').value
|
||||
)
|
||||
```
|
||||
|
||||
注册成功后,页面跳转回主页,并弹出注册成功通知:
|
||||
|
||||
<img src="注册成功.png" alt="注册成功" style="zoom:67%;" />
|
||||
|
||||
### 3.1.3. 修改账户信息页面
|
||||
|
||||
修改账户信息页面可以选择三种修改方式,通过点击下拉框即可选择:
|
||||
|
||||
- 删除账户:
|
||||
|
||||
<img src="删除账户界面.png" alt="删除账户界面" style="zoom:67%;" />
|
||||
|
||||
- 修改密码:
|
||||
|
||||
<img src="修改密码界面.png" alt="修改密码界面" style="zoom:67%;" />
|
||||
|
||||
- 修改手机号码:
|
||||
|
||||
<img src="修改手机号界面.png" alt="修改手机号界面" style="zoom:67%;" />
|
||||
|
||||
这里使用了 Javasript 代码操作列表的 `display` 属性,从而控制列表项的显示:
|
||||
|
||||
```javascript
|
||||
modify.showModifyPassword = function() {
|
||||
let modifyType = document.getElementById('modifyType').value
|
||||
let info = {
|
||||
modifyPasswordLis: document.getElementsByClassName('modifyPassword'),
|
||||
modifymobileNoLis: document.getElementsByClassName('modifymobileNo'),
|
||||
}
|
||||
// 遍历隐藏所有元素
|
||||
for (let key in info) {
|
||||
let elements = info[key];
|
||||
for (let item of elements) {
|
||||
item.style.display = 'none'; // 确保所有相关元素被隐藏
|
||||
}
|
||||
}
|
||||
// 根据 modifyType 显示相关元素
|
||||
if (modifyType === "2") {
|
||||
for (let item of info.modifyPasswordLis) {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
} else if (modifyType === "3") {
|
||||
for (let item of info.modifymobileNoLis) {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上述修改都会在后端检查证件号码和登录密码是否匹配(见后端部分实验结果)。
|
||||
|
||||
修改密码将会做密码二次匹配检查、密码格式检查,修改手机号将会做手机号格式化检查。弹出的提示和注册页面提示一样,这里不作赘述。
|
||||
|
||||
## 3.2. 后端
|
||||
|
||||
### 3.2.1. Flask 框架
|
||||
|
||||
Flask 框架负责作为 Web 服务器、应用服务器的实现方式。
|
||||
|
||||
我在代码代码中实现了一个函数,用于连接数据库,以便后续获取 cursor 以及关闭连接。
|
||||
|
||||
```python
|
||||
def get_db():
|
||||
return pymysql.connect(
|
||||
host='localhost', user='kejingfan',
|
||||
password='PASSWORD', database='TESTDB'
|
||||
)
|
||||
```
|
||||
|
||||
对于每一个前端页面,都需要在 Flask 的调用代码中编写一个函数来处理该页面的 GET / POST 请求。
|
||||
|
||||
#### 3.2.1.1. 主页请求处理函数
|
||||
|
||||
主页目前只有 GET 请求,因此只需要返回 `index.html` 即可。
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
```
|
||||
|
||||
#### 3.2.1.2. 注册页面请求处理函数
|
||||
|
||||
注册页面中,收到 GET 请求,返回 `signup.html` ;
|
||||
|
||||
```python
|
||||
@app.route("/signup.html", methods=('GET', 'POST'))
|
||||
def signup():
|
||||
if request.method == 'GET':
|
||||
return render_template('signup.html')
|
||||
```
|
||||
|
||||
收到 POST 请求,需要处理返回的表单。表单一共有 4 项:`'cardCode', 'name', 'mobileNo', 'encryptedPassword'` 。
|
||||
|
||||
```python
|
||||
if request.method == 'POST':
|
||||
id = request.form['cardCode']
|
||||
name = request.form['name']
|
||||
phone_number = request.form['mobileNo']
|
||||
password = request.form['encryptedPassword']
|
||||
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
```
|
||||
|
||||
首先使用身份证号 `'cardCode'` 项,对数据库进行查询,检查用户是否正在重复注册。
|
||||
|
||||
若重复注册,弹出:
|
||||
|
||||
<img src="重复注册警告.png" alt="重复注册警告" style="zoom:67%;" />
|
||||
|
||||
```python
|
||||
sql = """
|
||||
SELECT COUNT(*) FROM passengers \
|
||||
WHERE ID = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (id,))
|
||||
id_exist = cursor.fetchall()[0][0]
|
||||
except Exception as e:
|
||||
flash("数据库异常,查询失败")
|
||||
print(e)
|
||||
return redirect(url_for('signup'))
|
||||
if (id_exist != 0):
|
||||
flash("您已注册过,请勿重复注册")
|
||||
db.close()
|
||||
return redirect(url_for('index'))
|
||||
```
|
||||
|
||||
如果没有检索到已注册信息,则插入注册信息:
|
||||
|
||||
```python
|
||||
sql = '''
|
||||
INSERT INTO passengers (ID, `Name`, Phone_number, `Password`) \
|
||||
VALUES (%s, %s, %s, %s); \
|
||||
'''
|
||||
try:
|
||||
cursor.execute(sql, (id, name, phone_number, password))
|
||||
db.commit()
|
||||
flash("注册成功")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(e)
|
||||
flash("数据库异常,注册失败")
|
||||
db.close()
|
||||
return redirect(url_for('index'))
|
||||
```
|
||||
|
||||
此时数据库可以查询到:
|
||||
|
||||
<img src="新建用户后.png" alt="新建用户后" style="zoom:67%;" />
|
||||
|
||||
#### 3.2.1.3. 修改账户信息页面请求处理函数
|
||||
|
||||
修改账户信息页面中,收到 GET 请求,返回 `modify.html` ;
|
||||
|
||||
```python
|
||||
@app.route("/modify.html", methods=('GET', 'POST'))
|
||||
def modify():
|
||||
if request.method == 'GET':
|
||||
return render_template('modify.html')
|
||||
```
|
||||
|
||||
收到 POST 请求,需要处理返回的表单。表单一共有 5 项:
|
||||
|
||||
`'cardCode', 'encryptedPassword', 'modifyType', ''encryptedNewPassword'', 'mobileNo'` 。
|
||||
|
||||
首先需要验证用户是否存在,且密码正确。这里需要做两个查询操作,分别查询 `ID` 的数量以及 `Password` 的值。
|
||||
|
||||
将验证过程封装成函数:
|
||||
|
||||
```python
|
||||
def verify_user(cursor:Cursor, id:str, password:str) -> str:
|
||||
# 检查已有用户
|
||||
sql = """
|
||||
SELECT COUNT(*) FROM passengers \
|
||||
WHERE ID = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (id,))
|
||||
id_exist = cursor.fetchall()[0][0]
|
||||
except Exception as e:
|
||||
flash("数据库异常,查询失败")
|
||||
print(e)
|
||||
return redirect(url_for('signup'))
|
||||
if (id_exist == 0):
|
||||
return "NO_USER"
|
||||
|
||||
# 检查密码
|
||||
sql = """
|
||||
SELECT `Password` FROM passengers \
|
||||
WHERE ID = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (id,))
|
||||
record_password = cursor.fetchall()[0][0]
|
||||
except Exception as e:
|
||||
flash("数据库异常,查询失败")
|
||||
print(e)
|
||||
return redirect(url_for('modify'))
|
||||
if (record_password != password):
|
||||
return "WRONG_PASSWORD"
|
||||
|
||||
return "USER_VERIFIED"
|
||||
```
|
||||
|
||||
判断验证结果,作出响应:
|
||||
|
||||
```python
|
||||
id = request.form['cardCode']
|
||||
password = request.form['encryptedPassword']
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
verify_info = verify_user(cursor, id, password)
|
||||
if (verify_info == "NO_USER"):
|
||||
flash("您未注册过,无法修改账号")
|
||||
db.close()
|
||||
return redirect(url_for('signup'))
|
||||
elif (verify_info == "WRONG_PASSWORD"):
|
||||
flash("密码错误")
|
||||
db.close()
|
||||
return redirect(url_for('modify'))
|
||||
```
|
||||
|
||||
如果用户没有注册:
|
||||
|
||||
<img src="未注册警告.png" alt="未注册警告" style="zoom:67%;" />
|
||||
|
||||
如果密码错误:
|
||||
|
||||
<img src="密码错误警告.png" alt="密码错误警告" style="zoom:67%;" />
|
||||
|
||||
验证通过后,需要根据请求对数据库进行更改。
|
||||
|
||||
编写了一个类专门用来处理修改内容、数据库修改指令和提示信息:
|
||||
|
||||
```python
|
||||
class ModifyInfo:
|
||||
def __init__(self, form:Dict[str, str]):
|
||||
self.id = form['cardCode']
|
||||
modifyType = form['modifyType']
|
||||
self.new_password = form['encryptedNewPassword']
|
||||
self.phone_number = form['mobileNo']
|
||||
modifyType2command = {
|
||||
'1':'delete account',
|
||||
'2':'modify Password',
|
||||
'3':'modify Phone_Number'
|
||||
}
|
||||
self.sql_dict = {
|
||||
'delete account': 'DELETE FROM passengers WHERE ID = %s;',
|
||||
'modify Password': 'UPDATE passengers SET `Password` = %s WHERE ID = %s;',
|
||||
'modify Phone_Number': 'UPDATE passengers SET Phone_number = %s WHERE ID = %s;'
|
||||
}
|
||||
self.sql_args_dict = {
|
||||
'delete account': (self.id,),
|
||||
'modify Password': (self.new_password, self.id),
|
||||
'modify Phone_Number': (self.phone_number, self.id)
|
||||
}
|
||||
self.ok_message_dict = {
|
||||
'delete account': "删除账户成功",
|
||||
'modify Password': "修改密码成功",
|
||||
'modify Phone_Number': "修改手机号成功"
|
||||
}
|
||||
self.fail_message_dict = {
|
||||
'delete account': "数据库异常,删除账户失败",
|
||||
'modify Password': "数据库异常,修改密码失败",
|
||||
'modify Phone_Number': "数据库异常,修改手机号失败"
|
||||
}
|
||||
self.command = modifyType2command[modifyType]
|
||||
def get_sql(self):
|
||||
return self.sql_dict[self.command]
|
||||
def get_args(self):
|
||||
return self.sql_args_dict[self.command]
|
||||
def get_ok_message(self):
|
||||
return self.ok_message_dict[self.command]
|
||||
def get_fail_message(self):
|
||||
return self.fail_message_dict[self.command]
|
||||
```
|
||||
|
||||
在处理函数中创建该类对象,并调用该对象方法获取操作参数:
|
||||
|
||||
```python
|
||||
modifyInfo = ModifyInfo(request.form)
|
||||
try:
|
||||
cursor.execute(modifyInfo.get_sql(), modifyInfo.get_args())
|
||||
db.commit()
|
||||
flash(modifyInfo.get_ok_message())
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(e)
|
||||
flash(modifyInfo.get_fail_message())
|
||||
db.close()
|
||||
return redirect(url_for('index'))
|
||||
```
|
||||
|
||||
修改密码成功后:
|
||||
|
||||
<img src="密码修改成功.png" alt="密码修改成功" style="zoom:67%;" />
|
||||
|
||||
<img src="数据库中密码变化.png" alt="数据库中密码变化" style="zoom:67%;" />
|
||||
|
||||
修改手机号成功后:
|
||||
|
||||
<img src="手机号修改成功.png" alt="手机号修改成功" style="zoom:67%;" />
|
||||
|
||||
<img src="数据库中手机号变化.png" alt="数据库中手机号变化" style="zoom:67%;" />
|
||||
|
||||
删除账号成功后:
|
||||
|
||||
<img src="删除账号成功.png" alt="删除账号成功" style="zoom:67%;" />
|
||||
|
||||
<img src="数据库中账号信息变化.png" alt="数据库中账号信息变化" style="zoom:67%;" />
|
||||
|
||||
### 3.2.2. 数据库服务器
|
||||
|
||||
本实验使用 MySQL 作为数据库。
|
||||
|
||||
编写了一个 SQL 脚本和一个 Python 文件用于初始化测试数据库:
|
||||
|
||||
```mysql
|
||||
DROP TABLE IF EXISTS passengers;
|
||||
|
||||
CREATE TABLE passengers (
|
||||
ID BIGINT PRIMARY KEY,
|
||||
`Name` VARCHAR (255) NOT NULL,
|
||||
Phone_number BIGINT UNIQUE NOT NULL,
|
||||
`Password` VARCHAR (255) NOT NULL,
|
||||
CHECK (ID REGEXP '^\\d{18}$'),
|
||||
CHECK (Phone_number REGEXP '^\\d{11}$')
|
||||
);
|
||||
```
|
||||
|
||||
```python
|
||||
import pymysql
|
||||
|
||||
db = pymysql.connect(
|
||||
host='localhost', user='kejingfan',
|
||||
password='PASSWORD', database='TESTDB'
|
||||
)
|
||||
|
||||
cursor = db.cursor()
|
||||
|
||||
with open('db.sql', 'r') as f:
|
||||
sql_commands = f.read().split(';')
|
||||
for command in sql_commands:
|
||||
if command.strip(): # 确保不执行空命令
|
||||
cursor.execute(command)
|
||||
|
||||
db.close()
|
||||
```
|
||||
|
BIN
Assignments/Assignment5/source/修改密码界面.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
Assignments/Assignment5/source/修改手机号界面.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
Assignments/Assignment5/source/删除账号成功.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Assignments/Assignment5/source/删除账户界面.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Assignments/Assignment5/source/密码不匹配警告.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
Assignments/Assignment5/source/密码修改成功.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
Assignments/Assignment5/source/密码格式警告.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
Assignments/Assignment5/source/密码错误警告.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Assignments/Assignment5/source/手机号修改成功.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
Assignments/Assignment5/source/手机号格式警告.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Assignments/Assignment5/source/数据库中密码变化.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Assignments/Assignment5/source/数据库中手机号变化.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Assignments/Assignment5/source/数据库中账号信息变化.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
Assignments/Assignment5/source/新建用户后.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
Assignments/Assignment5/source/未注册警告.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Assignments/Assignment5/source/注册成功.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Assignments/Assignment5/source/注册页面.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Assignments/Assignment5/source/身份证格式警告.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Assignments/Assignment5/source/重复注册警告.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
10
Assignments/Assignment5/src/db.sql
Normal file
@ -0,0 +1,10 @@
|
||||
DROP TABLE IF EXISTS passengers;
|
||||
|
||||
CREATE TABLE passengers (
|
||||
ID BIGINT PRIMARY KEY,
|
||||
`Name` VARCHAR (255) NOT NULL,
|
||||
Phone_number BIGINT UNIQUE NOT NULL,
|
||||
`Password` VARCHAR (255) NOT NULL,
|
||||
CHECK (ID REGEXP '^\\d{18}$'),
|
||||
CHECK (Phone_number REGEXP '^\\d{11}$')
|
||||
);
|
17
Assignments/Assignment5/src/init_db.py
Normal file
@ -0,0 +1,17 @@
|
||||
import pymysql
|
||||
|
||||
db = pymysql.connect(
|
||||
host='localhost', user='kejingfan',
|
||||
password='PASSWORD', database='TESTDB'
|
||||
)
|
||||
|
||||
cursor = db.cursor()
|
||||
|
||||
with open('db.sql', 'r') as f:
|
||||
sql_commands = f.read().split(';')
|
||||
for command in sql_commands:
|
||||
if command.strip(): # 确保不执行空命令
|
||||
cursor.execute(command)
|
||||
|
||||
db.close()
|
||||
|
179
Assignments/Assignment5/src/main.py
Normal file
@ -0,0 +1,179 @@
|
||||
from flask import Flask, render_template, request, flash, redirect, url_for
|
||||
import pymysql
|
||||
from pymysql.cursors import Cursor
|
||||
import os
|
||||
from typing import Dict
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.environ.get('SECRET_KEY', 'OPTIONALSECRETKEY')
|
||||
|
||||
|
||||
def get_db():
|
||||
return pymysql.connect(
|
||||
host='localhost', user='kejingfan',
|
||||
password='PASSWORD', database='TESTDB'
|
||||
)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@app.route("/signup.html", methods=('GET', 'POST'))
|
||||
def signup():
|
||||
if request.method == 'GET':
|
||||
return render_template('signup.html')
|
||||
|
||||
if request.method == 'POST':
|
||||
id = request.form['cardCode']
|
||||
name = request.form['name']
|
||||
phone_number = request.form['mobileNo']
|
||||
password = request.form['encryptedPassword']
|
||||
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
# 检查已有用户
|
||||
sql = """
|
||||
SELECT COUNT(*) FROM passengers \
|
||||
WHERE ID = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (id,))
|
||||
id_exist = cursor.fetchall()[0][0]
|
||||
except Exception as e:
|
||||
flash("数据库异常,查询失败")
|
||||
print(e)
|
||||
return redirect(url_for('signup'))
|
||||
if (id_exist != 0):
|
||||
flash("您已注册过,请勿重复注册")
|
||||
db.close()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# 插入
|
||||
sql = '''
|
||||
INSERT INTO passengers (ID, `Name`, Phone_number, `Password`) \
|
||||
VALUES (%s, %s, %s, %s); \
|
||||
'''
|
||||
try:
|
||||
cursor.execute(sql, (id, name, phone_number, password))
|
||||
db.commit()
|
||||
flash("注册成功")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(e)
|
||||
flash("数据库异常,注册失败")
|
||||
db.close()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
def verify_user(cursor:Cursor, id:str, password:str) -> str:
|
||||
# 检查已有用户
|
||||
sql = """
|
||||
SELECT COUNT(*) FROM passengers \
|
||||
WHERE ID = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (id,))
|
||||
id_exist = cursor.fetchall()[0][0]
|
||||
except Exception as e:
|
||||
flash("数据库异常,查询失败")
|
||||
print(e)
|
||||
return redirect(url_for('signup'))
|
||||
if (id_exist == 0):
|
||||
return "NO_USER"
|
||||
|
||||
# 检查密码
|
||||
sql = """
|
||||
SELECT `Password` FROM passengers \
|
||||
WHERE ID = %s;
|
||||
"""
|
||||
try:
|
||||
cursor.execute(sql, (id,))
|
||||
record_password = cursor.fetchall()[0][0]
|
||||
except Exception as e:
|
||||
flash("数据库异常,查询失败")
|
||||
print(e)
|
||||
return redirect(url_for('modify'))
|
||||
if (record_password != password):
|
||||
return "WRONG_PASSWORD"
|
||||
|
||||
return "USER_VERIFIED"
|
||||
|
||||
|
||||
class ModifyInfo:
|
||||
def __init__(self, form:Dict[str, str]):
|
||||
self.id = form['cardCode']
|
||||
modifyType = form['modifyType']
|
||||
self.new_password = form['encryptedNewPassword']
|
||||
self.phone_number = form['mobileNo']
|
||||
modifyType2command = {
|
||||
'1':'delete account',
|
||||
'2':'modify Password',
|
||||
'3':'modify Phone_Number'
|
||||
}
|
||||
self.sql_dict = {
|
||||
'delete account': 'DELETE FROM passengers WHERE ID = %s;',
|
||||
'modify Password': 'UPDATE passengers SET `Password` = %s WHERE ID = %s;',
|
||||
'modify Phone_Number': 'UPDATE passengers SET Phone_number = %s WHERE ID = %s;'
|
||||
}
|
||||
self.sql_args_dict = {
|
||||
'delete account': (self.id,),
|
||||
'modify Password': (self.new_password, self.id),
|
||||
'modify Phone_Number': (self.phone_number, self.id)
|
||||
}
|
||||
self.ok_message_dict = {
|
||||
'delete account': "删除账户成功",
|
||||
'modify Password': "修改密码成功",
|
||||
'modify Phone_Number': "修改手机号成功"
|
||||
}
|
||||
self.fail_message_dict = {
|
||||
'delete account': "数据库异常,删除账户失败",
|
||||
'modify Password': "数据库异常,修改密码失败",
|
||||
'modify Phone_Number': "数据库异常,修改手机号失败"
|
||||
}
|
||||
self.command = modifyType2command[modifyType]
|
||||
def get_sql(self):
|
||||
return self.sql_dict[self.command]
|
||||
def get_args(self):
|
||||
return self.sql_args_dict[self.command]
|
||||
def get_ok_message(self):
|
||||
return self.ok_message_dict[self.command]
|
||||
def get_fail_message(self):
|
||||
return self.fail_message_dict[self.command]
|
||||
|
||||
|
||||
@app.route("/modify.html", methods=('GET', 'POST'))
|
||||
def modify():
|
||||
if request.method == 'GET':
|
||||
return render_template('modify.html')
|
||||
|
||||
if request.method == 'POST':
|
||||
id = request.form['cardCode']
|
||||
password = request.form['encryptedPassword']
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
verify_info = verify_user(cursor, id, password)
|
||||
if (verify_info == "NO_USER"):
|
||||
flash("您未注册过,无法修改账号")
|
||||
db.close()
|
||||
return redirect(url_for('signup'))
|
||||
elif (verify_info == "WRONG_PASSWORD"):
|
||||
flash("密码错误")
|
||||
db.close()
|
||||
return redirect(url_for('modify'))
|
||||
|
||||
modifyInfo = ModifyInfo(request.form)
|
||||
try:
|
||||
cursor.execute(modifyInfo.get_sql(), modifyInfo.get_args())
|
||||
db.commit()
|
||||
flash(modifyInfo.get_ok_message())
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(e)
|
||||
flash(modifyInfo.get_fail_message())
|
||||
db.close()
|
||||
return redirect(url_for('index'))
|
12
Assignments/Assignment5/src/requirements.txt
Normal file
@ -0,0 +1,12 @@
|
||||
blinker==1.8.1
|
||||
cffi==1.16.0
|
||||
click==8.1.7
|
||||
configparser==7.0.0
|
||||
cryptography==42.0.6
|
||||
Flask==3.0.3
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.3
|
||||
MarkupSafe==2.1.5
|
||||
pycparser==2.22
|
||||
PyMySQL==1.1.0
|
||||
Werkzeug==3.0.2
|
1
Assignments/Assignment5/src/run.sh
Normal file
@ -0,0 +1 @@
|
||||
SECRET_KEY='ILOVEDATABASETECH' FLASK_APP=main FLASK_ENV=development flask run
|
9
Assignments/Assignment5/src/static/css/style.css
Normal file
@ -0,0 +1,9 @@
|
||||
/* h1, ul, li, a, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
} */
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
51
Assignments/Assignment5/src/static/js/checkInfo.js
Normal file
@ -0,0 +1,51 @@
|
||||
var checkInfo = {}
|
||||
|
||||
checkInfo.checkCardCode = function() {
|
||||
let cardCode = document.getElementById('cardCode').value
|
||||
let regexCardCode = /^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
|
||||
if(!regexCardCode.test(cardCode)) {
|
||||
alert('身份证号格式有误')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
checkInfo.checkMobileNo = function() {
|
||||
let mobileNo = document.getElementById('mobileNo').value
|
||||
let regexMobileNo = /^1[3-9]\d{9}$/
|
||||
if (!regexMobileNo.test(mobileNo)) {
|
||||
alert('手机号格式有误')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
checkInfo.checkPassword = function() {
|
||||
let password = document.getElementById('password')
|
||||
let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/
|
||||
if (!regexPassword.test(password.value)) {
|
||||
alert("密码须为长度为6-20位字母、数字或符号")
|
||||
return false
|
||||
}
|
||||
let confirmPassword = document.getElementById('confirmPassword')
|
||||
if (password.value !== confirmPassword.value) {
|
||||
alert("两次输入的密码不一致")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
checkInfo.checkNewPassword = function() {
|
||||
let password = document.getElementById('newPassword')
|
||||
let regexPassword = /^[A-Za-z0-9\W_]{6,20}$/
|
||||
if (!regexPassword.test(password.value)) {
|
||||
alert("密码须为长度为6-20位字母、数字或符号")
|
||||
return false
|
||||
}
|
||||
let confirmPassword = document.getElementById('confirmNewPassword')
|
||||
if (password.value !== confirmPassword.value) {
|
||||
alert("两次输入的密码不一致")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
28
Assignments/Assignment5/src/static/js/modify.js
Normal file
@ -0,0 +1,28 @@
|
||||
var modify = {}
|
||||
|
||||
modify.showModifyPassword = function() {
|
||||
let modifyType = document.getElementById('modifyType').value
|
||||
let info = {
|
||||
modifyPasswordLis: document.getElementsByClassName('modifyPassword'),
|
||||
modifymobileNoLis: document.getElementsByClassName('modifymobileNo'),
|
||||
}
|
||||
|
||||
// 遍历隐藏所有元素
|
||||
for (let key in info) {
|
||||
let elements = info[key];
|
||||
for (let item of elements) {
|
||||
item.style.display = 'none'; // 确保所有相关元素被隐藏
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 modifyType 显示相关元素
|
||||
if (modifyType === "2") {
|
||||
for (let item of info.modifyPasswordLis) {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
} else if (modifyType === "3") {
|
||||
for (let item of info.modifymobileNoLis) {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
24
Assignments/Assignment5/src/templates/index.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>我的12306官网</title>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
var message = "";
|
||||
{% for msg in messages %}
|
||||
message += "{{ msg }}\n";
|
||||
{% endfor %}
|
||||
alert(message);
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Coming soon~</h1>
|
||||
<div><a href="signup.html">点击跳转注册</a></div>
|
||||
<div><a href="modify.html">点击跳转修改账号</a></div>
|
||||
</body>
|
||||
</html>
|
96
Assignments/Assignment5/src/templates/modify.html
Normal file
@ -0,0 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>修改账户</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
var message = "";
|
||||
{% for msg in messages %}
|
||||
message += "{{ msg }}\n";
|
||||
{% endfor %}
|
||||
alert(message);
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form action="#" method="post">
|
||||
<ul>
|
||||
<div>
|
||||
<div>证件号码:</div>
|
||||
<input type="text" id="cardCode" name="cardCode" placeholder="请输入您的证件号码" required>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>登陆密码:</div>
|
||||
<input type="password" id="password" placeholder="请输入您的密码" required>
|
||||
<input id="encryptedPassword" name="encryptedPassword" type="hidden">
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>修改内容:</div>
|
||||
<select id="modifyType" name="modifyType" onchange="modify.showModifyPassword()" aria-label="请选择您的修改内容" title="请选择您的修改内容" required>
|
||||
<option value="1">删除账户</option>
|
||||
<option value="2">修改密码</option>
|
||||
<option value="3">修改手机号</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li class="modifyPassword" style="display: none;">
|
||||
<div>
|
||||
<div>修改密码:</div>
|
||||
<input type="password" id="newPassword" placeholder="6-20位字母、数字或符号">
|
||||
<input id="encryptedNewPassword" name="encryptedNewPassword" type="hidden">
|
||||
</div>
|
||||
</li>
|
||||
<li class="modifyPassword" style="display: none;">
|
||||
<div>
|
||||
<div>确认密码:</div>
|
||||
<input type="password" id="confirmPassword" placeholder="再次输入您的修改密码">
|
||||
</div>
|
||||
</li>
|
||||
<li class="modifymobileNo" style="display: none;">
|
||||
<div>
|
||||
<div>修改手机号:</div>
|
||||
<input type="text" id="mobileNo" name="mobileNo" value title="手机号码" aria-label="手机号码" maxlength="11" placeholder="11位手机号">
|
||||
</div>
|
||||
</li>
|
||||
<div>
|
||||
<button type="submit" onclick="return submitForm()">确认</button>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/checkInfo.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/modify.js') }}"></script>
|
||||
<script>
|
||||
function submitForm() {
|
||||
document.getElementById('encryptedPassword').value = md5(
|
||||
document.getElementById('password').value
|
||||
)
|
||||
|
||||
let modifyType = document.getElementById('modifyType').value
|
||||
if (modifyType === '1') {
|
||||
// document.getElementById('newPassword') = ''
|
||||
return true
|
||||
} else if (modifyType == '2') {
|
||||
document.getElementById('encryptedNewPassword').value = md5(
|
||||
document.getElementById('newPassword').value
|
||||
)
|
||||
return checkInfo.checkNewPassword()
|
||||
} else if (modifyType == '3') {
|
||||
return checkInfo.checkMobileNo()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
115
Assignments/Assignment5/src/templates/signup.html
Normal file
@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<title>我的12306注册</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="https://cdn.bootcss.com/blueimp-md5/2.12.0/js/md5.min.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
var message = "";
|
||||
{% for msg in messages %}
|
||||
message += "{{ msg }}\n";
|
||||
{% endfor %}
|
||||
alert(message);
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form action="#" method="post">
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<div>用 户 名:</div>
|
||||
<input type="text" id="name" name="name" placeholder="用户名设置成功后不可修改" required>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>登陆密码:</div>
|
||||
<input type="password" id="password" placeholder="6-20位字母、数字或符号" required>
|
||||
<input id="encryptedPassword" name="encryptedPassword" type="hidden">
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>确认密码:</div>
|
||||
<input type="password" id="confirmPassword" placeholder="再次输入您的登录密码" required>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>证件类型:</div>
|
||||
<input name="cardTypeCode" id="cardTypeCode" value="1" type="hidden">
|
||||
<select id="cardType" aria-label="请选择您的证件类型" title="请选择您的证件类型" required>
|
||||
<option value="1">中国居民身份证</option>
|
||||
<option value="1">港澳台居民身份证</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>姓 名:</div>
|
||||
<input type="text" id="regist_name" name="regist_name" placeholder="请输入姓名" required>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>证件号码:</div>
|
||||
<input type="text" id="cardCode" name="cardCode" placeholder="请输入您的证件号码" maxlength="18" required>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>优惠(待)类型:</div>
|
||||
<select id="passengerType" name="passengerType" aria-label="请选择旅客类型" title="请选择旅客类型" required>
|
||||
<option value="1">成人</option>
|
||||
<option value="2">儿童</option>
|
||||
<option value="3">学生</option>
|
||||
<option value="4">残疾军人</option>
|
||||
</select>
|
||||
</div>
|
||||
</li>
|
||||
<li style="height: 1px;border-top: 1px dashed #DEDEDE;margin: 15px 0;"></li>
|
||||
<li>
|
||||
<div>
|
||||
<div>邮 箱:</div>
|
||||
<input type="text" id="email" name="email" title="请正确填写邮箱地址" placeholder="请正确填写邮箱地址" aria-label="请正确填写邮箱地址" required>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
<div>手机号码:</div>
|
||||
<div>
|
||||
<select id="mobileCode" name="mobileCode" required>
|
||||
<option value="86">+86 中国</option>
|
||||
</select>
|
||||
<input type="text" id="mobileNo" name="mobileNo" value title="手机号码" aria-label="手机号码" maxlength="11" required>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<div>
|
||||
<button type="submit" onclick="return submitForm()">下一步</button>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/checkInfo.js') }}"></script>
|
||||
<script>
|
||||
function submitForm() {
|
||||
if (checkInfo.checkCardCode() && checkInfo.checkMobileNo() && checkInfo.checkPassword()) {
|
||||
document.getElementById('encryptedPassword').value = md5(
|
||||
document.getElementById('password').value
|
||||
)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|