本文参考自
本文是仿12306项目实战第(二)章——项目实现 的第一篇,详细讲解后端框架的搭建,手把手演示从0到1创建项目的过程
1.新建项目
注意:
- 当新建不成功,可以试试把防火墙先关了
- jdk选择17
-
依赖选择
devtool 热部署
boot版本先随便选一个,后续改成3.0.0;cloud版本改成2022.0.0
- 启动试试
2.初始化配置
- 修改encode
-
修改auto import
(不用按Alt+Enter,会自动导入)
- 热部署配置
3秒不动,会自动编译
此时,改动代码后,会自动编译重启
目前devtool会有重启两次的问题,可能是设计上的缺陷,它将代码的修改分成了 删除、修改class 和 新增class 两个操作,分别重启了一次
3.实现代码关联Git远程仓库
实际工作中,每完成一个小功能,就提交一次,写清楚注释,下班前,代码全部push到远程仓库,前提是代码不要报错
代码一定要放远程仓库,防止电脑损坏丢失。一般公司会用gitlib自己搭仓库
远程仓库需要配置ssh
远程地址选择ssh(选择https需要用户名密码)
演示IDEA操作:
- 启用git管理(相当于git init)
- 配置项目git的提交用户名和邮箱
-
git add 和 git commit
把Local Changes打开
git add
git commit
取消默认勾选的检查代码,提高速度
-
创建远程仓库
用gitee举例
- 配置ssh
- 新建仓库
-
提交代码到远程仓库
(已有本地仓库)
git remote add origin 【远程git地址(ssh)】 git push -u origin "master"
git remote add <remote_name> <remote_url>
:添加一个新的远程仓库。指定一个远程仓库的名称和 URL,将其添加到当前仓库中。-u
:指定后面的origin作为默认主机,后面就可以不加任何参数使用git push
4.新增member会员模块
-
在train模块下新建子模块——member
-
将父pom的和剪切,移动到子pom
-
创建启动类com.neilxu.train.member.config.MemberApplication
-
增加测试控制器com.neilxu.train.member.controller.TestController
-
增加配置类application.properties
server.port=8001
-
将父项目的src删除
-
- 启动
-
提交git
每次改动都记得提交git保存记录
5.实现日志的相关配置
-
修改配置
server.port=8001 server.servlet.context-path=/member
-
修改增加启动日志
package com.neilxu.train.member.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.env.Environment; @SpringBootApplication @ComponentScan("com.neilxu") public class MemberApplication { private static final Logger LOG = LoggerFactory.getLogger(MemberApplication.class); public static void main(String[] args) { SpringApplication app = new SpringApplication(MemberApplication.class); Environment env = app.run(args).getEnvironment(); LOG.info("启动成功!!"); LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"),env.getProperty("server.servlet.context-path")); } }
-
增加logback日志
- 固定写法
- 注意路径
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 修改一下路径--> <property name="PATH" value="./log/member"></property> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>--> <Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern> </encoder> </appender> <appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${PATH}/trace.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <layout> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern> </layout> </appender> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${PATH}/error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <layout> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern> </layout> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <root level="ERROR"> <appender-ref ref="ERROR_FILE" /> </root> <root level="TRACE"> <appender-ref ref="TRACE_FILE" /> </root> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration>
-
修改.gitignore文件,忽略日志文件夹
log/
6.使用HTTP Client完成测试接口
新建http文件,使用快捷键gtr生成代码模板
7.增加AOP打印请求参数和返回结果
-
增加依赖
父pom:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 增加依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.3</version> </dependency> </dependencies> </dependencyManagement>
子pom:
<!--增加依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency>
-
增加切面类
com.neilxu.train.member.aspect.LogAspect
package com.neilxu.train.member.aspect; import cn.hutool.core.util.RandomUtil; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.support.spring.PropertyPreFilters; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; 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.slf4j.MDC; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile; @Aspect @Component public class LogAspect { public LogAspect() { System.out.println("LogAspect"); } private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class); /** * 定义一个切点 */ //任意返回类型,neilxu下任意包(项目名/模块名),任意Controller类下任意方法任意参数 @Pointcut("execution(public * com.neilxu..*Controller.*(..))") public void controllerPointcut() { } @Before("controllerPointcut()") public void doBefore(JoinPoint joinPoint) { // 增加日志流水号 MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3)); // 开始打印请求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Signature signature = joinPoint.getSignature(); String name = signature.getName(); // 打印请求信息 LOG.info("------------- 开始 -------------"); LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name); LOG.info("远程地址: {}", request.getRemoteAddr()); // 打印请求参数 Object[] args = joinPoint.getArgs(); // LOG.info("请求参数: {}", JSONObject.toJSONString(args)); // 排除特殊类型的参数,如文件类型 Object[] arguments = new Object[args.length]; for (int i = 0; i < args.length; i++) { if (args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) { continue; } arguments[i] = args[i]; } // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等 String[] excludeProperties = {"mobile"}; PropertyPreFilters filters = new PropertyPreFilters(); PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); excludefilter.addExcludes(excludeProperties); LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); } @Around("controllerPointcut()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等 String[] excludeProperties = {"mobile"}; PropertyPreFilters filters = new PropertyPreFilters(); PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); excludefilter.addExcludes(excludeProperties); LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter)); LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); return result; } }
-
重启服务
加入新的依赖需要重启服务才会生效
-
测试
调用http测试接口
8.增加通用模块common
- 新建common子模块
-
修改pom文件
-
将member下的公用依赖(除了test和devtools)去除,移到common模块下
-
将common作为依赖导入member模块,并在父模块管理版本
-
common pom文件
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> </dependencies>
-
member pom文件
<dependencies> <dependency> <groupId>com.neilxu</groupId> <artifactId>common</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
-
train pom文件
<dependency> <groupId>com.neilxu</groupId> <artifactId>common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
-
-
将aspect移到common模块
- 测试
9.增加网关模块
- 新增gateway模块
-
修改pom文件,增加gateway依赖
gateway pom文件
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
gateway是基于netty,不需要引入springboot web依赖
-
增加启动类
@SpringBootApplication @ComponentScan("com.neilxu") public class GatewayApplication { private static final Logger LOG = LoggerFactory.getLogger(GatewayApplication.class); public static void main(String[] args) { SpringApplication app = new SpringApplication(GatewayApplication.class); Environment env = app.run(args).getEnvironment(); LOG.info("启动成功!!"); LOG.info("网关地址: \thttp://127.0.0.1:{}", env.getProperty("server.port")); } }
-
增加配置文件和配置日志
server.port=8000
<property name="PATH" value="./log/gateway"></property>
- 启动服务
这里把多tab页的run面板,转移到了services面板
解决方式:
.idea/workspace.xml 文件中 添加
<component name="RunDashboard">
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
</component>
重启服务
-
配置路由转发
修改gateway 配置文件
server.port=8000 # 路由转发,将/member/...的请求转发了member模块 spring.cloud.gateway.routes[0].id=member spring.cloud.gateway.routes[0].uri=http://127.0.0.1:8001 #spring.cloud.gateway.routes[0].uri=lb://member spring.cloud.gateway.routes[0].predicates[0]=Path=/member/**
yaml和properties互转在线工具
重启gateway服务
-
测试
GET http://localhost:8000/member/hello Accept: application/json ###
生产发布时,只有gateway需要配置外网IP,其他模块都只开放对内网访问,外网访问不了,保证应用安全
-
给gateway增加日志
配重VM参数
-Dreactor.netty.http.server.accessLogEnabled=true
重启gateway
测试
10.本地数据库的准备
-
安装mysql8.0(本地/云数据库)
-
重点:专库专用,忌用root
-
新增数据库train_member
先试用root连接本地mysql,再新建数据库
-
ci不区分大小写
> MySQL 8.0 表名大小写设置后不支持修改,请谨慎设置
- 新增用户train_member,配置权限只能操作train_member数据库
- 新建连接
11.阿里云RDS准备(云数据库)
这章节和上一章节二选一,本项目我自己使用的是本地数据库,所以这块不做详细介绍
12.使用IDEA配置数据库连接
按idea提示先下载好mysql8驱动
创建sql脚本并执行
drop table if exists `member`;
create table `member` (
`id` bigint not null comment 'id',
`mobile` varchar(11) comment '手机号',
primary key (`id`),
unique key `mobile_unique` (`mobile`)
) engine=innodb default charset=utf8mb4 comment='会员';
按快捷键 ctrl+shift+alt+v 能按原格式粘贴
为了简洁页面,后续执行sql语句用navicat工具
13.集成Mybatis
-
加入依赖
修改common 的pom文件
这里mybatis要升级到3.0.0,才支持和springboot3集成
<!-- 集成mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency> <!-- 集成mysql连接 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
-
配置数据库连接
# 数据库连接 spring.datasource.url=jdbc:mysql://localhost/train_member?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai spring.datasource.username=train_member spring.datasource.password=train_member spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-
编写代码
-
持久层
com.neilxu.train.member.mapper.MemberMapper
package com.neilxu.train.member.mapper; public interface MemberMapper { int count(); }
MemberMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.neilxu.train.member.mapper.MemberMapper"> <select id="count" resultType="int"> select count(1) from member </select> </mapper>
-
服务层
com.neilxu.train.member.service.MemberService
@Service public class MemberService { @Resource private MemberMapper memberMapper; public int count(){ return memberMapper.count(); } }
-
控制层
com.neilxu.train.member.controller.MemberController
@RestController @RequestMapping("/member") public class MemberController { @Resource private MemberService memberService; @GetMapping("/count") public Integer count() { return memberService.count(); } }
-
修改启动类
@MapperScan("com.neilxu.train.member.mapper")
-
增加mybatis配置
# mybatis xml路径 mybatis.mapper-locations=classpath:/mapper/**/*.xml logging.level.com.neilxu.train.member.mapper=trace
-
测试
GET http://localhost:8000/member/member/count Accept: application/json ###
member表手动加1行数据
-
14.集成Mybatis官方生成器
这里跟着课程使用了Mybaits官方的生成器(为了防止版本问题踩坑),实际项目中,我们也可以集成Mybaits Plus(目前已经支持springboot3,最新版是3.5.3)
- 创建generator 模块
-
配置插件
generator的 pom文件
<build> <plugins> <!-- mybatis generator 自动生成代码插件 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.0</version> <configuration> <!--<configurationFile>src/main/resources/generator-config-member.xml</configurationFile>--> <configurationFile>src/main/resources/generator-config-business.xml</configurationFile> <!--<configurationFile>src/main/resources/generator-config-batch.xml</configurationFile>--> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> </dependencies> </plugin> </plugins> </build>
-
编写生成规则xml
generator-config-member.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> <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat"> <!-- 自动检查关键字,为关键字增加反引号 --> <property name="autoDelimitKeywords" value="true"/> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <!--覆盖生成XML文件--> <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" /> <!-- 生成的实体类添加toString()方法 --> <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/> <!-- 不生成注释 --> <commentGenerator> <property name="suppressAllComments" value="true"/> </commentGenerator> <!-- 配置数据源,需要根据自己的项目修改 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost/train_member?serverTimezone=Asia/Shanghai" userId="train_member" password="train_member"> </jdbcConnection> <!-- domain类的位置 targetProject是相对pom.xml的路径--> <javaModelGenerator targetProject="../member/src/main/java" targetPackage="com.neilxu.train.member.domain"/> <!-- mapper xml的位置 targetProject是相对pom.xml的路径 --> <sqlMapGenerator targetProject="../member/src/main/resources" targetPackage="mapper"/> <!-- mapper类的位置 targetProject是相对pom.xml的路径 --> <javaClientGenerator targetProject="../member/src/main/java" targetPackage="com.neilxu.train.member.mapper" type="XMLMAPPER"/> <!--<table tableName="member" domainObjectName="Member"/>--> <!--<table tableName="passenger" domainObjectName="Passenger"/>--> <table tableName="member" domainObjectName="Member"/> </context> </generatorConfiguration>
-
执行代码生成器
生成四个文件:
-
修改service
public int count(){ return Math.toIntExact(memberMapper.countByExample(null)); }
-
测试
15.完成会员注册接口的开发
- 下载插件GeneratorAllSetter,用于快速创建一个对象的set方法
-
编写service方法
public long register(String mobile) { //先判断手机号是否已经被注册 MemberExample memberExample = new MemberExample(); memberExample.createCriteria().andMobileEqualTo(mobile); List<Member> list = memberMapper.selectByExample(memberExample); if (CollUtil.isNotEmpty(list)) { // return list.get(0).getId(); throw new RuntimeException("该手机号已被注册"); } Member member = new Member(); member.setId(System.currentTimeMillis()); member.setMobile(mobile); memberMapper.insert(member); return member.getId(); }
-
编写controller
@PostMapping("/register") public long register(String mobile) { return memberService.register(mobile); }
-
测试
快捷键ptrp 生成post模版
16.封装请求参数和返回结果
-
封装请求参数
这里引入下lombok依赖,提高效率
父pom
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope> </dependency>
子pom
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
com.neilxu.train.req.MemberRegisterReq
@Data public class MemberRegisterReq { private String mobile; }
com.neilxu.train.member.service.MemberService
public long register(MemberRegisterReq req) { String mobile = req.getMobile(); //先判断手机号是否已经被注册 MemberExample memberExample = new MemberExample(); memberExample.createCriteria().andMobileEqualTo(mobile); List<Member> list = memberMapper.selectByExample(memberExample); if (CollUtil.isNotEmpty(list)) { // return list.get(0).getId(); throw new RuntimeException("该手机号已被注册"); } Member member = new Member(); member.setId(System.currentTimeMillis()); member.setMobile(mobile); memberMapper.insert(member); return member.getId(); }
com.neilxu.train.member.controller.MemberController
@PostMapping("/register") public long register(MemberRegisterReq req) { return memberService.register(req); }
-
封装返回结果
开发环境,先把日志忽略mobile的放开
// 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等 String[] excludeProperties = {};
com.neilxu.train.common.resp.CommonResp
写法基本类似,直接复制来用
package com.neilxu.train.common.resp; public class CommonResp<T> { /** * 业务上的成功或失败 */ private boolean success = true; /** * 返回信息 */ private String message; /** * 返回泛型数据,自定义类型 */ private T content; public CommonResp() { } public CommonResp(boolean success, String message, T content) { this.success = success; this.message = message; this.content = content; } public CommonResp(T content) { this.content = content; } public boolean getSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getContent() { return content; } public void setContent(T content) { this.content = content; } @Override public String toString() { final StringBuffer sb = new StringBuffer("CommonResp{"); sb.append("success=").append(success); sb.append(", message='").append(message).append('\''); sb.append(", content=").append(content); sb.append('}'); return sb.toString(); } }
com.neilxu.train.member.controller.MemberController
@GetMapping("/count") public CommonResp<Integer> count() { int count = memberService.count(); CommonResp<Integer> commonResp = new CommonResp<>(); commonResp.setContent(count); return commonResp; } @PostMapping("/register") public CommonResp<Long> register(MemberRegisterReq req) { long register =memberService.register(req); CommonResp<Long> commonResp = new CommonResp<>(); commonResp.setContent(register); return commonResp; }
-
测试
17.给项目增加统一异常处理
-
增加统一异常处理器
com.neilxu.train.common.controller.ControllerExceptionHandler
package com.neilxu.train.common.controller; import com.neilxu.train.common.resp.CommonResp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * 统一异常处理、数据预处理等 */ @ControllerAdvice public class ControllerExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class); /** * 所有异常统一处理 * @param e * @return */ @ExceptionHandler(value = Exception.class) @ResponseBody public CommonResp exceptionHandler(Exception e) throws Exception { CommonResp commonResp = new CommonResp(); LOG.error("系统异常:", e); commonResp.setSuccess(false); // commonResp.setMessage("系统出现异常,请联系管理员"); commonResp.setMessage(e.getMessage()); return commonResp; } }
-
测试
18.使用自定义异常处理异常业务
-
添加自定义异常类
自定义异常枚举类
com.neilxu.train.common.exception.BusinessExceptionEnum
package com.neilxu.train.common.exception; public enum BusinessExceptionEnum { MEMBER_MOBILE_EXIST("手机号已注册"); private String desc; BusinessExceptionEnum(String desc) { this.desc = desc; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "BusinessExceptionEnum{" + "desc='" + desc + '\'' + "} " + super.toString(); } }
自定义异常类
com.neilxu.train.common.exception.BusinessException
package com.neilxu.train.common.exception; public class BusinessException extends RuntimeException { private BusinessExceptionEnum e; public BusinessException(BusinessExceptionEnum e) { this.e = e; } public BusinessExceptionEnum getE() { return e; } public void setE(BusinessExceptionEnum e) { this.e = e; } }
-
修改异常处理类
com.neilxu.train.common.controller.ControllerExceptionHandler
@ControllerAdvice public class ControllerExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class); /** * 所有异常统一处理 * @param e * @return */ @ExceptionHandler(value = Exception.class) @ResponseBody public CommonResp exceptionHandler(Exception e) { CommonResp commonResp = new CommonResp(); LOG.error("系统异常:", e); commonResp.setSuccess(false); commonResp.setMessage("系统出现异常,请联系管理员"); return commonResp; } /** * 业务异常统一处理 * @param e * @return */ @ExceptionHandler(value = BusinessException.class) @ResponseBody public CommonResp exceptionHandler(BusinessException e) { CommonResp commonResp = new CommonResp(); LOG.error("业务异常:", e); commonResp.setSuccess(false); commonResp.setMessage(e.getE().getDesc()); return commonResp; } }
-
修改service
com.neilxu.train.member.service.MemberService
if (CollUtil.isNotEmpty(list)) { // return list.get(0).getId(); throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_EXIST); }
-
测试
-
不写入堆栈信息,提高性能
修改BusinessException.java
/** * 不写入堆栈信息,提高性能 */ @Override public Throwable fillInStackTrace() { return this; }
修改ControllerExceptionHandler.java
@ExceptionHandler(value = BusinessException.class) @ResponseBody public CommonResp exceptionHandler(BusinessException e) { CommonResp commonResp = new CommonResp(); LOG.error("业务异常:{}", e.getE().getDesc()); commonResp.setSuccess(false); commonResp.setMessage(e.getE().getDesc()); return commonResp; }
测试
19.集成校验框架validation
-
引入依赖
common的 pom文件
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
加注解
com.neilxu.train.req.MemberRegisterReq
@Data public class MemberRegisterReq { @NotBlank(message = "【手机号】不能为空") private String mobile; }
com.neilxu.train.member.controller.MemberController
@PostMapping("/register") public CommonResp<Long> register(@Valid MemberRegisterReq req) { long register =memberService.register(req); CommonResp<Long> commonResp = new CommonResp<>(); commonResp.setContent(register); return commonResp; }
重启member服务
-
修改统一异常处理类
com.neilxu.train.common.controller.ControllerExceptionHandler
import org.springframework.validation.BindException;
/** * 校验异常统一处理 * @param e * @return */ @ExceptionHandler(value = BindException.class) @ResponseBody public CommonResp exceptionHandler(BindException e) { CommonResp commonResp = new CommonResp(); LOG.error("校验异常:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); commonResp.setSuccess(false); commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return commonResp; }
-
测试
20.详解雪花算法
生成ID:
自增ID不适合分布式数据库,分表分库场景。适合小型项目
UUID是无序的,会影响索引效率
因此,推荐雪花算法生成ID
雪花算法问题
-
数据中心,机器ID怎么设置
可在启动时,用IP或MAC到数据库中申请一个不重复的ID
-
时钟回拨
一个应用一般是多节点,可停止节点,等时间过了,再启动
实现
common 增加 雪花算法工具类
com.neilxu.train.common.util.SnowUtil
package com.neilxu.train.common.util;
import cn.hutool.core.util.IdUtil;
/**
* 封装hutool雪花算法
*/
public class SnowUtil {
private static long dataCenterId = 1; //数据中心
private static long workerId = 1; //机器标识
public static long getSnowflakeNextId() {
return IdUtil.getSnowflake(workerId, dataCenterId).nextId();
}
public static String getSnowflakeNextIdStr() {
return IdUtil.getSnowflake(workerId, dataCenterId).nextIdStr();
}
}
修改service
public long register(MemberRegisterReq req) {
String mobile = req.getMobile();
MemberExample memberExample = new MemberExample();
memberExample.createCriteria().andMobileEqualTo(mobile);
List<Member> list = memberMapper.selectByExample(memberExample);
if (CollUtil.isNotEmpty(list)) {
// return list.get(0).getId();
throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_EXIST);
}
Member member = new Member();
member.setId(SnowUtil.getSnowflakeNextId());
member.setMobile(mobile);
memberMapper.insert(member);
return member.getId();
}
测试