当前位置:   article > 正文

博客管理系统

博客管理系统

需求分析

编写博客系统的目的是为了更加深入的了解项目相关各种命令及程序流程,使自己熟练的掌握一些基础知识

技术需求

开发使用了前后端分离的技术,使用docker部署

  • server : 使用python语言,fastapi库

    • fastapi sqlalchemy aiomysql实现 RESTful API与api文档自动生成,pydantic数据校验
    • 前后端鉴权使用OAuth2 实现 Bearer JWT 令牌
    • slowapi限制api速率
    • Pillow库生成简单验证码,文本文件缓存验证码
  • client :nodejs的前端开发环境

    • vite + ts :前端构建工具

    • vue3 + vue-router + navie-ui : js库,框架

    • openapi-generate-typescript :js库,用于自动生成axios请求代码

    • markdown解析和高亮使用highlight.js,编辑器v-md-editor

  • feature

    • 访问统计
    • 导出excel
    • 图片压缩上传
    • 暗色模式

功能需求分析

系统主要完成以下几方面的功能:

  1. 用户管理:用户的注册登录审核,发表文章和评论。
  2. 文章管理:用户可以在网站中发表删除文章。
  3. 评论管理:用户可以评论文章和回复其他用户的评论并审核。
  4. 标签管理:添加和删除标签,给文章设置标签。
用户管理

用户的相关信息如下:用户ID、用户名、用户密码、用户头像、用户组、状态、注册时间、更新时间。

用户注册时需提供用户名、用户密码。

用户登录时可以采用用户名,密码进行登录。

用户可以发布文章、发表评论、回复。审核、删除自己文章的评论,

文章管理

文章的相关信息如下:文章ID、发布日期、发表用户ID、文章标题、文章内容、状态。

文章可以被普通用户发布、修改、删除和评论,但修改和删除仅限于自己发表的。

文章发布时需要设置标签。

评论管理

评论的相关信息如下:评论ID、评论日期、发表用户ID、评论文章ID、评论内容、父评论ID、状态。

评论可以被用户发表和删除以及被其他用户回复。

标签管理

标签的相关信息如下:标签ID、标签名称

用户发表文章时可以设置标签

系统功能及用例图

普通用户
  • 个人资料管理

    修改头像,修改密码

  • 文章管理

    发表文章,删除文章

  • 评论管理

    发表评论,回复评论,删除自己发表的评论,审核/删除自己文章下的评论

  • 标签管理

    创建标签

管理员

在个人后台和普通用户一致,并且可以访问管理员后台

  • 用户管理

    审核/封禁用户,修改是否可以注册,查看api接口统计

用例图
游客 普通用户 系统 管理员 注册 格式正确 审核 审核通过 成为普通用户 opt [用户注册] 注册 验证用户 登录成功 登录失败 alt [用户登录] [验证通过] [验证失败] 游客 普通用户 系统 管理员
注册
审核
发表文章
登录
评论
审核
添加标签
删除标签
用户
网站
管理员
文章
标签

数据库表设计

表结构

用户表(tb_user)

字段名数据类型说明
idint用户ID,自增主键
usernamevarchar(255)用户名,不可重复
passwordvarchar(255)用户密码,加密存储
avatarvarchar(255)用户头像
group_idint用户组ID,用于权限管理
stateint用户状态ID,0表示未审核,1表示已审核
created_atdatetime注册时间
updated_atdatetime最近更新时间

文章表(tb_post)

字段名数据类型说明
idint文章ID,自增主键
user_idint发表用户ID
titlevarchar(50)文章标题
contenttext文章内容
stateint文章状态ID,0表示未审核,1表示已审核
created_atdatetime发表时间
updated_atdatetime最近更新时间

文章标签关联表(tb_post_tag)

字段名数据类型说明
post_idint文章ID,外键关联tb_post表
tag_idint标签ID,外键关联tb_tag表

评论表(tb_comments)

字段名数据类型说明
idint评论ID,自增主键
post_idint评论文章ID,外键关联tb_post表
parent_idint父评论ID,用于表示评论的层级关系
uidint评论用户ID,外键关联tb_user表
contenttext评论内容
stateint评论状态ID,0表示未审核,1表示已审核
created_atdatetime发表时间

标签表(tb_tag)

字段名数据类型说明
idint标签ID,自增主键
namevarchar(50)标签名称
reference_countint标签被引用次数,用于热门标签的排序

以上是blog数据库的表结构设计,其中表之间通过外键关联实现数据的关联和查询。

ER图

drawSQL-lolik-export-2023-06-17

sql 建表语句

使用orm 模型生成,sql文件是运行后导出的

模型类,每一个类对应一个数据表

from sqlalchemy import Column, String, Integer, DateTime, func, ForeignKey, Text
from sqlalchemy.orm import declarative_base, relationship

Base = declarative_base()


