超详细!论坛系统核心功能拆解:发帖互动、板块管理、消息通知,技术与体验双视角

admin 3807次浏览

摘要:项目源码:https://gitee.com/zero-one-zsj/forum 1.数据库设计1.1 用户表t_user字段 类型 非空 (Y/N) 主键 (Y/N) 默认值 备注 id bigint Y Y 编号,主键自增 username

项目源码:https://gitee.com/zero-one-zsj/forum

1.数据库设计1.1 用户表t_user字段

类型

非空 (Y/N)

主键 (Y/N)

默认值

备注

id

bigint

Y

Y

编号,主键自增

username

varchar(20)

Y

N

用户名,唯一

password

varchar(32)

Y

N

加密后的密码

nickname

varchar(50)

Y

N

昵称

phoneNum

varchar(20)

N

N

手机号

email

varchar(50)

N

N

电子邮箱

gender

tinyint

Y

N

2

性别 0 女,1 男,2 保密

salt

varchar(32)

Y

N

为密码加盐

avatarUrl

varchar(255)

N

N

用户头像路径

articleCount

int

Y

N

0

发帖数量

isAdmin

tinyint

Y

N

0

是否管理员 0 否,1 是

remark

varchar(1000)

N

N

备注,自我介绍

state

tinyint

Y

N

0

状态 0 正常,1 禁言

deleteState

tinyint

Y

N

0

是否删除,0 否,1 是

createTime

dateTime

Y

N

创建时间,精确到秒

updateTime

dateTime

Y

N

更新时间,精确到秒

代码语言:javascript复制DROP DATABASE IF EXISTS forum_db;

#创建数据库并指定字符集

CREATE DATABASE forum_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

USE forum_db;

#创建用户表

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user(

id BIGINT PRIMARY KEY auto_increment COMMENT '编号,主键自增',

username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名,唯一',

`password` VARCHAR(32) NOT NULL COMMENT '加密后的密码',

nickname VARCHAR(50) NOT NULL COMMENT '昵称',

phoneNum VARCHAR(20) COMMENT '手机号',

email VARCHAR(50) COMMENT '电子邮箱',

gender TINYINT NOT NULL DEFAULT 2 COMMENT '性别 0女,1男,2保密',

salt VARCHAR(32) NOT NULL COMMENT '为密码加盐',

avatarUrl VARCHAR(255) COMMENT '用户头像路径',

avatarCount INT NOT NULL DEFAULT 0 COMMENT '发帖数量',

isAdmin TINYINT NOT NULL DEFAULT 0 COMMENT '是否是管理员 0否,1是',

remark VARCHAR(100) COMMENT '备注,自我介绍',

state TINYINT NOT NULL DEFAULT 0 COMMENT '状态 0正常,1禁言',

deleteState TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除,0否,1是',

createTime DATETIME NOT NULL COMMENT '创建时间,精确到秒',

updateTime DATETIME NOT NULL COMMENT '更新时间,精确到秒'

);1.2 版块表t_board代码语言:javascript复制-- ----------------------------

-- 创建版块表 t_board

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

DROP TABLE IF EXISTS `t_board`;

CREATE TABLE `t_board` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '版块编号,主键,⾃增',

`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '版块名,⾮空',

`articleCount` int(11) NOT NULL DEFAULT 0 COMMENT '帖⼦数量,默认0',

`sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序优先级,升序,默认0,',

`state` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态,0 正常,1禁⽤,默认0',

`deleteState` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否删除 0否,1是,默认0',

`createTime` datetime NOT NULL COMMENT '创建时间,精确到秒,⾮空',

`updateTime` datetime NOT NULL COMMENT '更新时间,精确到秒,⾮空',

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE =

utf8mb4_general_ci COMMENT = '版块表' ROW_FORMAT = Dynamic;1.3 帖子表t_article代码语言:javascript复制DROP TABLE IF EXISTS `t_article`;

CREATE TABLE `t_article` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '帖⼦编号,主键,⾃增',

`boardId` bigint(20) NOT NULL COMMENT '关联板块编号,⾮空',

`userId` bigint(20) NOT NULL COMMENT '发帖⼈,⾮空,关联⽤⼾编号',

`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT

NULL COMMENT '标题,非空,最⼤⻓度100个字符',

`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL

COMMENT '帖⼦正⽂,⾮空',

`visitCount` int(11) NOT NULL DEFAULT 0 COMMENT '访问量,默认0',

`replyCount` int(11) NOT NULL DEFAULT 0 COMMENT '回复数据,默认0',

`likeCount` int(11) NOT NULL DEFAULT 0 COMMENT '点赞数,默认0',

`state` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态 0正常 1 禁⽤,默认0',

`deleteState` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否删除 0 否 1 是,默认0',

`createTime` datetime NOT NULL COMMENT '创建时间,精确到秒,⾮空',

`updateTime` datetime NOT NULL COMMENT '修改时间,精确到秒,⾮空',

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE =

utf8mb4_general_ci COMMENT = '帖⼦表' ROW_FORMAT = Dynamic;1.4 帖子回复表t_article_reply代码语言:javascript复制-- ----------------------------

-- 创建帖⼦回复表 t_article_reply

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

