新增member会员模块
创建member模块,添加依赖
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <parent> 6 <artifactId>train</artifactId> 7 <groupId>com.jiawa</groupId> 8 <version>0.0.1-SNAPSHOT</version> 9 </parent> 10 <modelVersion>4.0.0</modelVersion> 11 12 <artifactId>member</artifactId> 13 14 <properties> 15 <maven.compiler.source>17</maven.compiler.source> 16 <maven.compiler.target>17</maven.compiler.target> 17 </properties> 18 19 <dependencies> 20 <dependency> 21 <groupId>org.springframework.boot</groupId> 22 <artifactId>spring-boot-starter-web</artifactId> 23 </dependency> 24 <dependency> 25 <groupId>org.springframework.cloud</groupId> 26 <artifactId>spring-cloud-starter</artifactId> 27 </dependency> 28 29 <dependency> 30 <groupId>org.springframework.boot</groupId> 31 <artifactId>spring-boot-devtools</artifactId> 32 <scope>runtime</scope> 33 <optional>true</optional> 34 </dependency> 35 <dependency> 36 <groupId>org.springframework.boot</groupId> 37 <artifactId>spring-boot-starter-test</artifactId> 38 <scope>test</scope> 39 </dependency> 40 </dependencies> 41 42 <build> 43 <plugins> 44 <plugin> 45 <groupId>org.springframework.boot</groupId> 46 <artifactId>spring-boot-maven-plugin</artifactId> 47 </plugin> 48 </plugins> 49 </build> 50 51 </project>pom.xml
创建启动类MemberApplication.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.config; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.context.annotation.ComponentScan; 6 7 @SpringBootApplication 8 @ComponentScan("com.jiawa") 9 public class MemberApplication { 10 11 public static void main(String[] args) { 12 SpringApplication.run(MemberApplication.class, args); 13 } 14 15 }MemberApplication.java
实现日志的相关配置
在入口类中添加日志信息,提醒启动成功!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @SpringBootApplication 2 @ComponentScan("com.zihans") 3 public class MemberApplication { 4 5 private static final Logger LOG = LoggerFactory.getLogger(MemberApplication.class); 6 public static void main(String[] args) { 7 //SpringApplication.run(MemberApplication.class, args); 8 SpringApplication app = new SpringApplication(MemberApplication.class); 9 Environment env = app.run(args).getEnvironment(); 10 LOG.info("启动成功!!"); 11 LOG.info("测试地址: \thttp://127.0.0.1:{}/{}/hello", env.getProperty("server.port"), env.getProperty("server.servlet.context-path")); 12 } 13 }MemberApplication.java
然后在配置文件中添加context-path路径,此时请求地址要改为127.0.0.1:8001/member/hello,这是为了后面做路由转发,在网关模块中,将接口带有"/member"的请求都转发到member模块。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 server: 2 port: 8001 3 servlet: 4 context-path: /memberapplication.yml
resources中创建日志文件,方便查找日志
日志内容↓
分:秒:毫秒 日志级别 类名(30字符) 行号 线程 日志
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <configuration> 3 <!-- 修改一下路径--> 4 <property name="PATH" value="./log/member"></property> 5 6 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 7 <encoder> 8 <!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>--> 9 <Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern> 10 </encoder> 11 </appender> 12 13 <appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 14 <file>${PATH}/trace.log</file> 15 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 16 <FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern> 17 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 18 <maxFileSize>10MB</maxFileSize> 19 </timeBasedFileNamingAndTriggeringPolicy> 20 </rollingPolicy> 21 <layout> 22 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern> 23 </layout> 24 </appender> 25 26 <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 27 <file>${PATH}/error.log</file> 28 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 29 <FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern> 30 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 31 <maxFileSize>10MB</maxFileSize> 32 </timeBasedFileNamingAndTriggeringPolicy> 33 </rollingPolicy> 34 <layout> 35 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern> 36 </layout> 37 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 38 <level>ERROR</level> 39 <onMatch>ACCEPT</onMatch> 40 <onMismatch>DENY</onMismatch> 41 </filter> 42 </appender> 43 44 <root level="ERROR"> 45 <appender-ref ref="ERROR_FILE" /> 46 </root> 47 48 <root level="TRACE"> 49 <appender-ref ref="TRACE_FILE" /> 50 </root> 51 52 <root level="INFO"> 53 <appender-ref ref="STDOUT" /> 54 </root> 55 </configuration>logback-spring.xml
使用HTTP Client完成测试接口
IDEA自带Http Client插件(Tools -> HTTP Client -> Create Request in HTTP Client),可以在IDEA中直接发起http调用
或者直接创建一个http文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 GET http://localhost:8001/member/hello 2 Accept: application/jsonmember-test.http
增加AOP打印请求参数和返回结果
每个请求都要打印日志,方便维护和检查。最简单的方法是每一个接口都增加一个返回日志,但是这样会更改每一个接口,有侵入性,很不方便。最简单的方法是使用AOP或拦截器进行请求拦截。AOP可以拦截controller,也可以拦截service等其他类,拦截器只能拦截controller。
在根目录以及member目录增加依赖
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>fastjson</artifactId> 4 <version>1.2.70</version> 5 </dependency> 6 7 <dependency> 8 <groupId>cn.hutool</groupId> 9 <artifactId>hutool-all</artifactId> 10 <version>5.6.3</version> 11 </dependency>pom.xml
增加类LogAspect
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @Aspect 2 @Component 3 public class LogAspect { 4 public LogAspect() { 5 System.out.println("LogAspect"); 6 } 7 8 private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class); 9 10 /** 11 * 定义一个切点 12 */ 13 @Pointcut("execution(public * com.zihans..*Controller.*(..))") 14 public void controllerPointcut() { 15 } 16 17 @Before("controllerPointcut()") 18 public void doBefore(JoinPoint joinPoint) { 19 20 // 增加日志流水号 21 MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3)); 22 23 // 开始打印请求日志 24 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 25 HttpServletRequest request = attributes.getRequest(); 26 Signature signature = joinPoint.getSignature(); 27 String name = signature.getName(); 28 29 // 打印请求信息 30 LOG.info("------------- 开始 -------------"); 31 LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); 32 LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name); 33 LOG.info("远程地址: {}", request.getRemoteAddr()); 34 35 // 打印请求参数 36 Object[] args = joinPoint.getArgs(); 37 // LOG.info("请求参数: {}", JSONObject.toJSONString(args)); 38 39 // 排除特殊类型的参数,如文件类型 40 Object[] arguments = new Object[args.length]; 41 for (int i = 0; i < args.length; i++) { 42 if (args[i] instanceof ServletRequest 43 || args[i] instanceof ServletResponse 44 || args[i] instanceof MultipartFile) { 45 continue; 46 } 47 arguments[i] = args[i]; 48 } 49 // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等 50 String[] excludeProperties = {"mobile"}; 51 PropertyPreFilters filters = new PropertyPreFilters(); 52 PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); 53 excludefilter.addExcludes(excludeProperties); 54 LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); 55 } 56 57 @Around("controllerPointcut()") 58 public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 59 long startTime = System.currentTimeMillis(); 60 Object result = proceedingJoinPoint.proceed(); 61 // 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等 62 String[] excludeProperties = {"mobile"}; 63 PropertyPreFilters filters = new PropertyPreFilters(); 64 PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); 65 excludefilter.addExcludes(excludeProperties); 66 LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter)); 67 LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); 68 return result; 69 } 70 71 }LogAspect.java
此时发出请求输出
项目中增加通用模块
父pom用于更新版本号,其余pom不用写版本号,交给父pom统一管理。
增加common模块,用于放公共代码。
增加公共配置文件,必须放在/config/目录下,否则无效(不是覆盖关系);优先读公共配置下的配置。
项目中增加网关模块
网关模块非常重要,可以用来做路由转发,权限校验等。可以理解为一个关卡,所有的请求到达业务模块前都要经过这个关卡,验证身份才能访问。
前端请求全部访问网关模块,后端对应的模块由它去做路由。
gateway是基于netty的,只有一个依赖,不能引入common,也不能引入starter-web
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
生产发布时,只有gateway需要配置外网IP,其它模块都只开放内网访问,外网访问不了,保证应用安全
要输出请求日志需要增加启动参数:
-Dreactor.netty.http.server.accessLogEnabled=true
数据库准备
- Mysql 8.0
- 本地数据库 or 云数据库
- 重点:专库专用,忌用root
(1) 本地数据库
- 新增数据库train
- 新增用户train,配置权限只能操作train数据库
(2) 云数据库
好处:
-
免去环境搭建,版本任选
-
方便多台电脑协作开发
-
相当于雇了—帮运维
-
总结:花钱买时间
(3) 使用IDEA配置数据库连接
好处:
- 可直接执行sql脚本
- mybatis xml 文件可以识别数据库表
- 可以IDEA里直接操作数据库,增删改查等。
集成Mybatis持久层框架
在主配置和common配置中引入依赖
1 <dependency> 2 <groupId>org.mybatis.spring.boot</groupId> 3 <artifactId>mybatis-spring-boot-starter</artifactId> 4 <version>2.1.3</version> 5 </dependency> 6 7 <dependency> 8 <groupId>mysql</groupId> 9 <artifactId>mysql-connector-java</artifactId> 10 <version>8.0.30</version> 11 </dependency>
本地新增数据库train-member。新增用户train_member,配置权限只能操作train-member数据库。
每个模块都有对应数据库
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 drop table if exists member; 2 create table member ( 3 id bigint not null comment 'id', 4 mobile varchar(11) comment '手机号', 5 primary key (id), 6 unique key mobile_unique (mobile) 7 ) engine=innodb default charset=utf8mb4 comment='会员';member.sql
模块member中配置yml语句用于连接数据库
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#数据库连接 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost_3306/train-member?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Nanjing username: member password: memberapplication.yml
member以及其中的resources中新建包mapper,持久层代码写在mapper
启动类添加注解
@MapperScan("com/zihans/train/member/mapper")
创建接口MemberMapper.java以及配置文件MemberMapper.xml
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.mapper; 2 3 public interface MemberMapper { 4 int count(); 5 }MemberMapper.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > 3 <mapper namespace="com.zihans.train.member.mapper.MemberMapper" > 4 5 <select id="count" resultType="int"> 6 select count(1) from member 7 </select> 8 9 </mapper>MemberMapper.xml
继续配置yml添加mybatis xml路径
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # mybatis xml路径 2 mybatis: 3 mapper-locations: classpath:/mapper/**/*.xml 4 logging: 5 level: 6 com: 7 zihans: 8 train: 9 member: 10 mapper: traceapplication.yml
理解:新增一个mapper接口,在启动类添加@MapperScan注解使项目扫描该接口,然后通过xml文件写sql语句,同时通过yml配置文件使mybatis可以读到xml,通过xml中的命名空间将mapper接口和xml关联。
新建服务层包service,新建服务类MemberService,添加注解@Service使SpringBoot知道这是service类,使用注解@Resource注入MemberMapper。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.service; 2 import com.zihans.train.member.mapper.MemberMapper; 3 import jakarta.annotation.Resource; 4 import org.springframework.stereotype.Service; 5 6 @Service 7 public class MemberService { 8 @Resource 9 private MemberMapper memberMapper; 10 11 public int count() { 12 return memberMapper.count(); 13 14 } 15 }MemberService.java
新建MemberController类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.controller; 2 3 4 import com.zihans.train.member.service.MemberService; 5 import jakarta.annotation.Resource; 6 import org.springframework.web.bind.annotation.GetMapping; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RestController; 9 10 @RestController 11 @RequestMapping("/member") 12 public class MemberController { 13 14 @Resource 15 private MemberService memberService; 16 17 @GetMapping("/count") 18 public int count() { 19 return memberService.count(); 20 } 21 }MemberController.java
用http client测试
1 GET http://localhost:8000/member/member/count 2 Accept: application/json
输出下图,测试成功!
集成Mybatis官方生成器
只能生成单表增删改查
上一部分中,几个模块都是手动生成的,开发速度太慢,所以用代码生成器生成。这里没有使用MybatisPlus,而是使用Mabstis+官方生成器(Mybatis Generator),没有必要再多用一层框架。
创建一个generator模块,该模块不光写Mybatis官方生成器,后期还打算写一套自己的代码生成器。
在generator的依赖中引入mybatis generator插件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <build> 2 <plugins> 3 <!-- mybatis generator 自动生成代码插件 --> 4 <plugin> 5 <groupId>org.mybatis.generator</groupId> 6 <artifactId>mybatis-generator-maven-plugin</artifactId> 7 <version>1.4.0</version> 8 <configuration> 9 <configurationFile>src/main/resources/generator-config-member.xml</configurationFile> 10 <overwrite>true</overwrite> 11 <verbose>true</verbose> 12 </configuration> 13 <dependencies> 14 <dependency> 15 <groupId>mysql</groupId> 16 <artifactId>mysql-connector-java</artifactId> 17 <version>8.0.22</version> 18 </dependency> 19 </dependencies> 20 </plugin> 21 </plugins> 22 </build>pom.xml
在resources中新建xml文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE generatorConfiguration 3 PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" 4 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> 5 6 <generatorConfiguration> 7 <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat"> 8 9 <!-- 自动检查关键字,为关键字增加反引号 --> 10 <property name="autoDelimitKeywords" value="true"/> 11 <property name="beginningDelimiter" value="`"/> 12 <property name="endingDelimiter" value="`"/> 13 14 <!--覆盖生成XML文件--> 15 <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" /> 16 <!-- 生成的实体类添加toString()方法 --> 17 <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/> 18 19 <!-- 不生成注释 --> 20 <commentGenerator> 21 <property name="suppressAllComments" value="true"/> 22 </commentGenerator> 23 24 <!-- 配置数据源,需要根据自己的项目修改 --> 25 <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" 26 connectionURL="jdbc:mysql://localhost:3306/train-member?serverTimezone=Asia/Shanghai" 27 userId="train_member" 28 password="member"> 29 </jdbcConnection> 30 31 <!-- domain类的位置 targetProject是相对pom.xml的路径--> 32 <javaModelGenerator targetProject="..\member\src\main\java" 33 targetPackage="com.zihans.train.member.domain"/> 34 35 <!-- mapper xml的位置 targetProject是相对pom.xml的路径 --> 36 <sqlMapGenerator targetProject="..\member\src\main\resources" 37 targetPackage="mapper"/> 38 39 <!-- mapper类的位置 targetProject是相对pom.xml的路径 --> 40 <javaClientGenerator targetProject="..\member\src\main\java" 41 targetPackage="com.zihans.train.member.mapper" 42 type="XMLMAPPER"/> 43 44 <table tableName="member" domainObjectName="Member"/> 45 </context> 46 </generatorConfiguration>generator-config-member.xml
配置好后执行mybatis-generator:generate,将生成四个文件,domain目录下的Member.java、MemberExample.java,mapper目录下的MemberMapper.interface以及资源目录下的MemberMapper.xml。这四个文件不能手动更改,因为容易被重新覆盖。自定义SQL需要写到自己的mapper中,不能放在生成的mapper。
修改MemberService.java文件进行测试
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.service; 2 import com.zihans.train.member.mapper.MemberMapper; 3 import jakarta.annotation.Resource; 4 import org.springframework.stereotype.Service; 5 6 @Service 7 public class MemberService { 8 @Resource 9 private MemberMapper memberMapper; 10 11 public int count() { 12 return Math.toIntExact(memberMapper.countByExample(null)); 13 14 } 15 }MemberService.java
测试成功!
完成会员注册接口的开发
MemberService.java新增方法
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();
}
MemberController.java新增
@PostMapping("/register") public long register(String mobile) { return memberService.register(mobile); }
测试
POST http://localhost:8000/member/member/register Content-Type: application/x-www-form-urlencoded mobile = 18888888888
成功!
封装请求参数和返回结果
前后端分离项目,需要对后端接口做统一封装,这样便于前端做统—处理。
普通封装方法
在模块member中创建新的包req用于封装。
创建MemberRegisterReq.java类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.req; 2 3 public class MemberRegisterReq { 4 private String mobile; 5 6 public String getMobile() { 7 return mobile; 8 } 9 10 public void setMobile(String mobile) { 11 this.mobile = mobile; 12 } 13 14 @Override 15 public String toString() { 16 return "MemberRegisterReq{" + 17 "mobile='" + mobile + '\'' + 18 '}'; 19 } 20 }MemberRegisterReq.java
修改service和controller
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public long register(MemberRegisterReq req) { 2 String mobile = req.getMobile(); 3 MemberExample memberExample = new MemberExample(); 4 memberExample.createCriteria().andMobileEqualTo(mobile); 5 List<Member> list = memberMapper.selectByExample(memberExample); 6 7 if (CollUtil.isNotEmpty(list)) { 8 //return list.get(0).getId(); 9 throw new RuntimeException("手机号已注册"); 10 } 11 12 Member member = new Member(); 13 member.setId(System.currentTimeMillis()); 14 member.setMobile(mobile); 15 16 memberMapper.insert(member); 17 return member.getId(); 18 19 }MemberService.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @PostMapping("/register") 2 public long register(MemberRegisterReq req) { 3 return memberService.register(req); 4 }MemberController.java
为了方便前端统一处理,后端接口要统一包装
在common模块新建包resp,新增CommonResp.java类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.common.resp; 2 3 public class CommonResp<T> { 4 5 /** 6 * 业务上的成功或失败 7 */ 8 private boolean success = true; 9 10 /** 11 * 返回信息 12 */ 13 private String message; 14 15 /** 16 * 返回泛型数据,自定义类型 17 */ 18 private T content; 19 20 public CommonResp() { 21 } 22 23 public CommonResp(T content) { 24 this.content = content; 25 } 26 27 public boolean getSuccess() { 28 return success; 29 } 30 31 public void setSuccess(boolean success) { 32 this.success = success; 33 } 34 35 public String getMessage() { 36 return message; 37 } 38 39 public void setMessage(String message) { 40 this.message = message; 41 } 42 43 public T getContent() { 44 return content; 45 } 46 47 public void setContent(T content) { 48 this.content = content; 49 } 50 51 @Override 52 public String toString() { 53 final StringBuffer sb = new StringBuffer("CommonResp{"); 54 sb.append("success=").append(success); 55 sb.append(", message='").append(message).append('\''); 56 sb.append(", content=").append(content); 57 sb.append('}'); 58 return sb.toString(); 59 } 60 }CommonResp.java
修改MemberController.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.member.controller; 2 3 4 import com.zihans.train.common.resp.CommonResp; 5 import com.zihans.train.member.req.MemberRegisterReq; 6 import com.zihans.train.member.service.MemberService; 7 import jakarta.annotation.Resource; 8 import org.springframework.web.bind.annotation.GetMapping; 9 import org.springframework.web.bind.annotation.PostMapping; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 import org.springframework.web.bind.annotation.RestController; 12 13 @RestController 14 @RequestMapping("/member") 15 public class MemberController { 16 17 @Resource 18 private MemberService memberService; 19 20 @GetMapping("/count") 21 public CommonResp<Integer> count() { 22 int count = memberService.count(); 23 // CommonResp<Integer> commonResp = new CommonResp<>(); 24 // commonResp.setContent(count); 25 // return commonResp; 26 return new CommonResp<>(count); 27 } 28 29 @PostMapping("/register") 30 public CommonResp<Long> register(MemberRegisterReq req) { 31 long register = memberService.register(req); 32 // CommonResp<Long> commonResp = new CommonResp<>(); 33 // commonResp.setContent(register); 34 // return commonResp; 35 return new CommonResp<>(register); 36 } 37 }MemberController.java
测试成功!
为项目增加统一异常处理
common模块中新增controller包,用于拦截接口,创建ControllerExceptionHandler.java
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.jiawa.train.common.controller; 2 3 import com.jiawa.train.common.resp.CommonResp; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.web.bind.annotation.ControllerAdvice; 7 import org.springframework.web.bind.annotation.ExceptionHandler; 8 import org.springframework.web.bind.annotation.ResponseBody; 9 10 /** 11 * 统一异常处理、数据预处理等 12 */ 13 @ControllerAdvice 14 public class ControllerExceptionHandler { 15 16 private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class); 17 18 /** 19 * 所有异常统一处理 20 * @param e 21 * @return 22 */ 23 @ExceptionHandler(value = Exception.class) 24 @ResponseBody 25 public CommonResp exceptionHandler(Exception e) throws Exception { 26 CommonResp commonResp = new CommonResp(); 27 LOG.error("系统异常:", e); 28 commonResp.setSuccess(false); 29 // commonResp.setMessage("系统出现异常,请联系管理员"); 30 commonResp.setMessage(e.getMessage()); 31 return commonResp; 32 } 33 34 }ControllerExceptionHandler.java
使用自定义异常来解决业务异常
common模块中新增exception包,用于自定义异常。新增枚举类BusinessExceptionEnum类用于枚举异常
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.common.exception; 2 3 public enum BusinessExceptionEnum { 4 5 MEMBER_MOBILE_EXIST("手机号已注册"); 6 7 private String desc; 8 9 BusinessExceptionEnum(String desc) { 10 this.desc = desc; 11 } 12 13 public String getDesc() { 14 return desc; 15 } 16 17 public void setDesc(String desc) { 18 this.desc = desc; 19 } 20 21 @Override 22 public String toString() { 23 return "BusinessExceptionEnum{" + 24 "desc='" + desc + '\'' + 25 "} " + super.toString(); 26 } 27 }BusinessExceptionEnum.java
新建一个BusinessException类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.jiawa.train.common.exception; 2 3 public class BusinessException extends RuntimeException { 4 5 private BusinessExceptionEnum e; 6 7 public BusinessException(BusinessExceptionEnum e) { 8 this.e = e; 9 } 10 11 public BusinessExceptionEnum getE() { 12 return e; 13 } 14 15 public void setE(BusinessExceptionEnum e) { 16 this.e = e; 17 } 18 19 }BusinessException.java
MemberService.java调整以下字段
if (CollUtil.isNotEmpty(list)) { // return list.get(0).getId(); - throw new RuntimeException("手机号已注册"); + throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_EXIST); }
ControllerExceptionHandler.java的未知异常全部返回“系统出现异常,请联系管理员”(正式开发出现这句话说明系统有bug)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /** 2 * 全部异常统一处理 3 * @param e 4 * @return 5 */ 6 @ExceptionHandler(value = Exception.class) 7 @ResponseBody 8 public CommonResp exceptionHandler(Exception e) { 9 CommonResp commonResp = new CommonResp(); 10 LOG.error("系统异常:", e); 11 commonResp.setSuccess(false); 12 commonResp.setMessage("系统出现异常,请联系管理员"); 13 //commonResp.setMessage(e.getMessage()); 14 return commonResp; 15 } 16 17 /** 18 * 业务异常统一处理 19 * @param e 20 * @return 21 */ 22 @ExceptionHandler(value = BusinessException.class) 23 @ResponseBody 24 public CommonResp exceptionHandler(BusinessException e) { 25 CommonResp commonResp = new CommonResp(); 26 LOG.error("业务异常:", e); 27 commonResp.setSuccess(false); 28 //commonResp.setMessage("系统出现异常,请联系管理员"); 29 commonResp.setMessage(e.getE().getDesc()); 30 return commonResp; 31 }ControllerExceptionHandler.java
BusinessException.java新增以下语句,业务一场不写入堆栈信息,提高性能。
@Override public Throwable fillInStackTrace() { return this; }
集成校验框架Validation
Spring Boot Validation是Spring Boot整合了Hibernate Validation的一个框架,其核心是HibernateValidation,
此框架的作用:检验客户端向服务器端提交的请求参数的基本格式是否合法
常用的检查注解有:
- @NotNull:不允许为null值可用于任何类型的参数
- @NotEmpty:不允许为空字符串,即长度为0的字符串仅用于检查字符串类型的参数
- @NotBlank:不允许为空白的字符串,即仅由空格或TAB制表位或换行组成的值,仅用于检查字符串类型的参数
- @Length:限制字符串的长度
- @Pattern:通过正则表达式检查字符串的格式,此注解的regexp属性就是定义正则表达式的属性,仅用于检查字符串类型的参数
- @Min:限制整型数值的最小值,仅用于检查整型数值参数
- @Max:限制整型数值的最大值,仅用于检查整型数值参数
- @Range:限制整型数值的取值区间,默认最小值为0,最大值为long的上限值,仅用于检查整型数值参数
所有检查注解都有message属性,用于配置检查失败时的提示文本。
每个被检查参数可以同时添加多个检查注解!
common模块添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
在MemberRegisterReq.java添加注解
@NotBlank(message = "【手机号】不能为空") private String mobile;
同时MemberController.java增加@Valid注解
- public CommonResp<Long> register(MemberRegisterReq req) { + public CommonResp<Long> register(@Valid MemberRegisterReq req)
ControllerExceptionHandler.java新增校验异常处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /** 2 * 校验异常处理 3 * @param e 4 * @return 5 */ 6 @ExceptionHandler(value = BindException.class) 7 @ResponseBody 8 public CommonResp exceptionHandler(BindException e) { 9 CommonResp commonResp = new CommonResp(); 10 LOG.error("校验异常:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); 11 commonResp.setSuccess(false); 12 //commonResp.setMessage("系统出现异常,请联系管理员"); 13 commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); 14 return commonResp; 15 }ControllerExceptionHandler.java
雪花算法生成id
精确到毫秒不能满足高并发下多请求状况。
自增ID不适合分布式数据库、分表分库场景,适合小型项目
UUID会影响索引效率,因为UUID是无序的,用一堆无序的ID来构建一个有序的索引目录,性能上肯定有问题的
雪花算法由Twitter公司在2014年开源scala语言版本
雪花算法在同一毫秒时间戳下,可以生成2^12=4096个不重复id
雪花算法问题
- 数据中心,机器ID怎么设置
1.利用redis自增序列
2.利用数据库,为每台机器分配workId,保存ip和workId的关系
- 时钟回拨
—个应用─般是多节点,可停止节点,等时间过了,再启动
创建通用SnowUtil.java类,用来封装hutool雪花算法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.zihans.train.common.util; 2 3 import cn.hutool.core.util.IdUtil; 4 5 /** 6 * 封装hutool雪花算法 7 */ 8 public class SnowUtil { 9 10 private static long dataCenterId = 1; //数据中心 11 private static long workerId = 1; //机器标识 12 13 public static long getSnowflakeNextId() { 14 return IdUtil.getSnowflake(workerId, dataCenterId).nextId(); 15 } 16 17 public static String getSnowflakeNextIdStr() { 18 return IdUtil.getSnowflake(workerId, dataCenterId).nextIdStr(); 19 } 20 }SnowUtil.java
MemberService设置id
member.setId(SnowUtil.getSnowflakeNextId());
标签:java,CommonResp,Spring,train,Alibaba,member,Springbooot3.0,return,public From: https://www.cnblogs.com/szhNJUPT/p/17306113.html