Featured image of post 开源Springboot项目 Sql之父

开源Springboot项目 Sql之父

开源Springboot项目-sql之父

  1. 项目引用依赖
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
FreeMarker 是一款模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本(HTML 网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
生成接口文档使用的依赖库
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.12</version>
</dependency>
阿里巴巴出的数据库连接池
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>
阿里巴巴出的好用的excel库
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
java工具的增强版
        

2.项目主要结构 学习的地方: 1.注解的使用:自定义一个权限检测注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Target(ElementType.METHOD) 函数级别的
@Retention(RetentionPolicy.RUNTIME) 运行时
public @interface AuthCheck {

    /**
     * 有任何一个角色
     *
     * @return
     */
    String[] anyRole() default "";

    /**
     * 必须有某个角色
     *
     * @return
     */
    String mustRole() default "";

}

2.使用Aop进行权限控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Around("@annotation(authCheck)")
 public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        List<String> anyRole = Arrays.stream(authCheck.anyRole()).filter(StringUtils::isNotBlank).collect(Collectors.toList());
        String mustRole = authCheck.mustRole();
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 当前登录用户
        User user = userService.getLoginUser(request);
        // 拥有任意权限即通过
        /*
        下列为代码逻辑可以更改  
        */
        if (CollectionUtils.isNotEmpty(anyRole)) {
            String userRole = user.getUserRole();
            if (!anyRole.contains(userRole)) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        }
        // 必须有所有权限才通过
        if (StringUtils.isNotBlank(mustRole)) {
            String userRole = user.getUserRole();
            if (!mustRole.equals(userRole)) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        }
        // 通过权限校验,放行
        return joinPoint.proceed();
    }
1
 @Around("execution(* com.yupi.sqlfather.controller.*.*(..))")

3.全局配置 主要配置FreeMarker,Knife4j和MybatisPlus具体配置内容可根据需要进行百度 下面展示跨域配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 覆盖所有请求
        registry.addMapping("/**")
                // 允许发送 Cookie
                .allowCredentials(true)
                // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

针对每一个配置都需要写@Configuration才能注入到SpringBoot项目当中 4.通用返回类配置 实现一个通用返回类,符合RestfulApi风格的接口 定义code,data和返回信息,对于data需要进行泛型处理

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
@Data lomok风格自动生成Get和Set
public class BaseResponse<T> implements Serializable {

    private int code;

    private T data;

    private String message;

    public BaseResponse(int code, T data, String message) {
        this.code = code;
        this.data = data;
        this.message = message;
    }

    public BaseResponse(int code, T data) {
        this(code, data, "");
    }

    public BaseResponse(ErrorCode errorCode) {
        this(errorCode.getCode(), null, errorCode.getMessage());
    }
}
上述代码中使用的Errorcode则是一个枚举类,枚举可能发生的错误代码和提示信息
public enum ErrorCode {

    SUCCESS(0, "ok"),
    PARAMS_ERROR(40000, "请求参数错误"),
    NOT_LOGIN_ERROR(40100, "未登录"),
    NO_AUTH_ERROR(40101, "无权限"),
    NOT_FOUND_ERROR(40400, "请求数据不存在"),
    FORBIDDEN_ERROR(40300, "禁止访问"),
    SYSTEM_ERROR(50000, "系统内部异常"),
    OPERATION_ERROR(50001, "操作失败");

    /**
     * 状态码
     */
    private final int code;

    /**
     * 信息
     */
    private final String message;

    ErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}
分页也需要一个单独的类来进行控制,其中主要包含属性为页号,页面大小,总数等等

定义一个返回的工具类,工具类中主要实现以BaseRespose为对象的方法,
public class ResultUtils {

    /**
     * 成功
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>(0, data, "ok");
    }

    /**
     * 失败
     *
     * @param errorCode
     * @return
     */
    public static BaseResponse error(ErrorCode errorCode) {
        return new BaseResponse<>(errorCode);
    }

    /**
     * 失败
     *
     * @param code
     * @param message
     * @return
     */
    public static BaseResponse error(int code, String message) {
        return new BaseResponse(code, null, message);
    }

    /**
     * 失败
     *
     * @param errorCode
     * @return
     */
    public static BaseResponse error(ErrorCode errorCode, String message) {
        return new BaseResponse(errorCode.getCode(), null, message);
    }

5.全局异常配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
自定义异常类,自定义的异常类主要继承RuntimeException
public class BusinessException extends RuntimeException {

    private final int code;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.code = errorCode.getCode();
    }

    public BusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.code = errorCode.getCode();
    }

    public int getCode() {
        return code;
    }
}
定义全局异常处理方法
@RestControllerAdvice 在捕获各种异常是使用
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class) 在出现异常时使用用户自定义异常类进行处理
    public BaseResponse<?> businessExceptionHandler(BusinessException e) {
        log.error("businessException: " + e.getMessage(), e);
        return ResultUtils.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(RuntimeException.class) 运行时异常,中断,并报错。
    public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
        log.error("runtimeException", e);````
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage());
    }
}

