目录

[TOC]

项目地址

后端:https://github.com/liyupi/sql-father-backend-public

前端:https://github.com/liyupi/sql-father-frontend-public

synchronized加锁

String

使用String中的intern,将指定字符串作为锁,将用户的账号锁住,避免用户账号重复注册

String.intern()是一个Native方法,它的作用是:如果字符常量池中已经包含一个等于此String对象的字符串,则返回常量池中字符串的引用,否则,将新的字符串放入常量池,并返回新字符串的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public long userRegister(String userAccount) {

//给用户账号加锁,查询用户是否重复
synchronized (userAccount.intern()) {
// 账户不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
long count = userMapper.selectCount(queryWrapper);
if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
}
// 3. 插入数据
....
return user.getId();
}
}

AOP使用

自定义注解认证

使用AOP+自定义注解做校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 权限校验
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {

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

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

}
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
package com.yupi.sqlfather.aop;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.xiaofei.annotation.AuthCheck;
import com.xiaofei.common.ErrorCode;
import com.xiaofei.exception.BusinessException;
import com.xiaofei.model.entity.User;
import com.xiaofei.service.UserService;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
* 权限校验 AOP
*/
@Aspect
@Component
public class AuthInterceptor {

@Resource
private UserService userService;

/**
* 执行拦截
*/
@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();

//下面是具体的做校验的内容,可以根据实际情况进行修改---start

// 当前登录用户
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);
}
}

//上面是具体的做校验的内容,可以根据实际情况进行修改---end

// 通过权限校验,放行【这里放行就是执行原代码,上面的代码是给指定的方法做切面】
return joinPoint.proceed();
}
}
1
2
3
4
5
6
7
8
/**
* 更新(仅管理员)
*/
@PostMapping("/update")
@AuthCheck(mustRole = "admin")
public void update() {
...... 具体业务
}

日志切面

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
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
* 请求响应日志 AOP
**/
@Aspect
@Component
@Slf4j
public class LogInterceptor {

/**
* 执行拦截
*/
@Around("execution(* com.xiaofei.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 获取请求路径
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
// 生成请求唯一 id
String requestId = UUID.randomUUID().toString();
String url = httpServletRequest.getRequestURI();
// 获取请求参数
Object[] args = point.getArgs();
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
// 输出请求日志
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
httpServletRequest.getRemoteHost(), reqParam);
// 执行原方法
Object result = point.proceed();
// 输出响应日志
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
return result;
}
}

跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* 全局跨域配置
*/
@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("*");
}
}

设计模式

工厂 + 单例模式

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
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 指定方法工厂
* 工厂 + 单例模式,降低开销
*
* @author https://github.com/liyupi
*/
public class SQLDialectFactory {

/**
* className => 方言实例映射
*/
private static final Map<String, SQLDialect> DIALECT_POOL = new ConcurrentHashMap<>();

/**
* 构造器私有化,单例模式
*/
private SQLDialectFactory() {
}

/**
* 获取指定类的途径,隐藏构造过程,直接让使用者获取到创建浩的对象,工厂模式
*/
public static SQLDialect getDialect(String className) {
//具体获取方法 ......
}
}

策略模式

具体使用可以看(设计模式案例):http://naste.top/posts/3353504920.html

  • 将每种生成类型定义为一个 Builder(core/builder 目录)
  • 其中,对于 SQL 代码生成器( SqlBuilder),使用方言来支持不同的数据库类型(策略模式),并使用单例模式 + 工厂模式创建方言实例
  • 对于 Java、前端代码生成器(JavaCodeBuilder、FrontendCodeBuilder),使用 FreeMarker 模板引擎来生成

门面模式

门面模式就是让客户端将只请求一次即可获取所有信息,比如代码生成可能需要生成Java、vue、JavaScript等代码,如果不适用门面模式,就需要客户端分别调用生成Java、vue、JavaScript的接口来获取相应的信息,但是使用门面模式后,只需要调用一次方法,然后改方法去操作各个生成方法,再统一封装返回,就是在服务器端,提供一个对外的方法,改方法去统一调用生成的方法,将结果返回给客户端,不需要客户端一一调用,客户端只需要调用一次

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
106
107
108
109
110
111
112
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.xiaofei.core.builder.FrontendCodeBuilder;
import com.xiaofei.core.schema.SchemaException;
import com.xiaofei.core.schema.TableSchema;
import com.xiaofei.core.schema.TableSchema.Field;
import com.xiaofei.core.builder.DataBuilder;
import com.xiaofei.core.builder.JavaCodeBuilder;
import com.xiaofei.core.builder.JsonBuilder;
import com.xiaofei.core.builder.SqlBuilder;
import com.xiaofei.core.model.vo.GenerateVO;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

/**
* 集中数据生成器,将多种生成方法全部集成在该方法中,进行统一调用,统一返回
*
* 门面模式,统一生成
*/
@Component
@Slf4j
public class GeneratorFacade {

/**
* 生成所有内容
*
* @param tableSchema
* @return
*/
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;
}

/**
* 验证 schema
*
* @param tableSchema 表概要
*/
public static void validSchema(TableSchema tableSchema) {
if (tableSchema == null) {
throw new SchemaException("数据为空");
}
String tableName = tableSchema.getTableName();
if (StringUtils.isBlank(tableName)) {
throw new SchemaException("表名不能为空");
}
Integer mockNum = tableSchema.getMockNum();
// 默认生成 20 条
if (tableSchema.getMockNum() == null) {
tableSchema.setMockNum(20);
mockNum = 20;
}
if (mockNum > 100 || mockNum < 10) {
throw new SchemaException("生成条数设置错误");
}
List<Field> fieldList = tableSchema.getFieldList();
if (CollectionUtils.isEmpty(fieldList)) {
throw new SchemaException("字段列表不能为空");
}
for (Field field : fieldList) {
validField(field);
}
}

/**
* 校验字段
*/
public static void validField(Field field) {
String fieldName = field.getFieldName();
String fieldType = field.getFieldType();
if (StringUtils.isBlank(fieldName)) {
throw new SchemaException("字段名不能为空");
}
if (StringUtils.isBlank(fieldType)) {
throw new SchemaException("字段类型不能为空");
}
}

}