开源Springboot项目-sql之父
- 项目引用依赖
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;
}
}
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");
}`对于泛型的使用
*
|