6.业务逻辑学习

  • StringUtils.isAnyBlank 只要有一个为空则显示为True,所以可以放到检测用户登录时进行校验
  • synchronized (userAccount.intern()) {}是为了确保在synchronized代码块中对相同userAccount值的锁定是有效的。通过将userAccount字符串调用intern()方法,确保获取的字符串是字符串池中的引用,从而使得不同线程可以正确地对相同userAccount值进行锁定和同步操作。
  • String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());MD5加密算法,加密用户名密码。
  • // 3. 记录用户的登录态 request.getSession().setAttribute(USER_LOGIN_STATE, user); 记住用户的登陆状态
  • 获取枚举类中的值列表
1
2
3
    public static List<String> getValues() {
        return Arrays.stream(values()).map(FieldTypeEnum::getValue).collect(Collectors.toList());
    }
  • BeanUtils.copyProperties(user, userVO) 源对象和目标对象。它将源对象中的属性值复制到目标对象中对应的属性上,通过属性名称进行匹配。属性名称和类型必须在源对象和目标对象中相匹配,否则会抛出异常。
  • StringUtils.isNotBlank(***) 不为空 对于字符串的判断可以多用已完成的工具,避免重复造轮子还不准,上述主要使用StringUtils
  • Optional.ofNullable 判断对象是否为空
  • 针对工厂模式的运用也是主要的特色之一,针对随机,递增,默认等方式都实现了一个数据生成器接口,而工厂模式,获取实现了这个数据生成器接口的实例。
  • public static SQLDialect getDialect(String className) { SQLDialect dialect = DIALECT_POOL.get(className); if (null == dialect) { synchronized (className.intern()) { dialect = DIALECT_POOL.computeIfAbsent(className, key -> { try { return (SQLDialect) Class.forName(className).newInstance(); } catch (Exception e) { throw new BusinessException(ErrorCode.SYSTEM_ERROR); } }); } } return dialect; } 上述是一个单例模式的实现,运用在实际项目中
1
2
3
* 主要需要理解到DTO的设计,不要一直使用最初的实体类,因为可能会有很多废弃的属性没有实际作用,
* 内部类的设计
* 

@Data public class JavaObjectGenerateDTO {

/**
 * 类名
 */
private String className;

/**
 * 对象名
 */
private String objectName;

/**
 * 列信息列表
 */
private List<FieldDTO> fieldList;

/**
 * 列信息
 */
@Data
public static class FieldDTO {
    /**
     * set 方法名
     */
    private String setMethod;

    /**
     * 值
     */
    private String value;
}

}

1
2
* 枚举类的应用
* 

public enum MockTypeEnum {

NONE("不模拟"),
INCREASE("递增"),
FIXED("固定"),
RANDOM("随机"),
RULE("规则"),
DICT("词库");

private final String value;

MockTypeEnum(String value) {
    this.value = value;
}

/**
 * 获取值列表
 *
 * @return
 */
public static List<String> getValues() {
    return Arrays.stream(values()).map(MockTypeEnum::getValue).collect(Collectors.toList());
}

/**
 * 根据 value 获取枚举
 *
 * @param value
 * @return
 */
public static MockTypeEnum getEnumByValue(String value) {
    if (StringUtils.isBlank(value)) {
        return null;
    }
    for (MockTypeEnum mockTypeEnum : MockTypeEnum.values()) {
        if (mockTypeEnum.value.equals(value)) {
            return mockTypeEnum;
        }
    }
    return null;
}

public String getValue() {
    return value;
}

}

1
2
* 门面模式使用,向客户端提供一个高层次接口,避免客户端大量调用子系统使整个系统的耦合性高。
* 
public static GenerateVO generateAll(TableSchema tableSchema) {
    // 校验
    validSchema(tableSchema);
    SqlBuilder sqlBuilder = new SqlBuilder();
    // 构造建表 SQL
    String createSql = sqlBuilder.buildCreateTableSql(tableSchema);
    int mockNum = tableSchema.getMockNum();
    // 生成模拟数据
    List<Map<String, Object>> dataList = DataBuilder.generateData(tableSchema, mockNum);
    // 生成插入 SQL
    String insertSql = sqlBuilder.buildInsertSql(tableSchema, dataList);
    // 生成数据 json
    String dataJson = JsonBuilder.buildJson(dataList);
    // 生成 java 实体代码
    String javaEntityCode = JavaCodeBuilder.buildJavaEntityCode(tableSchema);
    // 生成 java 对象代码
    String javaObjectCode = JavaCodeBuilder.buildJavaObjectCode(tableSchema, dataList);
    // 生成 typescript 类型代码
    String typescriptTypeCode = FrontendCodeBuilder.buildTypeScriptTypeCode(tableSchema);
    // 封装返回
    GenerateVO generateVO = new GenerateVO();
    generateVO.setTableSchema(tableSchema);
    generateVO.setCreateSql(createSql);
    generateVO.setDataList(dataList);
    generateVO.setInsertSql(insertSql);
    generateVO.setDataJson(dataJson);
    generateVO.setJavaEntityCode(javaEntityCode);
    generateVO.setJavaObjectCode(javaObjectCode);
    generateVO.setTypescriptTypeCode(typescriptTypeCode);
    return generateVO;
}
1
2
3
4
5
6
* `CollectionUtils.isEmpty(dataList)` 判断列表是否为空或
* `DateUtils.parseDate(str, DATE_PATTERNS);` 第二个参数为一个字符串列表
*  `public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>(0, data, "ok");
    }`对于泛型的使用
*
Licensed under CC BY-NC-SA 4.0