Flask_admin—快速搭建访客登记系统Web管理后台

简介:在《App Inventor 2---自制身份证识别及人证比对验证系统》和《MySQL---访客登记系统数据库及Web服务搭建》的基础上,通过在云服务器上的Python程序中使用Flask_admin扩展,快速搭建数据库Web管理后台。通过整合上述实验,了解全栈开发的基本思路。

《App Inventor 2---自制身份证识别及人证比对验证系统》中,我们通过App Inventor 2调用万维易源的API接口,实现了身份证OCR识别和人脸验证比对功能,制作了一个基本的安卓版人证比对验证系统。在《MySQL---访客登记系统数据库及Web服务搭建》中,我们在前期人证比对验证系统的基础上,通过在云服务器上搭建MySQL数据库及相关的Web服务API接口,将人证比对系统数据存储到数据库,从而将人证比对系统拓展为访客登记系统。在本章,我们将在上面两个实验的基础上,通过在云服务器上的Python程序中使用Flask_admin扩展,快速搭建数据库的Web管理后台,实现PC端浏览器对数据库的访问及管理。通过实验,初步了解全栈开发的基本思路,从手机端采集数据,到云端存储数据,再到PC端管理数据的实现路径。

1. 实验目标

(1) 初步了解全栈开发的概念

(2) 学习实践Python的Flask_admin扩展库

(3) 学习实践云服务器的管理使用

(4) 学习实践创建虚拟数据进行模拟测试

2. 实验所需资源

硬件:

(1) 电脑

本实验所用的台式电脑为Windows7操作系统。

(2) 手机

本实验使用手机为安卓系统。

(3) 云服务器(或本地服务器)

本实验使用的是阿里云入门级云服务器,配置为2 vCPU 2 GiB,操作系统为CentOS 7.9。
软件:

(1) 云服务器安装宝塔面板

本实验使用宝塔面板为LNMP环境。

(2) 电脑安装Mind+软件

本实验所用的Mind+软件版本为V1.8.0 RC1.1。

和前两个实验一样,本实验也需要有一定的基础知识,如MySQL数据库的基础知识,Python语言和Flask的基础知识,API接口的概念,Web请求的概念,常见的GET、POST请求的区别,请求和返回的数据类型,比如常见的JSON、字典、base64等数据类型的概念。

3. Python的Flask_admin扩展库简介

在之前的实验中,我们已经开发了采集数据的手机APP,还搭建了存储数据的云服务器MySQL数据库,但目前查看和管理数据还只能通过MySQL数据库管理工具phpMyAdmin进行管理,需要一定的专业知识,并且不利于数据安全,不适合于普通用户。针对此问题,我们需要一个PC浏览器能访问的数据库Web后台管理页面。按此前对Flask库的了解,我们可以通过搭建模型,编写视图函数,设计HTML模板的方式来从零搭建,但这个方法太过于费时费力,有没有更简单的方法?答案是肯定的,本次实验用到的Flask_admin就可以帮助开发者快速生成一个功能强大的数据库Web管理面板,进行数据的增删改查操作。它支持多种数据库和表单,具有高度的可定制性。

4. 在云服务器上的Python程序中添加Flask_admin扩展相关代码

使用宝塔面板登录云服务器后,点击左侧"文件"菜单,在"/www/wwwroot/mysqlapi"路径下找到上个实验创建的"mysqlapi.py"文件,双击打开进行编辑。

如果只是简单的快速创建Web后台,针对这个项目,在导入Flask_admin模块后,只需在代码中添加下面这段代码就可以创建Web后台:

python 复制代码
# 配置 Flask-Admin
admin = Admin(app, name='MyApp', template_mode='bootstrap3')
admin.add_view(ModelView(UserInfo, db.session))
admin.add_view(ModelView(VisitorLog, db.session))

但为了进一步探索和了解Flask_admin的功能,我们在代码中使用了自定义模板、搜索、筛选等实用功能,在上一个项目的基础上,也只添加了少量代码,完整代码如下:

python 复制代码
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import SQLAlchemyError
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_babel import Babel 