DROP TABLE IF EXISTS `t_article_reply`;

CREATE TABLE `t_article_reply` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号,主键,⾃增',

`articleId` bigint(20) NOT NULL COMMENT '关联帖子编号,⾮空',

`postUserId` bigint(20) NOT NULL COMMENT '楼主⽤⼾,关联⽤⼾编号,⾮空',

`replyId` bigint(20) NULL DEFAULT NULL COMMENT '关联回复编号,⽀持楼中楼',

`replyUserId` bigint(20) NULL DEFAULT NULL COMMENT '楼主下的回复⽤⼾编号,支持楼中楼',

`content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '回贴内容,⻓度500个字符,⾮空',

`likeCount` int(11) NOT NULL DEFAULT 0 COMMENT '点赞数,默认0',

`state` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态 0 正常,1禁⽤,默认0',

`deleteState` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否删除 0否 1是,默认0',

`createTime` datetime NOT NULL COMMENT '创建时间,精确到秒,⾮空',

`updateTime` datetime NOT NULL COMMENT '更新时间,精确到秒,⾮空',

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE =

utf8mb4_general_ci COMMENT = '帖⼦回复表' ROW_FORMAT = Dynamic;1.5 站内信表t_message代码语言:javascript复制-- ----------------------------

-- 创建站内信表 for t_message

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

DROP TABLE IF EXISTS `t_message`;

CREATE TABLE `t_message` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '站内信编号,主键,⾃增',

`postUserId` bigint(20) NOT NULL COMMENT '发送者,并联⽤⼾编号',

`receiveUserId` bigint(20) NOT NULL COMMENT '接收者,并联⽤⼾编号',

`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT

NULL COMMENT '内容,⾮空,⻓度255个字符',

`state` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态 0未读 1已读,默认0',

`deleteState` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否删除 0否,1是,默认0',

`createTime` datetime NOT NULL COMMENT '创建时间,精确到秒,⾮空',

`updateTime` datetime NOT NULL COMMENT '更新时间,精确到秒,⾮空',

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE =

utf8mb4_general_ci COMMENT = '站内信表' ROW_FORMAT = Dynamic;1.6 创建用户表代码语言:javascript复制DROP TABLE IF EXISTS `t_user`;

CREATE TABLE `t_user` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '⽤⼾编号,主键,⾃增',

`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT

NULL COMMENT '⽤⼾名,⾮空,唯⼀',

`password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT

NULL COMMENT '加密后的密码',

`nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT

NULL COMMENT '昵称,⾮空',

`phoneNum` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL

DEFAULT NULL COMMENT '⼿机号',

`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL

DEFAULT NULL COMMENT '邮箱地址',

`gender` tinyint(4) NOT NULL DEFAULT 2 COMMENT '0⼥ 1男 2保密,⾮空,默认2',

`salt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL

COMMENT '为密码加盐,⾮空',

`avatarUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci

NULL DEFAULT NULL COMMENT '⽤⼾头像URL,默认系统图⽚',

`articleCount` int(11) NOT NULL DEFAULT 0 COMMENT '发帖数量,⾮空,默认0',

`isAdmin` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否管理员,0否 1是,默认0',

`remark` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL

DEFAULT NULL COMMENT '备注,⾃我介绍',

`state` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态 0 正常,1 禁⾔,默认0',

`deleteState` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否删除 0否 1是,默认0',

`createTime` datetime NOT NULL COMMENT '创建时间,精确到秒',

`updateTime` datetime NOT NULL COMMENT '更新时间,精确到秒',

PRIMARY KEY (`id`) USING BTREE,

