基于Spring前后端分离版本的论坛系统。
1、构建项目结构
- common公共类:统一返回结果、全局变量、异常枚举信息
- config配置类:Swagger,用于自动生成CRUD和基本对象
- controller控制器类:用于接受前端信息和控制路由
- dao数据库访问类:用于控制数据库操作
- exception自定义异常类:用以定义统一异常变量
- interceptor自定义拦截器类:用于定义拦截器
- model数据库实体对应模型类:用于定义对象
- services业务服务层接口:用于定义服务接口
- impl业务服务层接口实现类:复写接口的方法,用于实现业务
- utils辅助工具类:存放一些辅助工具,如加密、解密等
- mapper:用于存放自动生成的xml文件,以及自定义的xml文件
- mybatis:用于配置mybatis自动生成CRUD的文件
- static:前端组件
Mybatis - generator
配置数据源
<!-- 管理依赖版块号-->
<mysql-connector.version>5.1.49</mysql-connector.version>
<mybatis-starter.version>2.3.0</mybatis-starter.version>
<druid-starter.version>1.2.16</druid-starter.version>
# 配置数据源
datasource:
url: jdbc:mysql://127.0.0.1:3306/forum_db?characterEncoding=utf8&useSSL=false
# 数据库连接串
username: root # 数据库⽤⼾名
password: 123456 # 数据库密码
driver-class-name: com.mysql.jdbc.Driver # 数据库连接驱动
编写类与映射文件
编写映射文件xxxMapper.xml
编写Dao类,xxxMapper.java
在properties标签中加入版本号,在build的plugins中加入配置
<mybatis-generator-plugin-version>1.4.1</mybatis-generator-plugin-version>
<!-- mybatis ⽣成器插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>${mybatis-generator-plugin-version}</version>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>deploy</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<!-- 相关配置 -->
<configuration>
<!-- 打开⽇志 -->
<verbose>true</verbose>
<!-- 允许覆盖 -->
<overwrite>true</overwrite>
<!-- 配置⽂件路径 -->
<configurationFile>
src/main/resources/mybatis/generatorConfig.xml
</configurationFile>
</configuration>
</plugin>
创建generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 驱动包路径,location中路径替换成⾃⼰本地路径 -->
<classPathEntry location="D:\database\jar\mysql-connector-java-
5.1.49.jar"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 禁⽤⾃动⽣成的注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
<property name="suppressDate" value="true"/>
</commentGenerator>
<!-- 连接配置 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/forum_db?
characterEncoding=utf8&useSSL=false"
userId="root"
password="123456">
</jdbcConnection>
<javaTypeResolver>
<!-- ⼩数统⼀转为BigDecimal -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 实体类⽣成位置 -->
<javaModelGenerator targetPackage="com.bitejiuyeke.forum.model"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- mapper.xml⽣成位置 -->
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- DAO类⽣成位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.bitejiuyeke.forum.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 配置⽣成表与实例, 只需要修改表名tableName, 与对应类名domainObjectName 即
可-->
<table tableName="t_article" domainObjectName="Article"
enableSelectByExample="false"
enableDeleteByExample="false" enableDeleteByPrimaryKey="false"
enableCountByExample="false"
enableUpdateByExample="false">
<!-- 类的属性⽤数据库中的真实字段名做为属性名, 不指定这个属性会⾃动转换 _ 为
驼峰命名规则-->
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_article_reply" domainObjectName="ArticleReply"
enableSelectByExample="false"
enableDeleteByExample="false" enableDeleteByPrimaryKey="false"
enableCountByExample="false"
enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_board" domainObjectName="Board"
enableSelectByExample="false" enableDeleteByExample="false"
enableDeleteByPrimaryKey="false" enableCountByExample="false"
enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_message" domainObjectName="Message"
enableSelectByExample="false"
enableDeleteByExample="false" enableDeleteByPrimaryKey="false"
enableCountByExample="false"
enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="t_user" domainObjectName="User"
enableSelectByExample="false" enableDeleteByExample="false"
enableDeleteByPrimaryKey="false" enableCountByExample="false"
enableUpdateByExample="false">
<property name="useActualColumnNames" value="true"/>
</table>
</context>
</generatorConfiguration>
创建mapper目录,在maven选项中,plugins节点下出现mybatis-generator,双击运行。
<!-- useGeneratedKeys = true -->
<!-- keyProperty = 主键字段-->
<!-- 当插⼊⼀条数据后,可以通过user.getId()获取到⾃动⽣成的Id值,如果⽅法中需要⽴即获取
Id值,加⼊这个配置 -->
<insert id="insert" parameterType="com.bitejiuyeke.forum.model.User"
useGeneratedKeys="true" keyProperty="id" >
加@Mapper注解,在config包下新建MybatisConfig类,指定mybatis的扫描路径
// 配置类
@Configuration
// 指定Mybatis的扫描路径
@MapperScan("com.bitejiuyeke.forum.dao")
public class MybatisConfig {
}
mybatis:
mapper-locations: classpath:mapper/**/*.xml # 指定 xxxMapper.xml的扫描路径
2、编写公共代码
编写状态码,用枚举类型
package com.example.forum.common;
/**
* 枚举结果状态码
*/
public enum ResultCode {
SUCCESS (0, "操作成功"),
FAILED (1000, "操作失败"),
FAILED_UNAUTHORIZED (1001, "未授权"),
FAILED_PARAMS_VALID (1002, "参数校验失败"),
FAILED_FORBIDDEN (1003, "禁⽌访问"),
FAILED_CREATE (1004, "新增失败"),
FAILED_NOT_EXISTS (1005, "资源不存在"),
//用户
FAILED_USER_EXISTS (1101, "⽤⼾已存在"),
FAILED_USER_NOT_EXISTS (1102, "⽤⼾不存在"),
FAILED_LOGIN (1103, "⽤⼾名或密码错误"),
FAILED_USER_BANNED (1104, "您已被禁⾔, 请联系管理员, 并重新登录."),
FAILED_TWO_PWD_NOT_SAME (1105, "两次输⼊的密码不⼀致"),
FAILED_USER_ARTICLE_COUNT (1106,"更新帖子数量失败"),
//板块
FAILED_BOARD_ARTICLE_COUNT (1201,"更新帖子数量失败"),
FAILED_BOARD_BANNED (1202,"板块内部错误"),
FAILED_BOARD_NOT_EXISTS (1203, "板块不存在"),
FAILED_ARTICLE_NOT_EXISTS (1301, "帖子不存在"),
FAILED_ARTICLE_BANNED (1302, "帖子状态异常"),
//站内信
FAILED_MESSAGE_NOT_EXISTS (1401,"站内信不存在"),
//服务器
ERROR_SERVICES (2000, "服务器内部错误"),
ERROR_IS_NULL (2001, "IS NULL.");
int code; //状态码
String message; //描述信息
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return "code = " + code + "," + "message = " + message;
}
}
编写统一返回结果,用AppResult对象的格式返回
package com.example.forum.common;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 统一对外返回格式
* @param <T>
*/
public class AppResult<T> {
@JsonInclude(JsonInclude.Include.ALWAYS) //无论任何情况,这个字段都要参数json序列化
private int code; //状态码
@JsonInclude(JsonInclude.Include.ALWAYS)
private String message; //描述消息
@JsonInclude(JsonInclude.Include.ALWAYS)
private T data; //具体的数据,通配类型,会被编译成Object
//构造方法
public AppResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public AppResult(int code, String message) {
this.code = code;
this.message = message;
this.data = null;
}
//提供给外部使用的静态方法
public static AppResult success(){
return new AppResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
}
public static AppResult success(String message){
return new AppResult(ResultCode.SUCCESS.getCode(), message);
}
public static <T> AppResult<T> success (T data){
return new AppResult<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(),data);
}
public static <T> AppResult<T> success(String message,T data){
return new AppResult<>(AppResult.success().getCode(),message,data);
}
public static AppResult failed(){
return new AppResult(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage());
}
public static AppResult failed(String message){
return new AppResult(ResultCode.FAILED.getCode(), message);
}
public static AppResult failed(ResultCode resultCode){
return new AppResult(resultCode.getCode(), resultCode.getMessage());
}
//普通get set方法
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
编写自定义全局异常类ApplicationException
package com.example.forum.exception;
import com.example.forum.common.AppResult;
/**
* 统一异常处理,使用AppResult来包装返回值
*/
public class ApplicationException extends RuntimeException{
//自定义错误,在异常中持有一个错误信息对象
protected AppResult errorResult;
//自定义构造方法
public ApplicationException(AppResult errorResult){
super(errorResult.getMessage());
this.errorResult = errorResult;
}
//重写父类方法
public ApplicationException(String message) {
super(message);
}
public ApplicationException(String message, Throwable cause) {
super(message, cause);
}
public ApplicationException(Throwable cause) {
super(cause);
}
//get方法
public AppResult getErrorResult() {
return errorResult;
}
}
编写登录拦截器
package com.example.forum.interceptor;
import com.example.forum.common.AppConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 拦截器
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Value("${bit-forum.login.url}")
private String defaultURL;
/**
* 对请求的前置预处理
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(AppConfig.USER_SESSION) != null){
//校验通过,已登录状态
return true;
}
//校验URL前面有/吗
if (!defaultURL.startsWith("/")){
defaultURL = "/" + defaultURL;
}
//不通过就跳转到登录界面
response.sendRedirect(defaultURL);
return false;
}
}
package com.example.forum.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@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("/js/**")
.excludePathPatterns("/**.ico");
}
}
# 项⽬⾃定义相关配置
bit-forum:
login:
url: sign-in.html # 未登录状况下强制跳转⻚⾯
3、Swagger检测
<springfox-boot-starter.version>3.0.0</springfox-boot-starter.version>
<!-- API⽂档⽣成,基于swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${springfox-boot-starter.version}</version>
</dependency>
<!-- SpringBoot健康监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
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;
// 配置类
@Configuration
// 开启Springfox-Swagger
@EnableOpenApi
public class SwaggerConfig {
//创建基本扫描路径,与controller路径要一致
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("论坛系统API")
.description("论坛系统前后端分离API测试")
.contact(new Contact(" Tech",
"https://", "1161245326@qq.com"))
.version("1.0")
.build();
return apiInfo;
}
/**
* 解决SpringBoot 6.0以上与Swagger 3.0.0 不兼容的问题
* 复制即可
**/
@Bean
public WebMvcEndpointHandlerMapping webMvcEndpointHandlerMapping (
WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier,
ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties,
Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
Collection<ExposableWebEndpoint> 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));
}
}
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER #Springfox-Swagger兼容性配置
4、MD5Util
package com.example.forum.utils;
import org.apache.commons.codec.cli.Digest;
import org.apache.commons.codec.digest.DigestUtils;
/**
* 用于MD5加密的工具类
*/
public class MD5Util {
/**
* 对单个字符串进行加密
* 传入源字符串后,可以得到一个经过MD5加密后的密文字符串
* @param str
* @return
*/
public static String md5(String str){
return DigestUtils.md5Hex(str);
}
/**
* 给密码进行加密,盐+密码
* @param str
* @param salt
* @return
*/
public static String md5Salt(String str,String salt){
return md5(salt + md5(str));
}
}
package com.example.forum.utils;
import java.util.UUID;
public class UUIDUtil {
/**
* 生成一个标准的36位的UUID
* @return
*/
public static String UUID_36(){
return UUID.randomUUID().toString();
}
/**
* 生成一个32位的UUID
* @return
*/
public static String UUID_32(){
return UUID.randomUUID().toString().replace("-","");
}
}
package com.example.forum.utils;
public class StringUtil {
//判断字符串是否为空
public static boolean isEmpty(String value){
return value == null || value.length() == 0;
}
}