app = Flask(__name__)
# 配置 MySQL 数据库连接
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://visitor:你的数据库密码@127.0.0.1/visitor'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# 用户信息表模型
class UserInfo(db.Model):
    __tablename__ = 'user_info'
    identity_number = db.Column(db.String(18), primary_key=True)  # 身份证号码(主键)
    name = db.Column(db.String(50), nullable=False)  # 用户姓名
    sex = db.Column(db.Enum('男', '女'), nullable=False)  # 性别
    birth_date = db.Column(db.Date, nullable=False)  # 出生日期
    address = db.Column(db.String(255))  # 地址
    ethnicity = db.Column(db.String(20))  # 民族
    photo = db.Column(db.LargeBinary)  # 身份证照片
    face_comparison_result = db.Column(db.String(50))  # 人脸比对结果
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())  # 记录创建时间

# 访客登记表模型
class VisitorLog(db.Model):
    __tablename__ = 'visitor_log'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)  # 唯一标识
    visitor_name = db.Column(db.String(50), nullable=False)  # 访客姓名
    visitor_identity_number = db.Column(db.String(18), unique=True, nullable=False)  # 访客身份证号码
    visitor_phone = db.Column(db.String(20))  # 访客电话
    arrival_time = db.Column(db.DateTime, nullable=False)  # 到访时间
    departure_time = db.Column(db.DateTime)  # 离开时间
    visit_purpose = db.Column(db.Text)  # 访问目的
    photo = db.Column(db.LargeBinary)  # 访客照片
    user_identity_number = db.Column(db.String(18), db.ForeignKey('user_info.identity_number'))  # 外键
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())  # 记录创建时间

# 创建数据库表
with app.app_context():
    db.create_all()

babel = Babel(app)

# 自定义 ModelView 以支持中文名称
class UserInfoView(ModelView):
    column_labels = {
        'identity_number': '身份证号码',
        'name': '姓名',
        'sex': '性别',
        'birth_date': '出生日期',
        'address': '地址',
        'ethnicity': '民族',
        'photo': '照片',
        'face_comparison_result': '人脸比对结果',
        'created_at': '创建时间'
    }
    # 设置可以搜索的列
    column_searchable_list = ['identity_number', 'name', 'sex', 'birth_date', 'address', 'ethnicity', 'face_comparison_result']
    # 设置可以筛选的列
    column_filters = ['sex', 'birth_date', 'created_at']

    def __init__(self, session, **kwargs):
        super().__init__(UserInfo, session, name='用户信息', **kwargs)

class VisitorLogView(ModelView):
    column_labels = {
        'id': '编号',
        'visitor_name': '访客姓名',
        'visitor_identity_number': '访客身份证号码',
        'visitor_phone': '访客电话',
        'arrival_time': '到访时间',
        'departure_time': '离开时间',
        'visit_purpose': '访问目的',
        'photo': '访客照片',
        'user_identity_number': '用户身份证号码',
        'created_at': '创建时间'
    }
    # 设置可以搜索的列
    column_searchable_list = ['visitor_name', 'visitor_identity_number', 'visitor_phone', 'arrival_time', 'visit_purpose']
    # 设置可以筛选的列
    column_filters = ['arrival_time', 'departure_time', 'visit_purpose', 'user_identity_number', 'created_at']

    def __init__(self, session, **kwargs):
        super().__init__(VisitorLog, session, name='访客登记', **kwargs)


# 增加 Flask-Admin 管理界面
admin = Admin(app, name='访客登记系统', template_mode='bootstrap3')
admin.add_view(UserInfoView(db.session))
admin.add_view(VisitorLogView(db.session))


# 增加用户信息
@app.route('/user', methods=['POST'])
def add_user():
    try:
        data = request.json
        new_user = UserInfo(
            identity_number=data['identity_number'],
            name=data['name'],
            sex=data['sex'],
            birth_date=data['birth_date'],
            address=data.get('address'),
            ethnicity=data.get('ethnicity'),
            photo=data.get('photo'),
            face_comparison_result=data.get('face_comparison_result')
        )
        db.session.add(new_user)
        db.session.commit()
        return jsonify({"message": "用户信息添加成功"}), 201
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 400