UNIQUE INDEX `user_username_uindex`(`username`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE =

utf8mb4_general_ci COMMENT = '⽤⼾表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;.配置数据源2.1 pom.xml中引用依赖统一管理版本,在properties标签中加入版本号

代码语言:javascript复制

1.8

${java.version}

${java.version}

UTF-8

8.0.33

2.3.0

1.2.16

1.4.1

3.0.0

springfox-boot-starter 是 Spring 生态中用于集成 Swagger 的工具

添加依赖,在dependencies标签中加入相关依赖

代码语言:javascript复制

mysql

mysql-connector-java

${mysql-connector.version}

org.mybatis.spring.boot

mybatis-spring-boot-starter

${mybatis-starter.version}

com.alibaba

druid-spring-boot-starter

${druid-starter.version}

io.springfox

springfox-boot-starter

${springfox-boot-starter.version}

org.springframework.boot

spring-boot-starter-actuator

commons-codec

commons-codec

2.2 application.yml中添加配置在spring节点下添加数据源配置项

代码语言:javascript复制spring:

application:

name: 论坛系统

# 配置数据源

datasource:

url: jdbc:mysql://127.0.0.1:3306/forum_db?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true

username: root

password: jqka

driver-class-name: com.mysql.cj.jdbc.Driver

server:

port: 58080 #自定义服务器端口

logging:

pattern:

dateformat: MM-dd HH:mm:ss #日期显示格式

level:

root: info #日志默认级别

com.example.forum: debug #指定包下的日志级别

file:

path: D:/JavaCode/log2.3 生成类与映射文件2.3.1 pom.xml中引用依赖统一管理版本,在properties标签中加入版本号

代码语言:javascript复制

1.4.1在plugins标签中加入如下配置

代码语言:javascript复制

org.mybatis.generator

mybatis-generator-maven-plugin

${mybatis-generator-plugin-version}

Generate MyBatis Artifacts

deploy

generate

true

true

src/main/resources/mybatis/generatorConfig.xml

2.3.2 创建generatorConfig.xml驱动包:mysql-connector-java-5.1.49.jar

代码语言:javascript复制

PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

connectionURL="jdbc:mysql://127.0.0.1:3306/forum_db?characterEncoding=utf8&useSSL=false"

userId="root"

password="jqka">

enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableCountByExample="false"

enableUpdateByExample="false">

enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableCountByExample="false"

enableUpdateByExample="false">

enableDeleteByPrimaryKey="false" enableCountByExample="false" enableUpdateByExample="false">

enableDeleteByExample="false" enableDeleteByPrimaryKey="false" enableCountByExample="false"

enableUpdateByExample="false">

enableDeleteByPrimaryKey="false" enableCountByExample="false" enableUpdateByExample="false">

2.3.3 运行插件生成文件 在src/main/resources下创建mapper目录

点下下图重新加载Maven项目,在Plugins节点下出现mybatis-generator,双击运行,在对应的目录下生成相应的类与映射文件,如下图所示:

2.3.4 在Insert标签中添加获取主键值的选项代码语言:javascript复制

useGeneratedKeys="true" keyProperty="id" > useGeneratedKeys="true":开启 “自动获取数据库生成的主键” 功能(适用于数据库主键自增的场景,如 MySQL 的 AUTO_INCREMENT)。

keyProperty="id":指定将数据库生成的主键值,回填到 User 实体类的 id 属性中(插入后可直接通过 user.getId() 获取自动生成的主键)。

2.3.5 添加mapper注解dao包下的每个xxxMapper.java加入@Mapper注解(不加也行)

2.3.6 扫描配置 config包下新建MybatisConfig类,指定Mybatis的扫路径

配置 Mapper 扫描路径(如通过 @MapperScan("com.example.forum.dao"))的核心作用是:告诉框架 “去哪里找数据访问接口(Mapper)”,并自动为这些接口创建实现类

核心功能

自动注册 Mapper 接口:通过 @MapperScan 指定路径后,MyBatis 会自动识别该包下的接口为数据访问层(DAO)组件,省去了在每个 Mapper 接口上单独添加 @Mapper 注解的麻烦。

整合 Spring 与 MyBatis:该配置类配合 MyBatis 的 Starter 依赖(如 mybatis-spring-boot-starter),实现两者的无缝集成,使得 Mapper 接口可以通过 @Autowired 直接注入到 Service 层使用。

代码语言:javascript复制package com.example.forum.config;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.context.annotation.Configuration;

/**

* 配置mybatis的扫描路径

*/

@Configuration

@MapperScan("com.example.forum.dao")

public class MyBatisConfig {

}application.yml中加入mybatis配置

代码语言:javascript复制# mybatis 相关配置,单独配置,顶格写

mybatis:

mapper-locations: classpath:mapper/**/*.xml # 指定 xxxMapper.xml的扫描路径3.通过Git推送至远程仓库代码语言:javascript复制# 查看当前状态,列出未修改后添加的文件

git status

# 添加修改后的文件到暂存区,再次运行git status,上⾯的文件会变为绿色显示

git add .

# 提交到本地仓库

git commit -m '第⼀次提交'

# 推送到远程仓库

git push4.编写公共代码4.1 定义状态码对执行业务处理逻辑过程中可能出现的成功与失败状态做针对性描述,用枚举定义状态码,先定义一部分,业务中遇到新的问题再添加

代码语言:javascript复制package com.example.forum.common;

public enum ResultCode {

/** 定义状态码 */

SUCCESS (0, "成功"),

FAILED (1000, "操作失败"),

FAILED_UNAUTHORIZED (1001, "未授权"),

FAILED_PARAMS_VALIDATE (1002, "参数校验失败"),

FAILED_FORBIDDEN (1003, "禁⽌访问"),

FAILED_CREATE (1004, "新增失败"),

FAILED_NOT_EXISTS (1005, "资源不存在"),

AILED_USER_EXISTS (1101, "用户已存在"),

FAILED_USER_NOT_EXISTS (1102, "用户不存在"),

FAILED_LOGIN (1103, "用户名或密码错误"),

FAILED_USER_BANNED (1104, "您已被禁言, 请联系管理员, 并重新登录."),

FAILED_TWO_PWD_NOT_SAME (1105, "两次输的密码不⼀致"),

ERROR_SERVICES (2000, "服务器内部错误"),

ERROR_IS_NULL (2001, "IS NULL.");

//状态码

int code;

//描述信息

String message;

ResultCode(int code, String message) {

this.code = code;

this.message = message;

}

//使用@Getter也可以

public int getCode() {

return code;

}

public String getMessage() {

return message;

}

@Override

public String toString() {

return "code = " + code + ", message = " + message + ". ";

}

}4.2 定义返回结果系统实现前后端分离,统一返回JSON格式的字符串,需要定义一个类,其中包含状态码,描述信息,返回的结果数据

方法签名

作用(什么时候用)

举个实际例子(接口返回)

static AppResult success()

无数据、无自定义消息:只需要告诉前端 “操作成功”(比如删除数据、修改状态成功)

前端调用 “删除用户” 接口,返回:{"code":200, "message":"操作成功", "data":null}

static AppResult success(String message)

无数据、但要自定义提示(比如 “注销账号成功”,而非默认的 “操作成功”)

前端调用 “注销账号” 接口,返回:{"code":200, "message":"注销账号成功", "data":null}

AppResult success(T data)

有数据、用默认消息:需要返回业务数据(比如查询列表、查询详情)

前端调用 “查询用户列表” 接口,返回:{"code":200, "message":"操作成功", "data":[{id:1,name:"张三"}]}

static AppResult success(String message, T data)

有数据、且自定义提示:既要返回数据,又要个性化提示(比如 “查询我的订单成功”)

前端调用 “查询我的订单” 接口,返回:{"code":200, "message":"查询我的订单成功", "data":[{orderId:1,price:100}]}

方法签名

作用(什么时候用)

举个实际例子(接口返回)

static AppResult failed()

无自定义消息:通用失败(比如服务器未知错误,用默认的 “操作失败”)

前端调用 “提交订单” 时数据库报错,返回:{"code":500, "message":"操作失败", "data":null}

static AppResult failed(String message)

自定义失败提示:需要告诉前端具体失败原因(比如 “手机号已被注册”)

前端调用 “注册” 时手机号重复,返回:{"code":500, "message":"手机号已被注册", "data":null}

static AppResult failed(ResultCode resultCode)

用枚举定义的失败场景:统一管理特定失败(比如参数错误、无权限,避免硬编码状态码)

前端调用 “查询用户” 时传错参数,返回:{"code":400, "message":"参数错误", "data":null}(对应 ResultCode.PARAM_ERROR)

4.3 自定义异常ApplicationException 是 项目自定义的 “业务异常类”,专门用于处理 业务逻辑层面的错误(比如 “手机号已注册”“余额不足”“无权限操作” 等),区别于 Java 自带的 “系统异常”(如 NullPointerException 空指针、ArrayIndexOutOfBoundsException 数组越界)。它的核心作用是 “将业务失败信息标准化封装,方便全局统一处理,最终给前端返回一致的错误响应”。

ApplicationException 是业务错误的 “标准化载体”:

它把 “业务失败的状态码、提示、详情” 打包,让错误信息更完整;

它区分了 “业务错” 和 “系统错”,让全局处理更精准;

它配合全局异常处理器,最终实现 “前后端错误响应格式统一”,减少沟通和开发成本。

4.4 全局异常处理使用@ControllerAdvice+@ExceptionHandler注解实现统一异常处理

拦截范围: @ControllerAdvice 会拦截所有标注了 @Controller 或 @RestController 的类中未被手动 try-catch 的异常(即业务代码中向外抛出的异常)。

处理逻辑: 结合 @ExceptionHandler(异常类型.class),可以按异常类型分流处理:

自定义业务异常(如 ApplicationException):返回提前定义的错误信息(如 “用户名已存在”);

系统异常(如 NullPointerException):返回兜底提示(如 “服务异常,请稍后再试”)。

解决的核心问题: 全局化、标准化、解耦化

避免重复 try-catch

响应格式统一

日志与排查更高效

代码语言:javascript复制@Slf4j

@ControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(ApplicationException.class)

@ResponseBody

public AppResult applicationExceptionHandler(ApplicationException e) {

e.printStackTrace();

log.error(e.getMessage());

//优先返回异常中携带的标准化错误结果(ErrorResult)

if (e.getErrorResult() != null) {

// 返回异常类中记录的状态

return e.getErrorResult();

}

//非空校验

//若异常无携带结果,校验异常消息是否为空

if (e.getMessage() == null || "".equals(e.getMessage())) {

//返回默认服务错误

return AppResult.failed(ResultCode.ERROR_SERVICES);

}

// 返回具体异常信息

return AppResult.failed(e.getMessage());

}

@ExceptionHandler(Exception.class)

@ResponseBody

public AppResult exceptionHandler(Exception e) {

//打印异常信息

e.printStackTrace();

//打印日志

log.error(e.getMessage());

//非空判断

if (e.getMessage() == null || "".equals(e.getMessage())) {

return AppResult.failed(ResultCode.ERROR_SERVICES);

}

//返回异常信息

return AppResult.failed(e.getMessage());

}

}4.5 测试异常处理代码语言:javascript复制@GetMapping("/exception")

