后端代码初始化¶
这一个系列主要是做一个高数据并发下场景下的短链平台,在文章中记录一下自己踩的坑以及当中自己不熟悉的点
后端整体使用的是微服务,在第一天的时候搞了一下通用的工具类和一些公用类的封装,包括一些mysql,redis,nacos等的安装(这些就不一一赘述了)。
代码生成器¶
首先分享一个个人觉得比较好用的mybatis-plus的
package org.example.db;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class MyBatisPlusGenerator {
public static void main(String[] args) {
//1. 全局配置
GlobalConfig config = new GlobalConfig();
// 是否支持AR模式
config.setActiveRecord(true)
// 作者
.setAuthor("dkw")
// 生成路径,最好使用绝对路径,window路径是不一样的
//TODO TODO TODO TODO
.setOutputDir("文件生成地址")
// 文件覆盖
.setFileOverride(true)
// 主键策略
.setIdType(IdType.AUTO)
.setDateType(DateType.ONLY_DATE)
// 设置生成的service接口的名字的首字母是否为I,默认Service是以I开头的
.setServiceName("%sService")
//实体类结尾名称
.setEntityName("%sDO")
//生成基本的resultMap
.setBaseResultMap(true)
//不使用AR模式
.setActiveRecord(false)
//生成基本的SQL片段
.setBaseColumnList(true);
//2. 数据源配置
DataSourceConfig dsConfig = new DataSourceConfig();
// 设置数据库类型
dsConfig.setDbType(DbType.MYSQL)
.setDriverName("com.mysql.cj.jdbc.Driver")
//TODO TODO TODO TODO
.setUrl("数据库连接地址")
.setUsername("账号")
.setPassword("密码");
//3. 策略配置globalConfiguration中
StrategyConfig stConfig = new StrategyConfig();
//全局大写命名
stConfig.setCapitalMode(true)
// 数据库表映射到实体的命名策略
.setNaming(NamingStrategy.underline_to_camel)
//使用lombok
.setEntityLombokModel(true)
//使用restcontroller注解
.setRestControllerStyle(true)
// 生成的表, 支持多表一起生成,以数组形式填写
//TODO TODO TODO TODO
.setInclude("account","traffic","traffic_task");
//4. 包名策略配置
PackageConfig pkConfig = new PackageConfig();
pkConfig.setParent("org.example")
.setMapper("mapper")
.setService("service")
.setController("controller")
.setEntity("entity")
.setXml("mapper");
//5. 整合配置
AutoGenerator ag = new AutoGenerator();
ag.setGlobalConfig(config)
.setDataSource(dsConfig)
.setStrategy(stConfig)
.setPackageInfo(pkConfig);
//6. 执行操作
ag.execute();
System.out.println("相关代码生成完毕");
}
}
依赖版本控制¶
这个对我来说比较不太熟悉,所以记录一下
首先是在最外层做各个依赖的版本控制
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!-- 限定版本 -->
<spring.boot.version>2.5.5</spring.boot.version>
<spring.cloud.version>2020.0.4</spring.cloud.version>
<alibaba.cloud.version>2021.1</alibaba.cloud.version>
<mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
<lombok.version>1.18.16</lombok.version>
<commons.lang3.version>3.9</commons.lang3.version>
<commons.codec.version>1.15</commons.codec.version>
<xxl-job.version>2.3.0</xxl-job.version>
<aliyun.oss.version>3.10.2</aliyun.oss.version>
<captcha.version>1.1.0</captcha.version>
<redission.version>3.10.1</redission.version>
<jwt.version>0.7.0</jwt.version>
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
<junit.version>4.12</junit.version>
<druid.version>1.1.16</druid.version>
<!--跳过单元测试-->
<skipTests>true</skipTests>
<docker.image.prefix>dcloud</docker.image.prefix>
</properties>
<!--锁定版本 这里并不会安装依赖-->
<dependencyManagement>
<dependencies>
<!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.3.3.RELEASE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies/Hoxton.SR8-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies/2.2.1.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<!--https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.16-->
<!--scope=provided,说明它只在编译阶段生效,不需要打入包中, Lombok在编译期将带Lombok注解的Java文件正确编译为完整的Class文件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<!--用于加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version>
</dependency>
<!--验证码kaptcha依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>kaptcha-spring-boot-starter</artifactId>
<version>${captcha.version}</version>
</dependency>
<!--阿里云oss-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redission.version}</version>
</dependency>
<!--https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-spring-boot-starter-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
各个模块由于使用的依赖基本大差不差,所以只需要在一个公用模块中配置好依赖,在其他模块中直接引入即可
公用模块
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--项目中添加 spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--redis客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--用于加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!--redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<!--Hoxton.M2版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错,所以加入spring-cloud-loadbalancer依赖 并且在nacos中排除ribbon依赖,不然loadbalancer无效 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--配置中心, 留坑,后续用的时候再讲,解决方式,看springboot官方文档版本更新说明-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--Feign远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--限流依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--限流持久化到nacos-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--Springboot项目整合spring-kafka依赖包配置-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!--引入AMQP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--spring cache依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core -->
<!--分布式调度-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
其他模块中引入
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>公用模块名</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions> // 用于排除一些不需要的模块
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
公用类封装¶
主要介绍一下全局异常处理类,因为自己对这个之前不太熟悉。我理解下的为什么要封装全局异常处理类,原始的异常所抛出的一些信息其实不太容易定位问题,封装全局异常处理类将问题发生在具体的那一部分业务下暴露出来,方便排查定位。
全局异常处理类
package org.example.exception;
import lombok.extern.slf4j.Slf4j;
import org.example.utils.JsonData;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理类,用于统一处理 Controller 层抛出的异常,并返回统一格式的 JSON 响应
*/
//@ControllerAdvice
@RestControllerAdvice
@Slf4j
public class CustomExceptionHandler {
// ControllerAdvice-定义全局的异常处理逻辑以及数据绑定逻辑,对控制器层面出现的异常进行统一处理
// RestControllerAdvice-在 RESTful API 中,对异常进行统一处理并返回 JSON 格式的错误响应
// JsonData就是自定义的接口返回结果封装方法
@ExceptionHandler(value = Exception.class) // 捕获所有类型的异常(Exception.class)
public JsonData handler(Exception e) {
//业务异常(BizException):
//若捕获的异常是BizException类型,从异常中提取错误码(code)和错误信息(msg)。
//通过throw new BizException()抛出业务异常,这是为什么要自己封装BizException类-需要自定义业务异常code和信息
//使用JsonData.buildCodeAndMsg()方法封装成自定义的 JSON 响应结构。
//日志记录错误详情(使用log.error)。
//系统异常(其他异常):
//若捕获的是其他类型的异常(如数据库异常、空指针异常等),统一返回 “系统异常” 错误信息。
//使用JsonData.buildError()方法封装错误响应。
//日志记录完整的异常堆栈(便于排查问题)。
if (e instanceof BizException) {
BizException bizException = (BizException) e;
log.error("业务异常{}",e);
return JsonData.buildCodeAndMsg(bizException.getCode(),bizException.getMsg());
} else {
log.error("系统异常{}",e);
return JsonData.buildError("系统异常{}");
}
}
}
异常内部处理以及枚举类
package org.example.exception;
import lombok.Data;
import org.example.enums.BizCodeEnum;
@Data
public class BizException extends RuntimeException{
private int code;
private String msg;
public BizException(Integer code, String message) {
// 在 Java 中,子类的构造函数必须先调用父类的构造函数,这是 Java 语言的规则。
// 调用父类RuntimeException的构造函数并将message传入进去
super(message);
this.code = code;
this.msg = message;
}
public BizException(BizCodeEnum bizCodeEnum){
// 调用父类RuntimeException的构造函数并将message传入进去
super(bizCodeEnum.getMessage());
this.code = bizCodeEnum.getCode();
this.msg = bizCodeEnum.getMessage();
}
}
package org.example.enums;
import lombok.Getter;
/**
* @Description 状态码定义约束,共6位数,前三位代表服务,后3位代表接口
* 比如 商品服务210,购物车是220、用户服务230,403代表权限
*
**/
public enum BizCodeEnum {
/**
* 短链分组
*/
GROUP_REPEAT(23001,"分组名重复"),
GROUP_OPER_FAIL(23503,"分组名操作失败"),
GROUP_NOT_EXIST(23404,"分组不存在"),
/**
*验证码
*/
CODE_TO_ERROR(240001,"接收号码不合规"),
CODE_LIMITED(240002,"验证码发送过快"),
CODE_ERROR(240003,"验证码错误"),
CODE_CAPTCHA_ERROR(240101,"图形验证码错误"),
/**
* 账号
*/
ACCOUNT_REPEAT(250001,"账号已经存在"),
ACCOUNT_UNREGISTER(250002,"账号不存在"),
ACCOUNT_PWD_ERROR(250003,"账号或者密码错误"),
ACCOUNT_UNLOGIN(250004,"账号未登录"),
/**
* 短链
*/
SHORT_LINK_NOT_EXIST(260404,"短链不存在"),
/**
* 订单
*/
ORDER_CONFIRM_PRICE_FAIL(280002,"创建订单-验价失败"),
ORDER_CONFIRM_REPEAT(280008,"订单恶意-重复提交"),
ORDER_CONFIRM_TOKEN_EQUAL_FAIL(280009,"订单令牌缺少"),
ORDER_CONFIRM_NOT_EXIST(280010,"订单不存在"),
/**
* 支付
*/
PAY_ORDER_FAIL(300001,"创建支付订单失败"),
PAY_ORDER_CALLBACK_SIGN_FAIL(300002,"支付订单回调验证签失败"),
PAY_ORDER_CALLBACK_NOT_SUCCESS(300003,"支付宝回调更新订单失败"),
PAY_ORDER_NOT_EXIST(300005,"订单不存在"),
PAY_ORDER_STATE_ERROR(300006,"订单状态不正常"),
PAY_ORDER_PAY_TIMEOUT(300007,"订单支付超时"),
/**
* 流控操作
*/
CONTROL_FLOW(500101,"限流控制"),
CONTROL_DEGRADE(500201,"降级控制"),
CONTROL_AUTH(500301,"认证控制"),
/**
* 流量包操作
*/
TRAFFIC_FREE_NOT_EXIST(600101,"免费流量包不存在,联系客服"),
TRAFFIC_REDUCE_FAIL(600102,"流量不足,扣减失败"),
TRAFFIC_EXCEPTION(600103,"流量包数据异常,用户无流量包"),
/**
* 通用操作码
*/
OPS_REPEAT(110001,"重复操作"),
OPS_NETWORK_ADDRESS_ERROR(110002,"网络地址错误"),
/**
* 文件相关
*/
FILE_UPLOAD_USER_IMG_FAIL(700101,"用户头像文件上传失败");
@Getter
private String message;
@Getter
private int code;
private BizCodeEnum(int code, String message){
this.code = code;
this.message = message;
}
}