# 查询用户信息
@app.route('/user/<identity_number>', methods=['GET'])
def get_user(identity_number):
    try:
        user = UserInfo.query.get_or_404(identity_number)
        return jsonify({
            "identity_number": user.identity_number,
            "name": user.name,
            "sex": user.sex,
            "birth_date": user.birth_date,
            "address": user.address,
            "ethnicity": user.ethnicity,
            "photo": user.photo,
            "face_comparison_result": user.face_comparison_result,
            "created_at": user.created_at
        })
    except SQLAlchemyError as e:
        return jsonify({"error": str(e)}), 400

# 修改用户信息
@app.route('/user/<identity_number>', methods=['PUT'])
def update_user(identity_number):
    try:
        data = request.json
        user = UserInfo.query.get_or_404(identity_number)
        user.name = data.get('name', user.name)
        user.sex = data.get('sex', user.sex)
        user.birth_date = data.get('birth_date', user.birth_date)
        user.address = data.get('address', user.address)
        user.ethnicity = data.get('ethnicity', user.ethnicity)
        user.photo = data.get('photo', user.photo)
        user.face_comparison_result = data.get('face_comparison_result', user.face_comparison_result)
        db.session.commit()
        return jsonify({"message": "用户信息更新成功"})
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 400

# 删除用户信息
@app.route('/user/<identity_number>', methods=['DELETE'])
def delete_user(identity_number):
    try:
        user = UserInfo.query.get_or_404(identity_number)
        db.session.delete(user)
        db.session.commit()
        return jsonify({"message": "用户信息删除成功"})
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 400

# 查询所有用户信息
@app.route('/users', methods=['GET'])
def get_all_users():
    try:
        users = UserInfo.query.all()
        result = [{
            "identity_number": user.identity_number,
            "name": user.name,
            "sex": user.sex,
            "birth_date": user.birth_date,
            "address": user.address,
            "ethnicity": user.ethnicity,
            "photo": user.photo,
            "face_comparison_result": user.face_comparison_result,
            "created_at": user.created_at
        } for user in users]
        return jsonify(result)
    except SQLAlchemyError as e:
        return jsonify({"error": str(e)}), 400

# 增加访客登记
@app.route('/visitor', methods=['POST'])
def add_visitor():
    try:
        data = request.json
        new_visitor = VisitorLog(
            visitor_name=data['visitor_name'],
            visitor_identity_number=data['visitor_identity_number'],
            visitor_phone=data.get('visitor_phone'),
            arrival_time=data['arrival_time'],
            departure_time=data.get('departure_time'),
            visit_purpose=data.get('visit_purpose'),
            photo=data.get('photo'),
            user_identity_number=data.get('user_identity_number')
        )
        db.session.add(new_visitor)
        db.session.commit()
        return jsonify({"message": "访客登记添加成功"}), 201
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 400

# 查询访客登记
@app.route('/visitor/<int:id>', methods=['GET'])
def get_visitor(id):
    try:
        visitor = VisitorLog.query.get_or_404(id)
        return jsonify({
            "id": visitor.id,
            "visitor_name": visitor.visitor_name,
            "visitor_identity_number": visitor.visitor_identity_number,
            "visitor_phone": visitor.visitor_phone,
            "arrival_time": visitor.arrival_time,
            "departure_time": visitor.departure_time,
            "visit_purpose": visitor.visit_purpose,
            "photo": visitor.photo,
            "user_identity_number": visitor.user_identity_number,
            "created_at": visitor.created_at
        })
    except SQLAlchemyError as e:
        return jsonify({"error": str(e)}), 400