public String exception() throws Exception {

throw new Exception("抛出exception异常...");

}

@GetMapping("/application")

public String application() {

throw new ApplicationException("抛出applicationException异常...");

}4.6 登录拦截器4.6.1 拦截路径拦截路径是指我们定义的这个拦截器,对哪些请求生效

我们在注册配置拦截器的时候,通过addPathPatterns()方法指定要拦截哪些请求.也可以通过excludePathPatterns()指定不拦截哪些请求.

上述代码中,我们配置的是/**,表示拦截所有的请求。

比如用户登录校验,我们希望可以对除了登录之外所有的路径生效。

代码语言:javascript复制@Configuration

public class AppInterceptorConfigurer implements WebMvcConfigurer {

//注入自定义的登录拦截器

@Resource

private LoginInterceptor loginInterceptor;

@Override

public void addInterceptors(InterceptorRegistry registry) {

// 添加登录拦截器

registry.addInterceptor(loginInterceptor) // 添加⽤⼾登录拦截器

.addPathPatterns("/**") // 拦截所有请求

.excludePathPatterns("/sign-in.html") // 排除登录HTML

.excludePathPatterns("/sign-up.html") // 排除注册HTML

.excludePathPatterns("/user/login") // 排除登录api接⼝

.excludePathPatterns("/user/register") // 排除注册api接⼝

.excludePathPatterns("/user/logout") // 排除退出api接⼝

.excludePathPatterns("/swagger*/**") // 排除登录swagger下所有

