目录
[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, "账号重复"); } .... 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;
@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(); 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 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;
@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(); 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("/**") .allowCredentials(true) .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;
public class SQLDialectFactory {
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 {
public static GenerateVO generateAll(TableSchema tableSchema) { validSchema(tableSchema); SqlBuilder sqlBuilder = new SqlBuilder(); String createSql = sqlBuilder.buildCreateTableSql(tableSchema); int mockNum = tableSchema.getMockNum(); List<Map<String, Object>> dataList = DataBuilder.generateData(tableSchema, mockNum); String insertSql = sqlBuilder.buildInsertSql(tableSchema, dataList); String dataJson = JsonBuilder.buildJson(dataList); String javaEntityCode = JavaCodeBuilder.buildJavaEntityCode(tableSchema); String javaObjectCode = JavaCodeBuilder.buildJavaObjectCode(tableSchema, dataList); 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; }
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(); 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("字段类型不能为空"); } }
}
|