# 修改访客登记
@app.route('/visitor/<int:id>', methods=['PUT'])
def update_visitor(id):
    try:
        data = request.json
        visitor = VisitorLog.query.get_or_404(id)
        visitor.visitor_name = data.get('visitor_name', visitor.visitor_name)
        visitor.visitor_identity_number = data.get('visitor_identity_number', visitor.visitor_identity_number)
        visitor.visitor_phone = data.get('visitor_phone', visitor.visitor_phone)
        visitor.arrival_time = data.get('arrival_time', visitor.arrival_time)
        visitor.departure_time = data.get('departure_time', visitor.departure_time)
        visitor.visit_purpose = data.get('visit_purpose', visitor.visit_purpose)
        visitor.photo = data.get('photo', visitor.photo)
        visitor.user_identity_number = data.get('user_identity_number', visitor.user_identity_number)
        db.session.commit()
        return jsonify({"message": "访客登记更新成功"})
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 400

# 删除访客登记
@app.route('/visitor/<int:id>', methods=['DELETE'])
def delete_visitor(id):
    try:
        visitor = VisitorLog.query.get_or_404(id)
        db.session.delete(visitor)
        db.session.commit()
        return jsonify({"message": "访客登记删除成功"})
    except SQLAlchemyError as e:
        db.session.rollback()
        return jsonify({"error": str(e)}), 400

# 查询所有访客登记
@app.route('/visitors', methods=['GET'])
def get_all_visitors():
    try:
        visitors = VisitorLog.query.all()
        result = [{
            "id": visitor.id,
            "visitor_name": visitor.visitor_name,
            "visitor_identity_number": visitor.visitor_identity_number,
            "visitor_phone": visitor.visitor_phone,
            "arrival_time": visitor.arrival_time,
            "departure_time": visitor.departure_time,
            "visit_purpose": visitor.visit_purpose,
            "photo": visitor.photo,
            "user_identity_number": visitor.user_identity_number,
            "created_at": visitor.created_at
        } for visitor in visitors]
        return jsonify(result)
    except SQLAlchemyError as e:
        return jsonify({"error": str(e)}), 400

if __name__ == '__main__':
    app.run(debug=True)

代码中已经包含详细的功能注释,不再展开说明。相较于简单的快速创建,这里对ModelView进行了自定义,搭配flask_babel翻译模块,实现了表头中文显示,表格内容搜索和筛选等功能。
# 5. 重启网站中的Python项目,根据运行状态修改程序或添加模块

5.1 在上一步骤修改保存"mysqlapi.py"文件后,点击宝塔面板左侧的"网站"菜单,选择"Python项目",重启正在进行的"mysqlapi"项目。

5.2 项目的运行状态可以通过"设置"里的"项目日志"进行查看,根据提示信息,检查修复Python程序。

5.3 如果日志提示缺少相应模块,可以通过项目操作栏的"模块"按钮,添加相应模块。此步骤需要反复操作直到程序正常运行。

6. 通过浏览器访问Web后台

程序正常运行后,在PC上通过浏览器访问"http://. .. (你的云服务器网址):5000/admin"即可登录数据库管理后台。

7. 创建虚拟数据进行模拟测试

至此,一个简单的访客登记系统,包括手机端、云端、PC端都已经具备。完整的测试应该是通过手机拍照识别并保存数据,然后在PC端访问Web后台查看数据。但由于项目的特殊性,需要较多的身份证和真人配合才能获得大量数据,目前暂时不具备这个测试条件。为测试数据库及API接口是否正常,我们将通过一段Python小程序创建虚拟数据进行模拟测试。

7.1 在Mind+(或任意Python IDE)选择"python模式",在"代码"模式下新建一个python程序,名称随意,输入以下代码:

python 复制代码
import requests
import random
from faker import Faker
from datetime import datetime

# 创建 Faker 实例
fake = Faker("zh_CN")

# Flask API 地址
base_url = "http://*.*.*.*(你的服务器地址):5000"

# 生成随机性别
def random_sex():
    return random.choice(["男", "女"])

# 生成用户信息
def generate_user_data():
    identity_number = fake.ssn()
    name = fake.name()
    sex = random_sex()
    birth_date = fake.date_of_birth(minimum_age=18, maximum_age=90).strftime("%Y-%m-%d")
    address = fake.address()
    ethnicity = fake.word(ext_word_list=["汉族", "回族", "藏族", "维吾尔族", "满族", "蒙古族", "壮族"])
    
    photo = None  # 将 photo 字段设置为 None
    
    face_comparison_result = random.choice(["匹配", "不匹配"])
    
    return {
        "identity_number": identity_number,
        "name": name,
        "sex": sex,
        "birth_date": birth_date,
        "address": address,
        "ethnicity": ethnicity,
        "photo": photo,
        "face_comparison_result": face_comparison_result
    }