.excludePathPatterns("/v3*/**") // 排除登录v3下所有,与 swagger相关

.excludePathPatterns("/dist/**") // 排除所有静态⽂件

.excludePathPatterns("/image/**")

.excludePathPatterns("/**.ico")

.excludePathPatterns("/js/**");

}

}4.6.2 拦截器执行流程 添加拦截器后,执行Controller的方法之前,请求会先被拦截器拦截住.执行preHandle方法, 这个方法需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,继续访问controller中的方法. 如果返回false,则不会放行(controller中的方法也不会执行).

controller当中的方法执行完毕后,再回过来执行postHandle()这个方法以及afterCompletion()方法,执行完毕之后,最终给浏览器响应数据.

代码语言:javascript复制@Component

public class LoginInterceptor implements HandlerInterceptor {

@Value(("${404Lounge.login.url}"))

private String defaultURL;

/**

*

* 前置处理(对请求的预处理)

* @return true: 继续流程 false:流程终止

* @throws Exception

*/

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//获取session对象

HttpSession session = request.getSession();

//判断session是否有效

if(session != null && session.getAttribute(AppConfig.USER_SESSION) != null){

return true;

}

//校验URL是否正确

if(!defaultURL.startsWith("/")) {

defaultURL = "/" + defaultURL;

}

//强制将未登录用户的请求重定向到登录页面

response.sendRedirect(defaultURL);

//中断流程

return false;

}

}4.7 实现API自动生成Swagger简介:Swagger是一套API定义的规范,按照这套规范的要求去定义接口及接口相关信息,

再通过可以解析这套规范工具,就可以生成各种格式的接口文档,以及在线接口调试页面,通过自动

文档的方式,解决了接口文档更新不及时的问题。

Springfox简介:是对Swagger规范解析并生成文档的一个实现。

Springfox文档:http://springfox.github.io/springfox/docs/snapshot/

4.7.1 pom.xml中引用依赖统一管理版本,在properties标签中加入版本号

代码语言:javascript复制3.0.0springfox-boot-starter.version> 引入相关依赖

代码语言:javascript复制

io.springfox

springfox-boot-starter

${springfox-boot-starter.version}

org.springframework.boot

spring-boot-starter-actuator

4.7.2 编写配置类代码语言:javascript复制package com.example.forum.config;

import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;

import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;

import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;

import org.springframework.boot.actuate.endpoint.ExposableEndpoint;

import org.springframework.boot.actuate.endpoint.web.*;

import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;

import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;

import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.env.Environment;

import org.springframework.util.StringUtils;

import springfox.documentation.builders.ApiInfoBuilder;

import springfox.documentation.builders.PathSelectors;

import springfox.documentation.builders.RequestHandlerSelectors;

import springfox.documentation.oas.annotations.EnableOpenApi;

import springfox.documentation.service.ApiInfo;

import springfox.documentation.service.Contact;

import springfox.documentation.spi.DocumentationType;

import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;

import java.util.Collection;

import java.util.List;

/**

* Swagger配置类

*

*/

// 配置类

@Configuration

// 开启Springfox-Swagger

@EnableOpenApi

public class SwaggerConfig {/**

* Springfox-Swagger基本配置

* @return

*/

@Bean

public Docket createApi() {

Docket docket = new Docket(DocumentationType.OAS_30)

.apiInfo(apiInfo())

.select()

.apis(RequestHandlerSelectors.basePackage("com.example.forum.controller"))

.paths(PathSelectors.any())

.build();

return docket;

}

// 配置API基本信息

private ApiInfo apiInfo() {

ApiInfo apiInfo = new ApiInfoBuilder()

.title("404客厅API")

.description("404客厅系统前后端分离API测试")

.contact(new Contact("404Lounge",

"http://62.234.142.221:58080/sign-in.html", "435752097@qq.com"))

.version("1.0")

.build();

return apiInfo;

}

/**

* 解决SpringBoot 6.0以上与Swagger 3.0.0 不兼容的问题

* 复制即可

**/

@Bean

public WebMvcEndpointHandlerMapping

webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,

ServletEndpointsSupplier servletEndpointsSupplier,

ControllerEndpointsSupplier controllerEndpointsSupplier,

EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,

WebEndpointProperties webEndpointProperties, Environment environment) {

List> allEndpoints = new ArrayList();

Collection webEndpoints =

webEndpointsSupplier.getEndpoints();

allEndpoints.addAll(webEndpoints);

allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());

allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());

String basePath = webEndpointProperties.getBasePath();

EndpointMapping endpointMapping = new EndpointMapping(basePath);

boolean shouldRegisterLinksMapping =

this.shouldRegisterLinksMapping(webEndpointProperties, environment,

basePath);

return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints,

endpointMediaTypes,

corsProperties.toCorsConfiguration(), new

EndpointLinksResolver(allEndpoints, basePath),

shouldRegisterLinksMapping, null);

}

private boolean shouldRegisterLinksMapping(WebEndpointProperties

webEndpointProperties, Environment environment,

String basePath) {

return webEndpointProperties.getDiscovery().isEnabled() &&

(StringUtils.hasText(basePath)

||

ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));

}

}4.7.3 application.yml中添加配置在spring节点下添加mvc配置项

由于SpringBoot2.6之后版本把SpringMVC路径匹配策略修改为

MatchingStrategy.PATH_PATTERN_PARSER;

而Springfox-Swagger还没有更新版本,我们暂时先把路径匹配策略回退到之前的

MatchingStrategy.ANT_PATH_MATCHER

代码语言:javascript复制mvc:

pathmatch:

matching-strategy: ANT_PATH_MATCHER #Springfox-Swagger兼容性配置4.7.4 API常见注解4.7.5 修改测试接口代码语言:javascript复制@Api(tags = "测试类相关接口")

@RestController

@RequestMapping("/test")

public class TestController {

@ApiOperation("测试接口1,你好Spring boot")

@GetMapping("/hello")

public String hello() {

return "hello";

}

@ApiOperation("测试接口4,按传入的姓名传入你好信息")

@PostMapping("/helloByName")

public AppResult helloByName(@ApiParam("姓名") @RequestParam("name") String name) {

return AppResult.success("hello " + name);

}

@ApiOperation("测试接口2,显示抛出异常信息")

@GetMapping("/exception")

public String exception() throws Exception {

throw new Exception("抛出exception异常...");

}

@ApiOperation("测试接口3,显示自定义异常信息")

@GetMapping("/application")

public String application() {

throw new ApplicationException("抛出applicationException异常...");

}

}4.7.6 访问API列表4.8 创建工具类4.8.1 创建MD5加密工具类项目中使用commons-codec,它是Apache提供的用于摘要运算、编码解码的工具包。常见的编码解码工具Base64、MD5、HeX、SHA1、DES等。

pom.xml中导入依赖,SpringBoot已经对这个包做了版本管理,所以这里不用指定版本号

代码语言:javascript复制

commons-codec

commons-codec

密码加密流程:

定义一个原始密码

生成盐值(拢动字符)

原密码进行MD5加密 = 密文1

密文1 + 盐值 = 密文2

对密文2进行MD5加密

代码语言:javascript复制public class MD5Util {

//传入原字符串后可以得到一个经过md5加密的密文

/**

* 对字符串进行明文加密

* @param str 明文

* @return 返回密文

*/

public static String md5(String str) {

return DigestUtils.md5Hex(str);

}

/**

* 对用户密码进行加密

* 原始字符串加密后与扰动字符串组合再进⾏⼀次MD5加密

* @param str 密码明文

* @param salt 扰动字符 盐值

* @return 返回密文

*/

public static String md5Salt(String str, String salt) {

return md5(md5(str) + salt);

}

}代码语言:javascript复制String salt = UUIDUtil.UUID_32();

String ciphertext = MD5Util.md5Salt(password, salt);4.8.2 创建UUID工具类UUID(Universally Unique Identifier)是一种 “全球唯一” 的字符串标识(基于时间戳、机器 MAC 地址、随机数等信息生成),几乎不会出现重复。

代码语言:javascript复制package com.example.forum.utils;

import java.util.UUID;

public class UUIDUtil {

/**

* 标准的UUID

* @return

*/

public static String UUID_36() {

return UUID.randomUUID().toString();

}

/**

* 生成一个32位的 UUID 去掉 4 个 -

* @return

*/

public static String UUID_32() {

return UUID.randomUUID().toString().replace("-", "");

}

}5.1 注册(创建普通用户)5.1.1 参数要求参数名

描述

类型

默认值

条件

username

用户名

String

必须

nickname

昵称

String

与用户名相同

必须

password

密码

String

必须

passwordRepeat

确认密码

String

必须,与密码相同

5.1.2 接口规范代码语言:javascript复制// 请求

POST /user/register HTTP/1.1

Content-Type: application/x-www-form-urlencoded

username=user222&nickname=user222&password=123456&passwordRepeat=123456

// 响应

HTTP/1.1 200

Content-Type: application/json

{"code":0,"message":"成功","data":null}5.1.4 实现流程 在Mapper.xml中编写SQL语句 在Mapper.java中定义方法

定义Service接口

实现Service接口 设置默认值 必传参数以外的一些参数要设置默认值