class User(Base):
    __tablename__ = 'tb_user'
    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    username = Column(String(255), unique=True, nullable=False, comment='用户名')
    password = Column(String(255), unique=False, nullable=False, comment='密码')
    avatar = Column(String(255), unique=False, nullable=True, default='', comment='头像')
    group_id = Column(Integer, unique=False, nullable=False, comment='用户组id', default=0)
    state = Column(Integer, unique=False, nullable=False, comment='状态id', default=0)
    created_at = Column(DateTime, default=func.now(), server_default=func.now(), nullable=False, comment='创建时间')
    updated_at = Column(DateTime, default=func.now(), server_default=func.now(), nullable=False, onupdate=func.now(),
                        comment='更新时间')

    under_posts = relationship('Post', back_populates='own_user', cascade="all, delete")  # 用户拥有的文章

    def __repr__(self):
        return f'<User>id={self.id},username={self.username},group_id={self.group_id} ...'

class Tag(Base):
    __tablename__ = 'tb_tag'
    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    reference_count = Column(Integer)

    under_posts = relationship('Post', secondary='tb_post_tag', passive_deletes=True)  # 多对多, tag被多个post拥有

    def __repr__(self):
        return f'<tag> id={self.id},name={self.name}'


class Post(Base):
    __tablename__ = 'tb_post'
    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    user_id = Column(Integer, ForeignKey("tb_user.id"), nullable=False)
    title = Column(String(50), nullable=False)
    content = Column(Text, nullable=False)
    state = Column(Integer, unique=False, nullable=False, comment='状态id', default=0, server_default='0')
    created_at = Column(DateTime, default=func.now(), server_default=func.now(), nullable=False, comment='创建时间')
    updated_at = Column(DateTime, default=func.now(), server_default=func.now(), nullable=False, onupdate=func.now(),
                        comment='更新时间')

    own_user = relationship('User', back_populates='under_posts', passive_deletes=True)  # 文章作者
    own_tags = relationship('Tag', secondary='tb_post_tag', overlaps='under_posts')  # 拥有的tag
    own_comments = relationship("Comment", back_populates="under_post", cascade="all, delete")  # 拥有的评论

    def __repr__(self):
        return f'<post> id={self.id},title={self.title} ...'


class PostTag(Base):  # 中间表
    __tablename__ = 'tb_post_tag'
    post_id = Column(Integer, ForeignKey("tb_post.id"), primary_key=True)
    tag_id = Column(Integer, ForeignKey("tb_tag.id"), primary_key=True)

    def __repr__(self):
        return f'<post_tage> post_id={self.post_id},tag_id={self.tag_id} ...'


class Comment(Base):
    __tablename__ = 'tb_comments'
    id = Column(Integer, primary_key=True)
    post_id = Column(Integer, ForeignKey('tb_post.id'), nullable=False)
    parent_id = Column(Integer, nullable=False)
    uid = Column(Integer, ForeignKey('tb_user.id'), nullable=False)
    content = Column(Text, nullable=False)
    state = Column(Integer, nullable=False, comment='状态id', default=0)
    created_at = Column(DateTime, default=func.now(), server_default=func.now(), nullable=False, comment='创建时间')

    under_post = relationship("Post", back_populates="own_comments", passive_deletes=True)  # 所属文章

    def __repr__(self):
        return f'<Comment> id={self.id},content={self.content} ...'

  • 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
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

建表语句

-- --------------------------------------------------------
-- 主机:                           127.0.0.1
-- 服务器版本:                        8.0.29 - MySQL Community Server - GPL
-- 服务器操作系统:                      Win64
-- HeidiSQL 版本:                  12.3.0.6589
-- --------------------------------------------------------

