跳转至

后端代码初始化

这一个系列主要是做一个高数据并发下场景下的短链平台,在文章中记录一下自己踩的坑以及当中自己不熟悉的点

后端整体使用的是微服务,在第一天的时候搞了一下通用的工具类和一些公用类的封装,包括一些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;
    }
}