单元测试 密码的密文:对(原始密码+盐值)进行md5加密

Controller实现方法并对外提供API接口

测试API接口

实现前端逻辑,完成前后端交互

5.1.5 API注解和校验注解添加API注解和校验注解

@ApiOperation:生成API时描述方法

@ApiParam:生成API时描述单个参数

@RequestParam:请求时指定参数名

@NonNull:请求时参数不能为空

5.2 登录5.2.1 参数要求 注册时需要用户提交的参数列表

参数名

描述

类型

默认值

条件

username

用户名

String

必须

password

密码

String

必须

5.2.2 接口规范代码语言:javascript复制// 请求

POST /user/login HTTP/1.1

Content-Type: application/x-www-form-urlencoded

username=bitboy&password=123456

// 响应

HTTP/1.1 200

Content-Type: application/json

{"code":0,"message":"成功","data":null}5.2.3 实现流程 在Mapper.xml中编写SQL语句

在Mapper.java中定义方法 1,2自动生成了

定义Service接

根据用户名在数据库中查询用户信息

对密码进行登录校验

实现Service接口

单元测试

Controller实现方法并对外提供API接口

测试API接口

实现前端逻辑,完成前后端交互

设置session的意义:

表示用户已登陆状态

HTTP协议是无状态的(服务器默认是不记得上一次的请求时谁发的),当用户登录成功后,将登陆的User对象存入session,打个标记标识已经登陆了,后续访问当前登录用户下的其他接口时,通过session中是否存在该对象来判断用户是否已登录

方便后续操作获取用户信息

从session中获取之前存入的User对象

实现会话级别的数据共享

session是用户级别的存储空间(每个用户的session都是独立的),登陆后当前用户会话下的所有操作都能共享session中的User数据

5.3 获取用户信息(根据id查询)5.3.1 参数要求参数名

描述

类型

默认值

条件

id

用户 Id

long

可以为空

5.3.2 接口规范代码语言:javascript复制// 请求

GET /user/info HTTP/1.1

GET /user/info?id=1 HTTP/1.1

// 响应

HTTP/1.1 200

Content-type: application/json

//具体的User对象

{

"code": 0,

"message": "成功",

"data": {

"id": 25,

"username": "user223",

"nickname": "user223",

"phoneNum": null,

"email": null,

"gender": 1,

"avatarUrl": null,

"articleCount": 0,

"isAdmin": 0,

"state": 0,

"createTime": "2025-09-08 15:06:10",

"updateTime": "2025-09-08 15:06:10"

}

}5.3.3 实现流程 在Mapper.xml中编写SQL语句

在Mapper.java中定义方法

定义Service接口

实现Service接口

单元测试

Controller实现方法并对外提供API接口

测试API接口

实现前端逻辑,完成前后端交互

修复返回值存在的缺陷

通过观察登录成功的返回结果发现,用户信息中的password,salt,deletState不应该返回给前台,在User类中的对应属性上加@Jsonlgnore注解,可以使对应的字段不参与JSON的序列化修改日期格式为yyyy-MM-dd HH:mm:ss,在application.yml中添加配置

代码语言:javascript复制jackson:

date-format: yyyy-MM-dd HH:mm:ss # 日期格式

default-property-inclusion: NON_NULL # 不为null时序列化5.4 退出登录5.4.1 参数要求无参数要求

实现逻辑

用户访问退出接口

服务器注销Session

返回成功或失败

如果返回成功浏览器跳转到相应页面

结束

退出后跳转到前端登陆界面

5.4.2 接口规范代码语言:javascript复制// 请求

GET http://127.0.0.1:58080/user/logout HTTP/1.1

// 响应

HTTP/1.1 200

Content-Type: application/json

{"code":0,"message":"成功","data":null}5.4.3 实现流程在Contrller中实现销毁Session就完成了用户退出功能,不需要编写Service层代码

5.5 登录拦截器LoginInterceptor,重写preHandle方法 true: 继续流程 false:流程终止

判断session是否有效

没有登陆强制跳转到登陆界面

修改application.yml配置文件,添加跳转页面

代码语言:javascript复制CodeOnHub:

login:

url: sign-in.html # 未登录状况下强制跳转页面AppInterceptorConfigurer 拦截路径

5.6 获取首页板块列表在首页显示的版块信息,可以通过以下两种方式解决

方法一:单独提供查询前N条记录的接口,用来控制首页中版块的个数

方法二:通过配置表中state字段来实现,提供查询所有版块的接口

两种方式任选一个都可以,项目中使用方法一

5.6.1 参数要求无参数要求

实现逻辑:

用户访问首页

服务器查询有效的版块并按排序字段排序

返回版块集合

5.6.2 接口规范代码语言:javascript复制请求 URL:http://127.0.0.1:58080/board/topList

响应状态码:200

Content-Type:application/json

{

"code": 0,

"message": "成功",

"data": [

{

"id": 1,

"name": "Java",

"articleCount": 5,

"sort": 1,

"state": 0,

"createTime": "2023-01-14 11:02:18",

"updateTime": "2023-01-14 11:02:18"

},

{

"id": 2,

"name": "C++",

"articleCount": 1,

"sort": 2,

"state": 0,

"createTime": "2023-01-14 11:02:41",

"updateTime": "2023-01-14 11:02:41"

}

]

}5.6.3 实现流程在Mapper.xml中编写SQL语句