CREATE DATABASE IF NOT EXISTS `blog`;
USE `blog`;
-- 导出  表 blog.tb_comments 结构
CREATE TABLE IF NOT EXISTS `tb_comments` (
  `id` int NOT NULL AUTO_INCREMENT,
  `post_id` int NOT NULL,
  `parent_id` int NOT NULL,
  `uid` int NOT NULL,
  `content` text NOT NULL,
  `state` int NOT NULL COMMENT '状态id',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `post_id` (`post_id`),
  KEY `uid` (`uid`),
  CONSTRAINT `tb_comments_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `tb_post` (`id`),
  CONSTRAINT `tb_comments_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `tb_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 导出  表 blog.tb_post 结构
CREATE TABLE IF NOT EXISTS `tb_post` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `title` varchar(50) NOT NULL,
  `content` text NOT NULL,
  `state` int NOT NULL DEFAULT '0' COMMENT '状态id',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `ix_tb_post_id` (`id`),
  CONSTRAINT `tb_post_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 导出  表 blog.tb_post_tag 结构
CREATE TABLE IF NOT EXISTS `tb_post_tag` (
  `post_id` int NOT NULL,
  `tag_id` int NOT NULL,
  PRIMARY KEY (`post_id`,`tag_id`),
  KEY `tag_id` (`tag_id`),
  CONSTRAINT `tb_post_tag_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `tb_post` (`id`),
  CONSTRAINT `tb_post_tag_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tb_tag` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 导出  表 blog.tb_tag 结构
CREATE TABLE IF NOT EXISTS `tb_tag` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `reference_count` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `ix_tb_tag_id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- 导出  表 blog.tb_user 结构
CREATE TABLE IF NOT EXISTS `tb_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL COMMENT '用户名',
  `password` varchar(255) NOT NULL COMMENT '密码',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `group_id` int NOT NULL COMMENT '用户组id',
  `state` int NOT NULL COMMENT '状态id',
  `created_at` datetime NOT NULL COMMENT '创建时间',
  `updated_at` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  KEY `ix_tb_user_id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

  • 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
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

代码安全问题检测

使用python库 bandit

Bandit 是一款旨在查找 Python 代码中常见安全问题的工具。为此,Bandit 处理每个文件,从中构建 AST

运行结果

image-20230617163529326

(fastapipg-3.11) 24123@lolik D:\24123\code\python\fastapipg git:use-pdm ~5
> bandit -r app.py sql api config
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.11.1
[node_visitor]  WARNING Unable to find qualified name for module: app.py
Run started:2023-06-17 08:30:52.391282

Test results:
>> Issue: [B324:hashlib] Use of weak MD5 hash for security. Consider usedforsecurity=False
   Severity: High   Confidence: High
   CWE: CWE-327 (https://cwe.mitre.org/data/definitions/327.html)
   More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b324_hashlib.html
   Location: api\userapi.py:144:10
143
144         md5 = hashlib.md5(data).hexdigest()
145         filename = f"{md5}{ext}"

--------------------------------------------------
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
   More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b608_hardcoded_sql_expressions.html
   Location: config\options.py:162:46
161                     sheet = workbook.add_sheet(f'{table_name}', cell_overwrite_ok=True)
162                     result = session.execute(text(f"SELECT * FROM {table_name}"))
163                     columns = (i[0] for i in result.cursor.description)

--------------------------------------------------
>> Issue: [B605:start_process_with_a_shell] Starting a process with a shell, possible injection detected, security issue.
   Severity: High   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b605_start_process_with_a_shell.html
   Location: config\options.py:203:4
202     if not args.vite and args.open:
203         os.system(f'start chrome http://127.0.0.1:{args.port}')
204

--------------------------------------------------
>> Issue: [B605:start_process_with_a_shell] Starting a process with a shell, possible injection detected, security issue.
   Severity: High   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b605_start_process_with_a_shell.html
   Location: config\options.py:208:4
207             f.write(f"export const host:string = 'http://127.0.0.1:{args.port}'")
208         os.system(f'start cmd /k "cd {ppath}static && npm run dev"')
209

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b101_assert_used.html
   Location: sql\crud.py:108:12
107                 u = (await session.execute(select(User).where(User.username == username_new))).scalar_one_or_none()
108                 assert u is not None
109                 return UpdateSuccess.from_User(u, "更新成功")

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b101_assert_used.html
   Location: sql\crud.py:134:12
133                 u = (await session.execute(select(User).where(User.username == user_old.username))).scalar_one_or_none()
134                 assert u is not None
135                 return UpdateSuccess.from_User(u, "更新成功")

--------------------------------------------------

Code scanned:
        Total lines of code: 1604
        Total lines skipped (#nosec): 0

Run metrics:
        Total issues (by severity):
                Undefined: 0
                Low: 2
                Medium: 1
                High: 3
        Total issues (by confidence):
                Undefined: 0
                Low: 1
                Medium: 0
                High: 5
Files skipped (0):
(fastapipg-3.11) 24123@lolik D:\24123\code\python\fastapipg git:use-pdm ~5
>
  • 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
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

问题解析

共有5个问题

  • Issue0

    是说Use of weak MD5 hash for security

    这里把文件md5作为文件名并没有什么问题,还能避免文件重复

  • Issue1

    Possible SQL injection vector through string-based query construction

    存在sql注入,这里的sql语句是写死的字符串,且只会在初始化时运行,用来判断数据库是否存在,若不存在则自动创建

  • Issue2,3

    Starting a process with a shell, possible injection detected

    在开发时用到,生产环境可以直接删除,用来在服务器运行同时打开浏览器,编译前端代码

  • Issue4,5

​ Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

​ 断言应该删除,debug时遗留的问题

界面

登录/注册

image-20230617200611180

首页

image-20230506225558239

文章页

image-20230506225922997

文章编辑

image-20230517165217549

文章管理

image-20230517165507761

管理员后台,api访问统计

image-20230517165410545

api文档

image-20230617201601612

项目代码

代码仓库:https://github.com/2412322029/fastapi_test#readme

/docs 目录为对应api文档

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/144868?site
推荐阅读
相关标签
  

闽ICP备14008679号