## 一、选择起步依赖 ## 二、导入其他`Maven依赖` `spring-boot-starter-aop` Maven版本列表:[Maven Repository: org.springframework.boot » spring-boot-starter-aop](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop) ```xml org.springframework.boot spring-boot-starter-aop ``` `mybatis-plus-boot-starter` Maven版本列表:[Maven Repository: com.baomidou » mybatis-plus-boot-starter](https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter) ```xml com.baomidou mybatis-plus-boot-starter 3.5.14 org.mybatis mybatis-spring ``` `mybatis-spring` ```xml org.mybatis mybatis-spring 3.0.5 ``` `mybatis-plus-jsqlparser` Maven仓库列表:[Maven Repository: com.baomidou » mybatis-plus-jsqlparser](https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser) ```xml com.baomidou mybatis-plus-jsqlparser 3.5.14 ``` `spring-boot-starter-data-redis` Maven仓库列表:[Maven Repository: org.springframework.boot » spring-boot-starter-data-redis](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis) ```xml org.springframework.boot spring-boot-starter-data-redis ``` `spring-session-core` Maven仓库列表:[Maven Repository: org.springframework.session » spring-session-core](https://mvnrepository.com/artifact/org.springframework.session/spring-session-core) ```xml org.springframework.session spring-session-core ``` #### 1、完整`dependencies` ```xml org.springframework.boot spring-boot-starter-aop com.baomidou mybatis-plus-boot-starter 3.5.14 org.mybatis mybatis-spring org.mybatis mybatis-spring 3.0.5 com.baomidou mybatis-plus-jsqlparser 3.5.14 org.springframework.boot spring-boot-starter-data-redis org.springframework.session spring-session-core ``` ## 三、修改配置文件 #### 1、`application.properties` ```properties spring.application.name=[project] spring.main.banner-mode=off # sessionCookie 可以使用的域名 server.servlet.session.cookie.domain=127.0.0.1 # 日志 logging.level.org.springframework=info logging.level.cn.doooog.[project]=debug logging.file.path=bin/log/ logging.file.name=[project].log # DBConfig # MySQL spring.datasource.url=jdbc:mysql://localhost/database?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 spring.datasource.username=username spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # Redis spring.data.redis.host=localhost # spring.data.redis.username=username # spring.data.redis.password=password spring.data.redis.port=6379 # 0~16 spring.data.redis.database=0 # Mybatis-Plus # 关闭大写转换小写加下划线 mybatis-plus.configuration.map-underscore-to-camel-case=false # 关闭调试信息的banner mybatis-plus.global-config.banner=false # 使用Mapper.xml映射sql # mybatis-plus.mapper-locations=classpath*:mapper/*.xml # po类取别名 # mybatis-plus.type-aliases-package=cn.doooog.[project].po ``` #### 2、`*Application.java` 添加注解扫描器 ```java @MapperScan("cn.doooog.[project].mapper") ``` ## 四、新建配置包 新建`config` #### 1、MyBatisPlus插件配置文件 创建`MybatisPlusConfig.java`类 ```java import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` #### 2、跨域请求限制 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class GlobalCorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { // 重写父类提供的跨域请求处理的接口 @Override public void addCorsMappings(CorsRegistry registry) { // 添加映射路径 registry.addMapping("/**") // 放行哪些域名,可以多个 .allowedOriginPatterns("*") // 是否发送Cookie信息 .allowCredentials(true) // 放行哪些请求方式 .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") // 放行哪些原始域(头部信息) .allowedHeaders("*") // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息) .exposedHeaders("set-cookie", "Header2") // 预请求的结果有效期,默认1800分钟,3600是一小时 .maxAge(3600); } }; } } ``` #### 3、跨域设置Cookie ```java import org.springframework.boot.autoconfigure.session.DefaultCookieSerializerCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.MapSessionRepository; import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.web.http.DefaultCookieSerializer; import java.util.concurrent.ConcurrentHashMap; @Configuration @EnableSpringHttpSession public class SessionConfig { @Bean public SessionRepository sessionRepository() { return new MapSessionRepository(new ConcurrentHashMap<>()); } @Bean DefaultCookieSerializerCustomizer cookieSerializerCustomizer() { return new DefaultCookieSerializerCustomizer() { @Override public void customize(DefaultCookieSerializer cookieSerializer) { cookieSerializer.setSameSite("None"); cookieSerializer.setUseSecureCookie(true); // 此项必须,否则set-cookie会被chrome浏览器阻拦 } }; } } ``` #### 3、Redis数据库配置 创建`RedisTemplateConfig.java`类 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisTemplateConfig { /** * redisTemplate 当前方法只能叫redisTemplate */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisFactory){ RedisTemplate redis = new RedisTemplate<>(); redis.setConnectionFactory(redisFactory); /*配置键序列化*/ StringRedisSerializer keySerializer = new StringRedisSerializer(); /*配置值序列化*/ Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer<>(Object.class); redis.setKeySerializer(keySerializer); redis.setValueSerializer(valueSerializer); return redis; } } ``` #### 4、Vue3前端History模式路由转发 在`controller`包下创建`SpaErrorController.java`用来处理错误请求 ```java import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SpaErrorController implements ErrorController { @RequestMapping("/error") public String handleError(HttpServletRequest request, HttpServletResponse response) { Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); Object url = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); if (status != null) { int statusCode = Integer.parseInt(status.toString()); if (statusCode == HttpStatus.NOT_FOUND.value()) { if (url != null) { String requestUrl = url.toString(); if (!requestUrl.contains("/api")) { // 非API请求,返回index.html response.setStatus(HttpStatus.OK.value()); return "forward:/index.html"; } } return new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error.html").toString(); } } return new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error.html").toString(); } } ``` ## 五、新建工具包 新建`util` #### 1、通用工具类 ```java import java.text.SimpleDateFormat; import java.util.Date; public class CommonUtil { // 获取系统日期 public static String getNowDate() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(new Date()); } } ``` ## 六、新建模型层 #### 1、利用`Lombok`注释封装类 * `@Setter`:创建Setter * `@Getter`:创建Getter * `@ToString`:重写toString() * `@EqualsAndHashCode`:重写equals()与HashCode() * `@NoArgsConstructor`:创建无参构造方法 * `@AllArgsConstructor`:创建全参构造方法 * `@Data` 相当于上面注解作用的总和 #### 2、标记数据库信息 * `@TableName("表名")`:对应数据库表名 * `@TableId(value = "主键", type = IdType.AUTO)`:对应自增主键 * `@TableField("字段名")`:对应普通字段 #### 3、创建响应包装类 `ResponseResult.java` ```java import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ResponseVO { private Integer code; private String msg; private T data; public static ResponseVO success(T data) { ResponseVO result = new ResponseVO<>(); result.setCode(200); result.setMsg("success"); result.setData(data); return result; } public static ResponseVO failed(String msg) { return error(203, msg); } public static ResponseVO error(Integer code, String msg) { ResponseVO result = new ResponseVO<>(); result.setCode(code); result.setMsg(msg); result.setData("{}"); return result; } public static ResponseVO error(String msg) { return error(500, msg); } public static ResponseVO denyAccess(String msg) { return error(403, msg); } } ``` #### 4、创建登录状态类 ```java @Data public class Token { private int id = -1; private int type = -1; private boolean isLogin = false; private String JSESSIONID = null; } ``` ## 七、新建数据层 新建`mapper`、`sqlprovider`、`dao`包 #### 1、`*Mapper.java` 用`@Mapper`修饰 ```java import cn.doooog.mybatisplus.po.[po对象]; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * 使用MP,核心接口BaseMapper * 所有Mapper都需要集成BaseMapper * 单表所有操作:增删改查 */ @Mapper public interface [Po对象]Mapper extends BaseMapper<[po对象]> { } ``` #### 2、`Redis.java`接口 ```java public interface Redis { // 存数据 void save(String key,Object value); // 存数据并设置过期时间 // TimeUnit.SECONDS等 void save(String key, Object value, long time, TimeUnit timeUnit); // 取数据 Object get(String key); // 删除数据并返回删除的数据 Object delete(String key); } ``` #### 3、`RedisImpl.java`类 ```java import cn.doooog.[project].dao.Redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; @Repository public class RedisImpl implements Redis { @Autowired private RedisTemplate redisTemplate; @Override public void save(String key, Object value) { redisTemplate.opsForValue().set(key,value); } @Override public void save(String key, Object value, long time, TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, time, timeUnit); } @Override public Object get(String key) { return redisTemplate.opsForValue().get(key); } @Override public Object delete(String key) { return redisTemplate.opsForValue().getAndDelete(key); } } ``` ## 八、新建服务层 新建`service`包 用`@Service`注解修饰 用于与模型层直接交互,对传入参数进行合法性校验,对传出参数进行修改 ## 九、新建控制层 新建`controller`包 用于对传出参数进行包装 常用注解 * `@Controller`:修饰控制层部件 * `@ResponseBody`:表明以响应体形式返回 * `@RestController`:上面两个注解的合体 * `@RequestMapping`:定义URL路径,常用于类头上 * `@GetMapping`:定义GET请求URL路径 * `@PostMapping`:定义POST请求URL路径 * `@PutMapping`:定义PUT请求URL路径 * `@PatchMapping`:定义PATCH请求URL路径 * `@DeleteMapping`:定义DELETE请求URL路径 * `@RequestParam(name = "参数名", requried = true, defaultValue = "默认值")`:定义请求非对象型参数别名注入 * `@RequestBody`:定义请求对象型Json参数注入 * `@Transactional(propagation = Propagation.REQUIRED)`:以事务的形式提交SQL命令 * `@Autowired/@Rescource`:类属性依赖注入 ## 十、新建通知层 新建`advice`包 ### 1、日志 创建`Log.java`类 ```java import jakarta.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @Component @Aspect @EnableAspectJAutoProxy public class Log { private static final Logger log = LoggerFactory.getLogger(Log.class); // 表达式 @Pointcut("execution(* cn.doooog.[project].controller.*.*(..))") private void cut() { } @Around("cut()") public Object around(ProceedingJoinPoint point) throws Throwable { //获得方法签名 Signature signature = point.getSignature(); //获得业务方法名 String name = signature.getName(); //获得业务方法所在控制器类的信息 String className = signature.getDeclaringTypeName(); String methodFullName = className + "." + name; //以下可以用来记录获取访问本服务端应用的客户端信息 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String url = ""; String ip = ""; if (requestAttributes != null) { //获得请求对象 HttpServletRequest request = requestAttributes.getRequest(); //获得请求地址 url = request.getRequestURL().toString(); //获得客户端IP地址 ip = request.getRemoteAddr(); } //记录方法执行前的信息 log.info("业务方法: {}", methodFullName); log.info("业务方法参数: {}", point.getArgs()); log.info("客户端IP地址: {}", ip); log.info("请求地址: {}", url); long startTime = System.currentTimeMillis(); Object result; try { //执行目标方法 result = point.proceed(); //记录方法正常返回的信息 long endTime = System.currentTimeMillis(); log.info("业务方法: {}", methodFullName); log.info("方法返回值: {}", result); log.info("执行耗时: {} ms", (endTime - startTime)); return result; } catch (Throwable throwable) { //记录方法异常的信息 long endTime = System.currentTimeMillis(); log.error("业务方法: {}", methodFullName); log.error("异常类型: {}", throwable.getClass().getName()); log.error("异常信息: {}", throwable.getMessage()); log.error("执行耗时: {} ms", (endTime - startTime)); log.error("异常堆栈:", throwable); //重新抛出异常 throw throwable; } } } ``` ## 2、全局异常处理器 创建`GlobalExceptionDeal.java` ```java import lombok.extern.log4j.Log4j2; import org.springframework.http.HttpStatus; import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; @RestControllerAdvice @Log4j2 public class GlobalExceptionDeal { @ExceptionHandler(AuthorizationDeniedException.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) public ResponseVO dealException(AuthorizationDeniedException e) { e.printStackTrace(); return new ResponseVO<>(500, "鉴权失败", "{}"); } @ExceptionHandler(NoResourceFoundException.class) @ResponseStatus(value = HttpStatus.NOT_FOUND) public ResponseVO dealException(NoResourceFoundException e) { log.warn("访问不存在的资源:{}", e.getMessage()); return new ResponseVO<>(404, "未找到资源", "{}"); } @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) public ResponseVO dealException(Exception e) { e.printStackTrace(); return new ResponseVO<>(500, "服务器错误", "{}"); } } ``` ## 十一、Redis常见问题 [Day28 - Redis常见问题](https://www.soki.me/archives/348.html) ## 十二、新建前端 Loading... ## 一、选择起步依赖 <img src="https://pic.soki.me/file/piclist/image-20251104114151285.png" alt="image-20251104114151285" style="zoom:50%;" style=""> ## 二、导入其他`Maven依赖` `spring-boot-starter-aop` Maven版本列表:[Maven Repository: org.springframework.boot » spring-boot-starter-aop](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop) ```xml <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` `mybatis-plus-boot-starter` Maven版本列表:[Maven Repository: com.baomidou » mybatis-plus-boot-starter](https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter) ```xml <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.14</version> <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </exclusion> </exclusions> </dependency> ``` `mybatis-spring` ```xml <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.5</version> </dependency> ``` `mybatis-plus-jsqlparser` Maven仓库列表:[Maven Repository: com.baomidou » mybatis-plus-jsqlparser](https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser) ```xml <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.14</version> </dependency> ``` `spring-boot-starter-data-redis` Maven仓库列表:[Maven Repository: org.springframework.boot » spring-boot-starter-data-redis](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis) ```xml <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` `spring-session-core` Maven仓库列表:[Maven Repository: org.springframework.session » spring-session-core](https://mvnrepository.com/artifact/org.springframework.session/spring-session-core) ```xml <!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-core --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> </dependency> ``` #### 1、完整`dependencies` ```xml <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.14</version> <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.5</version> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.14</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-core --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> </dependency> ``` ## 三、修改配置文件 #### 1、`application.properties` ```properties spring.application.name=[project] spring.main.banner-mode=off # sessionCookie 可以使用的域名 server.servlet.session.cookie.domain=127.0.0.1 # 日志 logging.level.org.springframework=info logging.level.cn.doooog.[project]=debug logging.file.path=bin/log/ logging.file.name=[project].log # DBConfig # MySQL spring.datasource.url=jdbc:mysql://localhost/database?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 spring.datasource.username=username spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # Redis spring.data.redis.host=localhost # spring.data.redis.username=username # spring.data.redis.password=password spring.data.redis.port=6379 # 0~16 spring.data.redis.database=0 # Mybatis-Plus # 关闭大写转换小写加下划线 mybatis-plus.configuration.map-underscore-to-camel-case=false # 关闭调试信息的banner mybatis-plus.global-config.banner=false # 使用Mapper.xml映射sql # mybatis-plus.mapper-locations=classpath*:mapper/*.xml # po类取别名 # mybatis-plus.type-aliases-package=cn.doooog.[project].po ``` #### 2、`*Application.java` 添加注解扫描器 ```java @MapperScan("cn.doooog.[project].mapper") ``` ## 四、新建配置包 新建`config` #### 1、MyBatisPlus插件配置文件 创建`MybatisPlusConfig.java`类 ```java import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` #### 2、跨域请求限制 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class GlobalCorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { // 重写父类提供的跨域请求处理的接口 @Override public void addCorsMappings(CorsRegistry registry) { // 添加映射路径 registry.addMapping("/**") // 放行哪些域名,可以多个 .allowedOriginPatterns("*") // 是否发送Cookie信息 .allowCredentials(true) // 放行哪些请求方式 .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") // 放行哪些原始域(头部信息) .allowedHeaders("*") // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息) .exposedHeaders("set-cookie", "Header2") // 预请求的结果有效期,默认1800分钟,3600是一小时 .maxAge(3600); } }; } } ``` #### 3、跨域设置Cookie ```java import org.springframework.boot.autoconfigure.session.DefaultCookieSerializerCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.MapSessionRepository; import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.web.http.DefaultCookieSerializer; import java.util.concurrent.ConcurrentHashMap; @Configuration @EnableSpringHttpSession public class SessionConfig { @Bean public SessionRepository sessionRepository() { return new MapSessionRepository(new ConcurrentHashMap<>()); } @Bean DefaultCookieSerializerCustomizer cookieSerializerCustomizer() { return new DefaultCookieSerializerCustomizer() { @Override public void customize(DefaultCookieSerializer cookieSerializer) { cookieSerializer.setSameSite("None"); cookieSerializer.setUseSecureCookie(true); // 此项必须,否则set-cookie会被chrome浏览器阻拦 } }; } } ``` #### 3、Redis数据库配置 创建`RedisTemplateConfig.java`类 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisTemplateConfig { /** * redisTemplate 当前方法只能叫redisTemplate */ @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisFactory){ RedisTemplate<String,Object> redis = new RedisTemplate<>(); redis.setConnectionFactory(redisFactory); /*配置键序列化*/ StringRedisSerializer keySerializer = new StringRedisSerializer(); /*配置值序列化*/ Jackson2JsonRedisSerializer<Object> valueSerializer = new Jackson2JsonRedisSerializer<>(Object.class); redis.setKeySerializer(keySerializer); redis.setValueSerializer(valueSerializer); return redis; } } ``` #### 4、Vue3前端History模式路由转发 在`controller`包下创建`SpaErrorController.java`用来处理错误请求 ```java import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SpaErrorController implements ErrorController { @RequestMapping("/error") public String handleError(HttpServletRequest request, HttpServletResponse response) { Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); Object url = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); if (status != null) { int statusCode = Integer.parseInt(status.toString()); if (statusCode == HttpStatus.NOT_FOUND.value()) { if (url != null) { String requestUrl = url.toString(); if (!requestUrl.contains("/api")) { // 非API请求,返回index.html response.setStatus(HttpStatus.OK.value()); return "forward:/index.html"; } } return new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error.html").toString(); } } return new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error.html").toString(); } } ``` ## 五、新建工具包 新建`util` #### 1、通用工具类 ```java import java.text.SimpleDateFormat; import java.util.Date; public class CommonUtil { // 获取系统日期 public static String getNowDate() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(new Date()); } } ``` ## 六、新建模型层 #### 1、利用`Lombok`注释封装类 * `@Setter`:创建Setter * `@Getter`:创建Getter * `@ToString`:重写toString() * `@EqualsAndHashCode`:重写equals()与HashCode() * `@NoArgsConstructor`:创建无参构造方法 * `@AllArgsConstructor`:创建全参构造方法 * `@Data` 相当于上面注解作用的总和 #### 2、标记数据库信息 * `@TableName("表名")`:对应数据库表名 * `@TableId(value = "主键", type = IdType.AUTO)`:对应自增主键 * `@TableField("字段名")`:对应普通字段 #### 3、创建响应包装类 `ResponseResult.java` ```java import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class ResponseVO<T> { private Integer code; private String msg; private T data; public static <T> ResponseVO<T> success(T data) { ResponseVO<T> result = new ResponseVO<>(); result.setCode(200); result.setMsg("success"); result.setData(data); return result; } public static ResponseVO<String> failed(String msg) { return error(203, msg); } public static ResponseVO<String> error(Integer code, String msg) { ResponseVO<String> result = new ResponseVO<>(); result.setCode(code); result.setMsg(msg); result.setData("{}"); return result; } public static ResponseVO<String> error(String msg) { return error(500, msg); } public static ResponseVO<String> denyAccess(String msg) { return error(403, msg); } } ``` #### 4、创建登录状态类 ```java @Data public class Token { private int id = -1; private int type = -1; private boolean isLogin = false; private String JSESSIONID = null; } ``` ## 七、新建数据层 新建`mapper`、`sqlprovider`、`dao`包 #### 1、`*Mapper.java` 用`@Mapper`修饰 ```java import cn.doooog.mybatisplus.po.[po对象]; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * 使用MP,核心接口BaseMapper * 所有Mapper都需要集成BaseMapper * 单表所有操作:增删改查 */ @Mapper public interface [Po对象]Mapper extends BaseMapper<[po对象]> { } ``` #### 2、`Redis.java`接口 ```java public interface Redis { // 存数据 void save(String key,Object value); // 存数据并设置过期时间 // TimeUnit.SECONDS等 void save(String key, Object value, long time, TimeUnit timeUnit); // 取数据 Object get(String key); // 删除数据并返回删除的数据 Object delete(String key); } ``` #### 3、`RedisImpl.java`类 ```java import cn.doooog.[project].dao.Redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; @Repository public class RedisImpl implements Redis { @Autowired private RedisTemplate<String,Object> redisTemplate; @Override public void save(String key, Object value) { redisTemplate.opsForValue().set(key,value); } @Override public void save(String key, Object value, long time, TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, time, timeUnit); } @Override public Object get(String key) { return redisTemplate.opsForValue().get(key); } @Override public Object delete(String key) { return redisTemplate.opsForValue().getAndDelete(key); } } ``` ## 八、新建服务层 新建`service`包 用`@Service`注解修饰 用于与模型层直接交互,对传入参数进行合法性校验,对传出参数进行修改 ## 九、新建控制层 新建`controller`包 用于对传出参数进行包装 常用注解 * `@Controller`:修饰控制层部件 * `@ResponseBody`:表明以响应体形式返回 * `@RestController`:上面两个注解的合体 * `@RequestMapping`:定义URL路径,常用于类头上 * `@GetMapping`:定义GET请求URL路径 * `@PostMapping`:定义POST请求URL路径 * `@PutMapping`:定义PUT请求URL路径 * `@PatchMapping`:定义PATCH请求URL路径 * `@DeleteMapping`:定义DELETE请求URL路径 * `@RequestParam(name = "参数名", requried = true, defaultValue = "默认值")`:定义请求非对象型参数别名注入 * `@RequestBody`:定义请求对象型Json参数注入 * `@Transactional(propagation = Propagation.REQUIRED)`:以事务的形式提交SQL命令 * `@Autowired/@Rescource`:类属性依赖注入 ## 十、新建通知层 新建`advice`包 ### 1、日志 创建`Log.java`类 ```java import jakarta.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @Component @Aspect @EnableAspectJAutoProxy public class Log { private static final Logger log = LoggerFactory.getLogger(Log.class); // 表达式 @Pointcut("execution(* cn.doooog.[project].controller.*.*(..))") private void cut() { } @Around("cut()") public Object around(ProceedingJoinPoint point) throws Throwable { //获得方法签名 Signature signature = point.getSignature(); //获得业务方法名 String name = signature.getName(); //获得业务方法所在控制器类的信息 String className = signature.getDeclaringTypeName(); String methodFullName = className + "." + name; //以下可以用来记录获取访问本服务端应用的客户端信息 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String url = ""; String ip = ""; if (requestAttributes != null) { //获得请求对象 HttpServletRequest request = requestAttributes.getRequest(); //获得请求地址 url = request.getRequestURL().toString(); //获得客户端IP地址 ip = request.getRemoteAddr(); } //记录方法执行前的信息 log.info("业务方法: {}", methodFullName); log.info("业务方法参数: {}", point.getArgs()); log.info("客户端IP地址: {}", ip); log.info("请求地址: {}", url); long startTime = System.currentTimeMillis(); Object result; try { //执行目标方法 result = point.proceed(); //记录方法正常返回的信息 long endTime = System.currentTimeMillis(); log.info("业务方法: {}", methodFullName); log.info("方法返回值: {}", result); log.info("执行耗时: {} ms", (endTime - startTime)); return result; } catch (Throwable throwable) { //记录方法异常的信息 long endTime = System.currentTimeMillis(); log.error("业务方法: {}", methodFullName); log.error("异常类型: {}", throwable.getClass().getName()); log.error("异常信息: {}", throwable.getMessage()); log.error("执行耗时: {} ms", (endTime - startTime)); log.error("异常堆栈:", throwable); //重新抛出异常 throw throwable; } } } ``` ## 2、全局异常处理器 创建`GlobalExceptionDeal.java` ```java import lombok.extern.log4j.Log4j2; import org.springframework.http.HttpStatus; import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; @RestControllerAdvice @Log4j2 public class GlobalExceptionDeal { @ExceptionHandler(AuthorizationDeniedException.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) public ResponseVO<String> dealException(AuthorizationDeniedException e) { e.printStackTrace(); return new ResponseVO<>(500, "鉴权失败", "{}"); } @ExceptionHandler(NoResourceFoundException.class) @ResponseStatus(value = HttpStatus.NOT_FOUND) public ResponseVO<String> dealException(NoResourceFoundException e) { log.warn("访问不存在的资源:{}", e.getMessage()); return new ResponseVO<>(404, "未找到资源", "{}"); } @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.BAD_REQUEST) public ResponseVO<String> dealException(Exception e) { e.printStackTrace(); return new ResponseVO<>(500, "服务器错误", "{}"); } } ``` ## 十一、Redis常见问题 [Day28 - Redis常见问题](https://www.soki.me/archives/348.html) ## 十二、新建前端 最后修改:2025 年 12 月 17 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。