BoardExtMapper.xml

代码语言:javascript复制SELECT * FROM t_board WHERE state = 0 AND deleteState = 0 ORDER BY sort ASC LIMIT 0,9代码语言:javascript复制# 项目自定义相关配置

CodeOnHub:

login:

url: sign-in.html # 未登录状况下强制跳转页面

index: # 首页配置节点

board-num: 9 # 首页中显示的版块个数在Mapper.java中定义方法

代码语言:javascript复制List selectByNum(@Param("num") Integer num);定义Service接口

实现Service接口

单元测试

Controller实现方法并对外提供API接口

测试API接口

实现前端逻辑,完成前后端交互

5.7 获取指定板块信息5.7.1 参数要求参数名

描述

类型

默认值

条件

id

版块 Id

long

必须

5.7.2 接口规范代码语言:javascript复制请求方式:GET

请求 URL:http://127.0.0.1:58080/board/getById

响应状态码:200

Content-Type:application/json

{

"code": 0,

"message": "成功",

"data": {

"id": 1,

"name": "Java",

"articleCount": 5,

"sort": 1,

"state": 0,

"createTime": "2025-01-14 11:02:18",

"updateTime": "2025-01-14 11:02:18"

}

}5.7.3 实现流程 在Mapper.xml中编写SQL语句

在Mapper.java中定义方法 1,2自动生成了

定义Service接口

实现Service接口

单元测试

Controller实现方法并对外提供API接口

测试API接口

实现前端逻辑,完成前后端交互

5.8 发布新帖5.7.1 参数要求参数名

描述

类型

默认值

条件

boardId

版块 Id

long

必须

title

文章标题

String

必须

content

帖子内容

String

必须

5.7.2 接口规范代码语言:javascript复制// 请求

POST http://127.0.0.1:58080/article/create HTTP/1.1

Content-Type: application/x-www-form-urlencoded

boardId=1&title=% E6% B5%8B% E8% AF%95% E6%96% B0% E5% B8%96% E5% AD%90% E6% A0%87% E9% A2%98&content=% E6% B5%8B% E8% AF%95% E6%96% B0% E5% B8%96% E5% AD%90% E5%86%85% E5% AE% B9

// 响应

HTTP/1.1 200

Content-Type: application/json

{"code":0,"message":"成功","data":null}5.7.3 实现流程 在Mapper.xml中编写SQL语句

在Mapper.java中定义方法

定义Service接口

实现Service接口

校验信息将新帖内容写入到文章表t_board中

更新用户发帖数,更新用户表t_user中

更新板块帖子数板块表t_board中

单元测试

Controller实现方法并对外提供API接口

实现前端逻辑,完成前后端交互

5.9 获取帖子列表对应版块中显示的帖子列表以发布时间降序排列,不传入版块Id返回所有帖子

用户点击某个版块或首页时,将版块Id做为参数向服务器发送请求

服务器接收请求,并获取版块Id,查询对应版块下的所有帖子

返回查询结果5.8.1 参数要求参数名

描述

类型

默认值

条件

boardId

版块 Id

Long

可为空

5.8.2 接口规范代码语言:javascript复制// 请求

// 返回指定版块下的帖子列表

GET http://127.0.0.1:58080/article/getAllByBoardId?boardId=1 HTTP/1.1

// 返回所有的帖子列表

GET http://127.0.0.1:58080/article/getAllByBoardId HTTP/1.1

// 响应

HTTP/1.1 200

Content-Type: application/json

{

"code": 0,

"message": "成功",

"data": [

{

"id": 17,

"boardId": 1,

"userId": 1,

"title": "测试删除",

"visitCount": 8,

"replyCount": 1,

"likeCount": 1,

"state": 0,

"createTime": "2025-07-05 04:10:46",

"updateTime": "2025-07-05 11:22:43",

"board": {

"id": 1,

"name": "Java"

},

"user": {

"id": 1,

"nickname": "bitboy",

"phoneNum": null,

"email": null,

"gender": 1,

"avatarUrl": null

},

"own": false

}5.8.3 实现流程 在Mapper.xml中编写SQL语句 在Mapper.java中定义方法

定义Service接口

实现Service接口

单元测试

Controller实现方法并对外提供API接口

实现前端逻辑,完成前后端交互

★5.8.4 MyBatis 映射文件参数含义多表关联查询

关联用户表(t_user)和文章表(t_article),主要是为了在查询帖子列表时,同时获取发布该帖子的用户信息

代码语言:javascript复制

columnPrefix="u_"/>

columnPrefix="b_" />

代码语言:javascript复制 namespace:指定该 XML 映射文件对应的 Mapper 接口全路径(ArticleMapper.java)。

作用:MyBatis 通过 namespace 关联接口与 XML,确保接口中的方法能找到对应的 SQL 语句。

代码语言:javascript复制

type="com.example.forum.model.Article" extends="ResultMapWithBLOBs"> 标签(自定义结果集映射)

id="AllInfoResultMap":当前结果集映射的唯一标识,供