# 生成访客登记信息
def generate_visitor_data(user_identity_number):
    visitor_name = fake.name()
    visitor_identity_number = fake.unique.ssn()  # 确保唯一性
    visitor_phone = fake.phone_number()
    arrival_time = fake.date_time_this_year(before_now=True, after_now=False).strftime("%Y-%m-%d %H:%M:%S")
    
    # 使用 datetime 对象生成 departure_time,确保格式正确
    departure_time = fake.date_time_between_dates(
        datetime_start=datetime.strptime(arrival_time, "%Y-%m-%d %H:%M:%S"),
        datetime_end=datetime.now()
    ).strftime("%Y-%m-%d %H:%M:%S")
    
    visit_purpose = fake.sentence(nb_words=6)
    
    photo = None  # 将 photo 字段设置为 None

    return {
        "visitor_name": visitor_name,
        "visitor_identity_number": visitor_identity_number,
        "visitor_phone": visitor_phone,
        "arrival_time": arrival_time,
        "departure_time": departure_time,
        "visit_purpose": visit_purpose,
        "photo": photo,
        "user_identity_number": user_identity_number
    }

# 添加用户信息
def add_user(user_data):
    response = requests.post(f"{base_url}/user", json=user_data)
    try:
        user_response = response.json()
    except ValueError:
        print(f"Failed to decode JSON: {response.text}")
        user_response = response.text
    return response.status_code, user_response

# 添加访客登记
def add_visitor(visitor_data):
    response = requests.post(f"{base_url}/visitor", json=visitor_data)
    try:
        visitor_response = response.json()
    except ValueError:
        print(f"Failed to decode JSON: {response.text}")
        visitor_response = response.text
    return response.status_code, visitor_response

# 模拟填充 100 条用户信息和 100 条访客登记信息
for i in range(100):
    try:
        print(f"正在生成第 {i + 1} 条数据")
        # 生成并添加用户信息
        user_data = generate_user_data()
        status_code, user_response = add_user(user_data)
        print(f"添加用户: {user_data['identity_number']}, 状态码: {status_code}, 响应: {user_response}")

        # 生成并添加访客登记信息
        visitor_data = generate_visitor_data(user_data['identity_number'])
        status_code, visitor_response = add_visitor(visitor_data)
        print(f"添加访客: {visitor_data['visitor_identity_number']}, 状态码: {status_code}, 响应: {visitor_response}")
    except Exception as e:
        print(f"在生成第 {i + 1} 条数据时发生错误: {e}")

上面的Python程序通过Faker库生成数据库对应格式的虚拟信息,根据在云服务器上设置的API接口,通过requests库发送HTTP请求,将虚拟信息添加到MySQL数据库。在运行程序前,需要通过"库管理",添加相应库到环境。准备就绪后,运行程序查看效果。

根据提示,程序已成功添加100条信息到云服务器上的MySQL数据库。

7.2 在PC浏览器再次访问"http://. .. (你的云服务器网址):5000/admin"查看虚拟数据是否正常。

100条虚拟数据已成功生成,可以直接在Web界面进行添加、修改、查询、修改操作进一步验证API接口和后台程序是否达到预期。

8.后记

近期的三个实验,搭建了一个包括移动端、云端、PC端的应用系统框架,不过也仅限于了解基本功能,起到学习了解技术栈的作用。每一个部分都还有较大的深化和优化的空间,需要一定的学习成本。但思路理清楚了,知道什么工具能实现什么功能,也可以少走一些学习的弯路。编程学无止境,围绕自己的需求,通过看书、看视频、AI搜索等渠道,不断探索,不断动手实践,总会有所收获。

相关推荐
寻星探路11 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
想用offer打牌12 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
工程师老罗13 小时前
如何在Android工程中配置NDK版本
android
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX14 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
ValhallaCoder14 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端