sk-matrix-service 项目 api + services

This commit is contained in:
Shikong 2023-05-08 00:55:39 +08:00
parent 9c8f7fcfc5
commit f54213d8ef
52 changed files with 2151 additions and 10 deletions

View File

@ -16,14 +16,15 @@
<entry name="D:/Repository/maven/org/mapstruct/mapstruct-processor/1.5.3.Final/mapstruct-processor-1.5.3.Final.jar" />
<entry name="D:/Repository/maven/org/mapstruct/mapstruct/1.5.3.Final/mapstruct-1.5.3.Final.jar" />
<entry name="D:/Repository/maven/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar" />
<entry name="D:/Repository/maven/org/projectlombok/lombok-mapstruct-binding/0.2.0/lombok-mapstruct-binding-0.2.0.jar" />
<entry name="D:/Repository/maven/org/springframework/boot/spring-boot-configuration-processor/3.0.1/spring-boot-configuration-processor-3.0.1.jar" />
</processorPath>
<module name="annotation" />
<module name="common" />
<module name="starter" />
<module name="common" />
<module name="auth" />
<module name="orm" />
<module name="api" />
<module name="orm" />
<module name="services" />
<module name="casbin" />
</profile>

View File

@ -36,6 +36,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.skcks.matrix.v2</groupId>
<artifactId>services</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -0,0 +1,78 @@
package cn.skcks.matrix.v2.api.auth;
import cn.skcks.matrix.v2.annotation.web.JsonMapping;
import cn.skcks.matrix.v2.annotation.web.methods.PostJson;
import cn.skcks.matrix.v2.config.swagger.SwaggerConfig;
import cn.skcks.matrix.v2.model.auth.dto.UserLoginDto;
import cn.skcks.matrix.v2.model.auth.dto.UserRegisterDto;
import cn.skcks.matrix.v2.model.auth.vo.UserLoginVo;
import cn.skcks.matrix.v2.model.auth.vo.UserRegisterVo;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import cn.skcks.matrix.v2.services.auth.AuthService;
import cn.skcks.matrix.v2.services.casbin.CasbinRegister;
import cn.skcks.matrix.v2.services.role.RoleService;
import cn.skcks.matrix.v2.utils.json.JsonResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.casbin.jcasbin.main.Enforcer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.regex.Pattern;
import static cn.skcks.matrix.v2.services.casbin.CasbinService.DEFAULT_SYSTEM;
@Tag(name = "用户认证")
@RestController
@JsonMapping("/auth")
@RequiredArgsConstructor
public class AuthController implements CasbinRegister {
private final AuthService authService;
public final static String LOGIN_PERMISSION = "login";
public final static String LOGIN_PERMISSION_NAME = "登录";
@Bean
public GroupedOpenApi authApi() {
return SwaggerConfig.api("Auth", "/auth");
}
@Override
public void register(Enforcer enforcer, List<RouteInfo> routes, RoleService roleService) {
log.info("[casbin] 初始化 {} 权限", LOGIN_PERMISSION_NAME);
roleService.initRole(LOGIN_PERMISSION, LOGIN_PERMISSION_NAME, true);
List<RouteInfo> filterRoutes = routes.stream()
.filter(routeInfo -> Pattern.matches("/auth/(register|login)", routeInfo.requestUrl))
.toList();
CasbinRegister.addPolicies(enforcer, LOGIN_PERMISSION, DEFAULT_SYSTEM, filterRoutes);
}
@Operation(summary = "用户注册")
@PostJson("/register")
public JsonResponse<UserRegisterVo> registerUser(@RequestBody @Validated UserRegisterDto params) {
UserRegisterVo vo = authService.register(params);
if (vo.isStatus()) {
return JsonResponse.success(vo);
} else {
return JsonResponse.error(vo);
}
}
@Operation(summary = "用户登录")
@PostJson("/login")
public JsonResponse<UserLoginVo> registerUser(@RequestBody @Validated UserLoginDto params) {
UserLoginVo vo = authService.login(params);
if (vo.isStatus()) {
return JsonResponse.success(vo);
} else {
return JsonResponse.error(null, vo.getException());
}
}
}

View File

@ -0,0 +1,121 @@
package cn.skcks.matrix.v2.api.role;
import cn.skcks.matrix.v2.annotation.web.JsonMapping;
import cn.skcks.matrix.v2.annotation.web.auth.Auth;
import cn.skcks.matrix.v2.annotation.web.methods.DeleteJson;
import cn.skcks.matrix.v2.annotation.web.methods.GetJson;
import cn.skcks.matrix.v2.annotation.web.methods.PostJson;
import cn.skcks.matrix.v2.annotation.web.methods.PutJson;
import cn.skcks.matrix.v2.config.swagger.SwaggerConfig;
import cn.skcks.matrix.v2.model.role.convert.RoleConvertor;
import cn.skcks.matrix.v2.model.role.dto.*;
import cn.skcks.matrix.v2.model.role.vo.RoleVo;
import cn.skcks.matrix.v2.model.service.ServiceResult;
import cn.skcks.matrix.v2.model.user.convert.UserConvertor;
import cn.skcks.matrix.v2.model.user.vo.UserVo;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.Role;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import cn.skcks.matrix.v2.services.casbin.Permission;
import cn.skcks.matrix.v2.services.role.RoleService;
import cn.skcks.matrix.v2.utils.json.JsonResponse;
import cn.skcks.matrix.v2.utils.page.PageWrapper;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Auth
@Tag(name = "角色")
@RestController
@JsonMapping("/role")
@RequiredArgsConstructor
public class RoleController {
private final RoleService roleService;
private final CasbinService casbinService;
@Bean
public GroupedOpenApi roleApi() {
return SwaggerConfig.api("Role", "/role");
}
@Operation(summary = "添加角色")
@PutJson("/add")
public JsonResponse<Void> addRole(@RequestBody @Validated AddRoleParams params) throws Exception {
ServiceResult<Void> result = roleService.addRole(params.getRoleName(), params.getRole(), params.getSystem(), params.getPermissions());
return result.parseResponse();
}
@Operation(summary = "删除角色")
@DeleteJson("/del")
public JsonResponse<Void> delRole(@RequestBody @Validated DelRoleParams params) throws Exception {
ServiceResult<Void> result = roleService.delRole(params.getRole(), params.getSystem());
return result.parseResponse();
}
@Operation(summary = "查询角色表")
@PostJson("/list")
public JsonResponse<PageWrapper<RoleVo>> roles(@RequestBody @Validated GetRoleParams params) {
PageInfo<Role> roles = roleService.getRoles(params.getPage(), params.getSize(), false);
return JsonResponse.success(PageWrapper.of(RoleConvertor.INSTANCE.daoToVo(roles)));
}
@Operation(summary = "查询角色表(所有)")
@GetJson("/list/all")
public JsonResponse<List<RoleVo>> roles() {
PageInfo<Role> roles = roleService.getRoles(0, 0, true);
return JsonResponse.success(RoleConvertor.INSTANCE.daoToVo(roles).getList());
}
@PostJson("/user/grant")
@Operation(summary = "授予用户角色")
public JsonResponse<Boolean> grantRoleForUser(@RequestBody @Validated GrantAndRevokeRoleForUserDto dto) {
try {
roleService.grantRoleForUser(dto.getUserId(), dto.getRole(), dto.getSystem());
return JsonResponse.success(true);
} catch (Exception e) {
return JsonResponse.error(false, e.getMessage());
}
}
@DeleteJson("/user/revoke")
@Operation(summary = "撤销用户角色")
public JsonResponse<Boolean> revokeRoleForUser(@RequestBody @Validated GrantAndRevokeRoleForUserDto dto) {
try {
roleService.revokeRoleForUser(dto.getUserId(), dto.getRole(), dto.getSystem());
return JsonResponse.success(true);
} catch (Exception e) {
return JsonResponse.error(false, e.getMessage());
}
}
@GetJson("/user/roles")
@Operation(summary = "获取用户所拥有的角色列表")
public JsonResponse<List<String>> getUserRoles(@ParameterObject @Validated GetUserRoles dto) {
return JsonResponse.success(roleService.getUserRoles(dto.getUserId(), dto.getSystem()));
}
@GetJson("/user/list")
@Operation(summary = "获取角色下所有的用户列表")
public JsonResponse<List<UserVo>> getRoleUsers(@ParameterObject @Validated GetRoleUsers dto) {
List<UserVo> vos = roleService.getRoleUsers(dto.getRole(), dto.getSystem())
.parallelStream()
.map(UserConvertor.INSTANCE::daoToVo)
.toList();
return JsonResponse.success(vos);
}
@GetJson("/user/permissions")
@Operation(summary = "用户在某个系统下所拥有的权限列表")
public JsonResponse<List<Permission>> getUserPermissions(@ParameterObject @Validated GetUserPermissions dto) {
return JsonResponse.success(casbinService.getUserPermission(dto.getUserId(), dto.getSystem()));
}
}

View File

@ -0,0 +1,91 @@
package cn.skcks.matrix.v2.api.test;
import cn.hutool.core.util.IdUtil;
import cn.skcks.matrix.v2.annotation.web.JsonMapping;
import cn.skcks.matrix.v2.annotation.web.auth.Auth;
import cn.skcks.matrix.v2.annotation.web.methods.GetJson;
import cn.skcks.matrix.v2.annotation.web.methods.PostJson;
import cn.skcks.matrix.v2.config.swagger.SwaggerConfig;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import cn.skcks.matrix.v2.model.test.dto.GetUserParams;
import cn.skcks.matrix.v2.model.user.convert.UserConvertor;
import cn.skcks.matrix.v2.model.user.vo.UserVo;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import cn.skcks.matrix.v2.services.jwt.JwtService;
import cn.skcks.matrix.v2.services.user.UserService;
import cn.skcks.matrix.v2.services.route.RouteService;
import cn.skcks.matrix.v2.utils.json.JsonResponse;
import cn.skcks.matrix.v2.utils.page.PageWrapper;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "测试")
@RestController
@JsonMapping("/test")
@RequiredArgsConstructor
public class TestController {
private final UserService userService;
private final RouteService routeService;
private final JwtService jwtService;
private final CasbinService casbinService;
@Bean
public GroupedOpenApi testApi() {
return SwaggerConfig.api("Test", "/test");
}
@Operation(summary = "查询用户表")
@PostJson("/users")
public JsonResponse<PageWrapper<UserVo>> users(@RequestBody @Validated GetUserParams params) {
PageInfo<User> users = userService.getUsers(params.getPage(), params.getSize());
return JsonResponse.success(PageWrapper.of(UserConvertor.INSTANCE.daoToVo(users)));
}
@JsonMapping(value = "/route/{*}", method = {RequestMethod.GET})
public JsonResponse<RouteInfo> route(HttpServletRequest request) {
return JsonResponse.success(routeService.getRouteInfo(request));
}
@Auth
@JsonMapping(value = "/routes", method = {RequestMethod.GET})
public JsonResponse<Object> routes(HttpServletRequest request) {
return JsonResponse.success(routeService.getRoutes());
}
@GetJson("/getToken")
public JsonResponse<String> getToken() {
return JsonResponse.success(jwtService.generateToken(new Claims(IdUtil.fastUUID(), "test")));
}
@PostJson("/verifyToken")
public JsonResponse<Boolean> verifyToken(@RequestParam String token) {
return JsonResponse.success(jwtService.verifyToken(token));
}
@PostJson("/parseToken")
public JsonResponse<Claims> parseToken(@RequestParam String token) {
return JsonResponse.success(jwtService.parseToken(token));
}
@GetJson("/casbin")
public JsonResponse<Boolean> casbinTest(@RequestParam String identify,
@RequestParam String system,
@RequestParam String url,
@RequestParam String method) {
return JsonResponse.success(casbinService.enforce(identify, system, url, method));
}
}

View File

@ -0,0 +1,21 @@
package cn.skcks.matrix.v2.model.role.convert;
import cn.skcks.matrix.v2.model.role.vo.RoleVo;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.Role;
import com.github.pagehelper.PageInfo;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.Collection;
@Mapper
public abstract class RoleConvertor {
public final static RoleConvertor INSTANCE = Mappers.getMapper(RoleConvertor.class);
abstract public RoleVo daoToVo(Role role);
abstract public Collection<RoleVo> daoToVo(Collection<Role> roles);
abstract public PageInfo<RoleVo> daoToVo(PageInfo<Role> pageInfo);
}

View File

@ -0,0 +1,14 @@
package cn.skcks.matrix.v2.model.role.dto;
import cn.skcks.matrix.v2.services.role.RoleRoutePermission;
import lombok.Data;
import java.util.List;
@Data
public class AddRoleParams {
private String roleName;
private String role;
private String system;
private List<RoleRoutePermission> permissions;
}

View File

@ -0,0 +1,9 @@
package cn.skcks.matrix.v2.model.role.dto;
import lombok.Data;
@Data
public class DelRoleParams {
private String role;
private String system;
}

View File

@ -0,0 +1,19 @@
package cn.skcks.matrix.v2.model.role.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class GetRoleParams {
@Schema(description = "页数")
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
int page;
@Schema(description = "每页条数", example = "10")
@NotNull(message = "size 不能为空")
@Min(value = 1, message = "size 必须为正整数")
int size;
}

View File

@ -0,0 +1,18 @@
package cn.skcks.matrix.v2.model.role.dto;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Schema(title = "获取角色下所有用户")
@Data
public class GetRoleUsers {
@NotBlank(message = "角色 不能为空")
@Schema(description = "角色")
private String role;
@NotBlank(message = "系统 不能为空")
@Schema(description = "系统", defaultValue = CasbinService.DEFAULT_SYSTEM)
private String system;
}

View File

@ -0,0 +1,21 @@
package cn.skcks.matrix.v2.model.role.dto;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Schema(title = "用户在某个系统下所拥有的权限")
@Data
public class GetUserPermissions {
@NotBlank(message = "用户id 不能为空")
@Pattern(regexp = "\\d*", message = "用户id 格式错误")
@Schema(description = "用户id")
private String userId;
@NotBlank(message = "系统 不能为空")
@Schema(description = "系统", defaultValue = CasbinService.DEFAULT_SYSTEM)
private String system;
}

View File

@ -0,0 +1,20 @@
package cn.skcks.matrix.v2.model.role.dto;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Schema(title = "获取用户所拥有的角色")
@Data
public class GetUserRoles {
@NotBlank(message = "用户id 不能为空")
@Pattern(regexp = "\\d*", message = "用户id 格式错误")
@Schema(description = "用户id")
private String userId;
@NotBlank(message = "系统 不能为空")
@Schema(description = "系统", defaultValue = CasbinService.DEFAULT_SYSTEM)
private String system;
}

View File

@ -0,0 +1,24 @@
package cn.skcks.matrix.v2.model.role.dto;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Schema(title = "授予用户角色")
@Data
public class GrantAndRevokeRoleForUserDto {
@NotBlank(message = "用户id 不能为空")
@Pattern(regexp = "\\d*", message = "用户id 格式错误")
@Schema(description = "用户id")
private String userId;
@NotBlank(message = "角色 不能为空")
@Schema(description = "角色")
private String role;
@NotBlank(message = "系统 不能为空")
@Schema(description = "系统", defaultValue = CasbinService.DEFAULT_SYSTEM)
private String system;
}

View File

@ -0,0 +1,20 @@
package cn.skcks.matrix.v2.model.role.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(title = "角色信息")
public class RoleVo {
@Schema(description = "角色id")
private String id;
@Schema(description = "角色值")
private String role;
@Schema(description = "角色名称")
private String name;
@Schema(description = "是否启用")
private Boolean active;
}

View File

@ -0,0 +1,20 @@
package cn.skcks.matrix.v2.model.test.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class GetUserParams {
@Schema(description = "页数")
@NotNull(message = "page 不能为空")
@Min(value = 1, message = "page 必须为正整数")
int page;
@Schema(description = "每页条数", example = "10")
@NotNull(message = "size 不能为空")
@Min(value = 1, message = "size 必须为正整数")
int size;
}

View File

@ -0,0 +1,26 @@
package cn.skcks.matrix.v2.model.user.convert;
import cn.skcks.matrix.v2.model.user.vo.UserVo;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import com.github.pagehelper.PageInfo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.Collection;
@Mapper
public abstract class UserConvertor {
public final static UserConvertor INSTANCE = Mappers.getMapper(UserConvertor.class);
@Mappings(
@Mapping(source = "id", target = "id")
)
abstract public UserVo daoToVo(User user);
abstract public Collection<UserVo> daoToVo(Collection<User> users);
abstract public PageInfo<UserVo> daoToVo(PageInfo<User> userPageInfo);
}

View File

@ -0,0 +1,37 @@
package cn.skcks.matrix.v2.model.user.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
@Schema(title = "用户信息")
public class UserVo {
@Schema(description = "用户id")
private String id;
@Schema(description = "用户名")
private String userName;
@Schema(description = "邮箱")
private String email;
@Schema(description = "手机号码")
private String phoneNumber;
@Schema(description = "备注")
private String description;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@Schema(description = "最后更新时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@ -249,6 +249,12 @@
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>

View File

@ -36,6 +36,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.skcks.matrix.v2</groupId>
<artifactId>orm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>

View File

@ -1,7 +0,0 @@
package cn.skcks.matrix.v2;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}

View File

@ -0,0 +1,17 @@
package cn.skcks.matrix.v2.config;
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("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH");
}
}

View File

@ -0,0 +1,28 @@
package cn.skcks.matrix.v2.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig{
private final static String DEFAULT_PREFIX = "JWT";
private final static String SEPARATOR = ":";
// 非对称加密算法名称
private String algorithm = "ES512";
// redis 缓存 公钥 base91 的键名
private String publicCacheKey = DEFAULT_PREFIX + SEPARATOR +"PublicCacheKey";
// redis 缓存 私钥 base91 的键名
private String privateCacheKey = DEFAULT_PREFIX + SEPARATOR +"PrivateCacheKey";
// 过期时间 定位:
private Duration expire = Duration.ofHours(24);
// 每次启动都重新生成密钥对
private boolean generateKeyPairEveryTime = false;
}

View File

@ -0,0 +1,24 @@
package cn.skcks.matrix.v2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}

View File

@ -0,0 +1,91 @@
package cn.skcks.matrix.v2.config;
import cn.skcks.matrix.v2.interceptor.AuthorizationInterceptor;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.services.auth.AuthService;
import cn.skcks.matrix.v2.services.jwt.JwtService;
import cn.skcks.matrix.v2.utils.json.JsonResponse;
import cn.skcks.matrix.v2.utils.json.ResponseStatus;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final AuthorizationInterceptor authorizationInterceptor;
private final ObjectMapper objectMapper;
private final JwtService jwtService;
private final AuthService authService;
private final static JsonResponse<String> NO_LOGIN = JsonResponse.build("未登录", ResponseStatus.UNAUTHORIZED);
private final static JsonResponse<String> TOKEN_EXPIRE = JsonResponse.build("认证失效 请重新登录", ResponseStatus.UNAUTHORIZED);
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加请求 权限校验 拦截器
authorizationInterceptor.addAuthHandler(this::auth);
registry.addInterceptor(authorizationInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/error/**", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**");
}
private void writeResponse(HttpServletResponse response, int status, Object data) throws IOException {
response.setStatus(status);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), data);
}
private String getToken(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader("token")).orElseGet(() -> {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals("token")) {
return cookie.getValue();
}
}
}
return null;
});
}
private boolean auth(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = getToken(request);
log.info("token => {}", token);
if (token == null) {
writeResponse(response, NO_LOGIN.getCode(), NO_LOGIN);
return false;
}
Claims claims = jwtService.parseToken(token);
log.info("[解析的令牌信息] {}", claims);
if (claims == null) {
log.info("[认证失败] 无效 token => {}", token);
writeResponse(response, NO_LOGIN.getCode(), NO_LOGIN);
return false;
}
if (!authService.validateToken(claims, token)) {
writeResponse(response, TOKEN_EXPIRE.getCode(), TOKEN_EXPIRE);
return false;
}
return true;
}
}

View File

@ -0,0 +1,17 @@
package cn.skcks.matrix.v2.model.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Schema(title = "用户登录")
@Data
public class UserLoginDto {
@Schema(title = "账号")
@NotBlank(message = "账号不能为空")
private String account;
@Schema(title = "密码")
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@ -0,0 +1,36 @@
package cn.skcks.matrix.v2.model.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
@Schema(title = "用户注册")
@Data
public class UserRegisterDto {
@Schema(title = "用户名")
@NotBlank(message = "用户名不能为空")
private String userName;
@Schema(title = "密码")
@NotBlank(message = "密码不能为空")
// @Pattern(
// regexp = "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\\w\\s]).{8,}$",
// message = "密码必须大于8位 且至少有 一个大写字母 一个小写字符 一个特殊符号")
// @Pattern(
// regexp = "^(?=.*?[A-z])(?=.*?[0-9])(?=.*?[^\\w\\s]).{8,}$",
// message = "密码必须大于8位 且至少有 一个字母 一个特殊符号")
@Pattern(
regexp = "^(?=.*?[A-z])(?=.*?[0-9]).{6,}$",
message = "密码必须大于6位 且至少有 一个字母")
private String password;
@Schema(title = "邮箱")
@Email(message = "邮箱格式错误")
@NotBlank(message = "邮箱不能为空")
private String email;
@Schema(title = "手机号码")
private String phoneNumber;
}

View File

@ -0,0 +1,25 @@
package cn.skcks.matrix.v2.model.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@Schema(title = "用户登录返回信息")
public class UserLoginVo {
@Schema(title = "登录状态")
private boolean status;
@Schema(title = "用户id")
private String id;
@Schema(title = "用户令牌")
private String token;
@Schema(title = "刷新令牌")
private String refreshToken;
@Schema(title = "错误信息")
private String exception;
}

View File

@ -0,0 +1,19 @@
package cn.skcks.matrix.v2.model.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@Schema(title = "用户注册返回信息")
public class UserRegisterVo {
@Schema(title = "注册状态")
private boolean status;
@Schema(title = "异常信息")
private String exception;
@Schema(title = "用户id")
private String userId;
}

View File

@ -0,0 +1,35 @@
package cn.skcks.matrix.v2.model.jwt.convert;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.springframework.cglib.beans.BeanMap;
import java.util.HashMap;
import java.util.Map;
@Mapper
@SuppressWarnings({"unused"})
public abstract class ClaimConvertor {
public final static ClaimConvertor INSTANCE = Mappers.getMapper(ClaimConvertor.class);
public Map<String,Object> toMap(Claims claims) {
BeanMap beanMap = BeanMap.create(claims);
Map<String,Object> map = new HashMap<>(beanMap.size());
for(Object key: beanMap.keySet()){
map.put(String.valueOf(key),beanMap.get(key));
}
return map;
}
public Claims toClaims(Map<String,Object> map) {
Claims claims = new Claims();
if(map == null){
return claims;
}
BeanMap.create(claims).putAll(map);
return claims;
}
}

View File

@ -0,0 +1,13 @@
package cn.skcks.matrix.v2.model.jwt.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Claims {
private String userId;
private String userName;
}

View File

@ -0,0 +1,15 @@
package cn.skcks.matrix.v2.model.route.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@Schema(title = "路由信息")
@Data
@Builder
public class RouteInfo {
public String controller;
public String methodName;
public String requestMethod;
public String requestUrl;
}

View File

@ -0,0 +1,38 @@
package cn.skcks.matrix.v2.model.service;
import cn.skcks.matrix.v2.utils.json.JsonResponse;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ServiceResult<T> {
private final T result;
private String exception;
private String message;
public boolean hasException() {
return StringUtils.isNotBlank(exception);
}
public boolean hasMessage() {
return StringUtils.isNotBlank(message);
}
public JsonResponse<T> parseResponse() {
if (hasException()) {
return JsonResponse.error(null, exception);
} else {
if (hasMessage()) {
return JsonResponse.success(result, message);
} else {
return JsonResponse.success(result);
}
}
}
}

View File

@ -0,0 +1,23 @@
package cn.skcks.matrix.v2.services.auth;
import cn.skcks.matrix.v2.model.auth.dto.UserLoginDto;
import cn.skcks.matrix.v2.model.auth.dto.UserRegisterDto;
import cn.skcks.matrix.v2.model.auth.vo.UserLoginVo;
import cn.skcks.matrix.v2.model.auth.vo.UserRegisterVo;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
public interface AuthService {
boolean validateToken(String token);
String generateToken(User user);
String refreshToken(String refreshToken);
UserRegisterVo register(UserRegisterDto dto);
UserLoginVo login(UserLoginDto dto);
boolean validateToken(Claims claims, String token);
void saveToken(User user, String token);
}

View File

@ -0,0 +1,180 @@
package cn.skcks.matrix.v2.services.auth;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.skcks.matrix.v2.config.JwtConfig;
import cn.skcks.matrix.v2.model.auth.dto.UserLoginDto;
import cn.skcks.matrix.v2.model.auth.dto.UserRegisterDto;
import cn.skcks.matrix.v2.model.auth.vo.UserLoginVo;
import cn.skcks.matrix.v2.model.auth.vo.UserRegisterVo;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import cn.skcks.matrix.v2.services.jwt.JwtService;
import cn.skcks.matrix.v2.services.user.UserService;
import cn.skcks.matrix.v2.utils.redis.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final JwtConfig jwtConfig;
private final JwtService jwtService;
private final UserService userService;
private final static String DEFAULT_PREFIX = "AUTH";
private final static String SEPARATOR = ":";
private final static String TOKEN_PREFIX = "TOKEN";
@Override
public boolean validateToken(String token) {
return jwtService.verifyToken(token);
}
@Override
public String generateToken(User user) {
Claims claims = new Claims();
claims.setUserName(user.getUserName());
claims.setUserId(String.valueOf(user.getId()));
return jwtService.generateToken(claims);
}
@Override
public String refreshToken(String refreshToken) {
return null;
}
@Data
@AllArgsConstructor
protected static class Credentials {
private String passwordHex;
private String keyHex;
}
protected Credentials createCredentials(String password, byte[] key) {
// aes 加密
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
// 加密后的 hex
String passwordHex = aes.encryptHex(password);
// 密钥 (hex)
String keyHex = HexUtil.encodeHexStr(key);
return new Credentials(passwordHex, keyHex);
}
@Override
public UserRegisterVo register(UserRegisterDto dto) {
if (userService.exist(dto.getUserName(), dto.getEmail())) {
return UserRegisterVo.builder()
.status(false)
.exception("用户名 或 邮箱已被注册")
.build();
}
// 计算 密码的 hash
String passwordHash = SecureUtil.sha256(dto.getPassword());
// 随机生成密钥
final byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
final Credentials credentials = createCredentials(passwordHash, key);
User user = new User();
final long id = IdUtil.getSnowflakeNextId();
user.setId(id);
user.setUserName(dto.getUserName());
user.setPassword(credentials.passwordHex);
user.setSalt(credentials.keyHex);
user.setEmail(dto.getEmail());
user.setDescription("");
user.setPhoneNumber("");
user.setCreateTime(DateUtil.date());
user.setUpdateTime(DateUtil.date());
final boolean status = userService.add(user);
final UserRegisterVo.UserRegisterVoBuilder builder = UserRegisterVo.builder().status(status);
if (status) {
builder.userId(String.valueOf(id));
} else {
builder.exception("注册失败");
}
return builder.build();
}
@Override
public UserLoginVo login(UserLoginDto dto) {
UserLoginVo.UserLoginVoBuilder builder = UserLoginVo.builder().status(false);
final String exception = "用户不存在 或 密码错误";
if (!userService.exist(dto.getAccount(), dto.getAccount())) {
return builder.exception(exception).build();
}
User user = userService.getUser(dto.getAccount());
if (user == null) {
return builder.exception(exception).build();
}
final String passwordHash = SecureUtil.sha256(dto.getPassword());
final byte[] keyHex = HexUtil.decodeHex(user.getSalt());
Credentials credentials = createCredentials(passwordHash, keyHex);
if (!user.getPassword().equals(credentials.getPasswordHex())) {
return builder.exception(exception).build();
}
String token = generateToken(user);
saveToken(user, token);
user.setUpdateTime(DateUtil.date());
userService.update(user);
return builder.status(true)
.id(String.valueOf(user.getId()))
.token(token)
.build();
}
private String generateRedisKey(Long id, String token) {
return generateRedisKey(String.valueOf(id), token);
}
private String generateRedisKey(String id, String token) {
String tokenMd5 = SecureUtil.md5(token);
return DEFAULT_PREFIX + SEPARATOR + TOKEN_PREFIX
+ SEPARATOR + id
+ SEPARATOR + tokenMd5;
}
@Override
public boolean validateToken(Claims claims, String token) {
String redisKey = generateRedisKey(claims.getUserId(), token);
String cacheToken = RedisUtil.StringOps.get(redisKey);
if (StringUtils.isEmpty(cacheToken)) {
return false;
}
return StringUtils.equals(cacheToken, token);
}
@Override
public void saveToken(User user, String token) {
String redisKey = generateRedisKey(user.getId(), token);
RedisUtil.StringOps.set(redisKey, token);
RedisUtil.KeyOps.expire(redisKey, jwtConfig.getExpire().getSeconds(), TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,43 @@
package cn.skcks.matrix.v2.services.casbin;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import cn.skcks.matrix.v2.services.role.RoleService;
import cn.skcks.matrix.v2.services.route.RouteService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.casbin.jcasbin.main.Enforcer;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.web.context.WebApplicationContext;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Order(10)
@Service
@RequiredArgsConstructor
public class CasbinInitService implements CommandLineRunner {
private final WebApplicationContext applicationContext;
private final List<CasbinRegister> registers = new ArrayList<>();
private final RoleService roleService;
private final Enforcer enforcer;
private final RouteService routeService;
@Override
public void run(String... args) {
// Spring 容器中获取 CasbinRegister
applicationContext.getBeansOfType(CasbinRegister.class).forEach((name, clz) -> {
log.info("[casbin] 添加 注册器 {}", name);
registers.add(clz);
});
List<RouteInfo> routes = routeService.getRoutes();
// 并行 执行所有注册器
registers.parallelStream().forEach(register -> {
register.register(enforcer, routes, roleService);
});
}
}

View File

@ -0,0 +1,39 @@
package cn.skcks.matrix.v2.services.casbin;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import cn.skcks.matrix.v2.services.role.RoleService;
import org.casbin.jcasbin.main.Enforcer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
public interface CasbinRegister {
void register(Enforcer enforcer, List<RouteInfo> routes, RoleService roleService);
Logger log = LoggerFactory.getLogger(CasbinRegister.class);
static void addPolicies(Enforcer enforcer, String role, String system, List<RouteInfo> routes) {
HashSet<List<String>> params = new HashSet<>(routes.size());
for (RouteInfo route : routes) {
Permission permission = new Permission(Permission.Identify.ROLE, role, system, route.requestUrl, route.requestMethod);
params.add(permission.getPolicyParamList());
}
List<List<String>> existPolicies = enforcer.getFilteredPolicy(0, role, system);
existPolicies = existPolicies.stream().toList();
existPolicies.forEach(existPolicy -> {
if (!params.contains(existPolicy)) {
log.info("[casbin] {} 移除无效规则 {}", role, existPolicy);
enforcer.removePolicy(existPolicy);
}
});
// 批量添加规则 enforcer.addPolicies 在部分权限缺失时存在 bug 依然会遗漏缺失的规则 故改为并行单条插入
ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() * 2);
pool.submit(()-> params.stream().toList().parallelStream().forEach(enforcer::addPolicy));
}
}

View File

@ -0,0 +1,39 @@
package cn.skcks.matrix.v2.services.casbin;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface CasbinService {
String SEPARATOR = "::";
String USER = "USER";
String USER_PREFIX = USER + SEPARATOR;
String ROLE = "ROLE";
String ROLE_PREFIX = ROLE + SEPARATOR;
String DEFAULT_SYSTEM = "WEB";
String SUPER_PERMISSION = "root";
String SUPER_PERMISSION_NAME = "超级管理员";
String SUPER_ROLE = ROLE_PREFIX + SUPER_PERMISSION;
boolean addRole(String role, String system, String url, String method);
boolean addRole(Permission permission);
void grantRole(String userId, String role, String system);
void revokeRole(String userId, String role, String system);
boolean enforce(String identify, String system, String url, String method);
List<String> getUserRoles(String userId, String system);
List<String> getRoleUsers(String role, String system);
List<Permission> getUserPermission(String userId, String system);
List<String> getRelationForRoleInSystem(String role, String system);
boolean delRole(String role, String system);
}

View File

@ -0,0 +1,92 @@
package cn.skcks.matrix.v2.services.casbin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.casbin.jcasbin.main.Enforcer;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class CasbinServiceImpl implements CasbinService {
private final Enforcer enforcer;
@Override
public boolean addRole(String role, String system, String url, String method) {
return addRole(new Permission(Permission.Identify.ROLE, role, system, url, method));
}
@Override
public boolean addRole(Permission permission) {
if (!permission.getIdentify().equals(Permission.Identify.ROLE)) {
return false;
}
enforcer.addPolicy(permission.getPolicyParam());
return true;
}
@Override
public void grantRole(String userId, String role, String system) {
enforcer.addRoleForUserInDomain(Permission.Identify.getRoleName(Permission.Identify.USER, userId),
Permission.Identify.getRoleName(Permission.Identify.ROLE, role),
system);
}
@Override
public void revokeRole(String userId, String role, String system) {
enforcer.deleteRoleForUserInDomain(Permission.Identify.getRoleName(Permission.Identify.USER, userId),
Permission.Identify.getRoleName(Permission.Identify.ROLE, role),
system);
}
@Override
public boolean enforce(String identify, String system, String url, String method) {
return enforcer.enforce(identify, system, url, method);
}
@Override
public List<String> getUserRoles(String userId, String system) {
return enforcer.getRolesForUserInDomain(Permission.Identify.getRoleName(Permission.Identify.USER, userId), system);
}
@Override
public List<String> getRoleUsers(String role, String system) {
return enforcer.getUsersForRoleInDomain(Permission.Identify.getRoleName(Permission.Identify.ROLE, role), system)
.parallelStream()
.filter(item -> item.startsWith(Permission.Identify.USER.getPrefix()))
.collect(Collectors.toList());
}
@Override
public List<String> getRelationForRoleInSystem(String role, String system) {
return enforcer.getImplicitUsersForRole(Permission.Identify.getRoleName(Permission.Identify.ROLE, role), system);
}
@Override
public List<Permission> getUserPermission(String userId, String system) {
List<List<String>> permissions = enforcer.getImplicitPermissionsForUserInDomain(Permission.Identify.getRoleName(Permission.Identify.USER, userId), system);
return permissions.parallelStream()
.map(permission -> new Permission(
Permission.Identify.ROLE,
permission.get(0),
permission.get(1),
permission.get(2),
permission.get(3)
))
.toList();
}
@Override
public boolean delRole(String role, String system) {
if (getRelationForRoleInSystem(role, system).size() != 0) {
return false;
}
enforcer.removeFilteredNamedPolicy("p", 0, Permission.Identify.getRoleName(Permission.Identify.ROLE, role), system);
return true;
}
}

View File

@ -0,0 +1,72 @@
package cn.skcks.matrix.v2.services.casbin;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
@AllArgsConstructor
@Data
public class Permission {
@Getter
@AllArgsConstructor
public enum Identify {
ROLE(CasbinService.ROLE, CasbinService.ROLE_PREFIX), USER(CasbinService.USER, CasbinService.USER_PREFIX);
private final String identify;
private final String prefix;
public static String getRoleName(Identify identify, String name) {
final String prefix = identify.getPrefix();
return getRoleName(prefix, name);
}
public static String getRoleName(String prefix, String name) {
return name.startsWith(prefix) ? name : prefix + name;
}
public static String getRawRoleName(String roleName) {
for (Identify identify : Identify.values()) {
if (roleName.startsWith(identify.getPrefix())) {
return StringUtils.removeStart(roleName, identify.getPrefix());
}
}
return roleName;
}
@Override
public String toString() {
return identify;
}
}
@JsonIgnore
private Identify identify;
@JsonIgnore
private String name;
private String system;
private String url;
private String method;
@JsonIgnore
public String[] getPolicyParam() {
return new String[]{Identify.getRoleName(identify.getPrefix(), name), system, url, method};
}
@JsonIgnore
public List<String> getPolicyParamList() {
return new ArrayList<>() {{
add(Identify.getRoleName(identify.getPrefix(), name));
add(system);
add(url);
add(method);
}};
}
}

View File

@ -0,0 +1,26 @@
package cn.skcks.matrix.v2.services.casbin.init;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import cn.skcks.matrix.v2.services.casbin.CasbinRegister;
import cn.skcks.matrix.v2.services.role.RoleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.casbin.jcasbin.main.Enforcer;
import org.springframework.stereotype.Component;
import java.util.List;
import static cn.skcks.matrix.v2.services.casbin.CasbinService.*;
@Slf4j
@Component
@RequiredArgsConstructor
public class SuperRoleRegister implements CasbinRegister {
@Override
public void register(Enforcer enforcer, List<RouteInfo> routes, RoleService roleService) {
log.info("[casbin] 初始化 {} 权限", SUPER_PERMISSION_NAME);
roleService.initRole(SUPER_PERMISSION, SUPER_PERMISSION_NAME, true);
CasbinRegister.addPolicies(enforcer, SUPER_ROLE, DEFAULT_SYSTEM, routes);
}
}

View File

@ -0,0 +1,46 @@
package cn.skcks.matrix.v2.services.jwt;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import org.springframework.stereotype.Service;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
@Service
@SuppressWarnings({"unused"})
public interface JwtService {
/**
* 初始化
*/
void init();
/**
* 获取公私密钥对
* @return 公私密钥对
*/
KeyPair getKeyPair();
/**
* 获取公钥
* @return 公钥
*/
PublicKey getPublicKey();
/**
* 获取私钥
* @return 私钥
*/
PrivateKey getPrivateKey();
/**
* 重新加载
*/
void reload();
String generateToken(Claims claims);
boolean verifyToken(String token);
Claims parseToken(String token);
}

View File

@ -0,0 +1,154 @@
package cn.skcks.matrix.v2.services.jwt;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.signers.AlgorithmUtil;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import cn.skcks.matrix.v2.config.JwtConfig;
import cn.skcks.matrix.v2.model.jwt.convert.ClaimConvertor;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.utils.base91.Base91;
import cn.skcks.matrix.v2.utils.redis.RedisUtil;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import javax.annotation.Nullable;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
@Data
@Order
@Service
public class JwtServiceImpl implements JwtService, CommandLineRunner {
private final JwtConfig jwtConfig;
private String publicCacheKey;
private String privateCacheKey;
private KeyPair keyPair;
private PublicKey publicKey;
private PrivateKey privateKey;
private boolean checkKeys() {
return StringUtils.isNotBlank(RedisUtil.StringOps.get(publicCacheKey))
&& StringUtils.isNotBlank(RedisUtil.StringOps.get(privateCacheKey));
}
private boolean hasKeys() {
return RedisUtil.KeyOps.hasKey(publicCacheKey)
&& RedisUtil.KeyOps.hasKey(privateCacheKey);
}
public void init() {
if (!jwtConfig.isGenerateKeyPairEveryTime()) {
if (hasKeys() && checkKeys()) {
return;
}
} else {
log.info("[JwtService] 每次启动重新生成 jwt 密钥对 已启用");
}
log.info("[JwtService] 生成 jwt 密钥对");
final String algorithm = jwtConfig.getAlgorithm();
final KeyPair keyPair = KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(algorithm));
RedisUtil.StringOps.set(publicCacheKey, Base91.encode(keyPair.getPublic().getEncoded()));
RedisUtil.StringOps.set(privateCacheKey, Base91.encode(keyPair.getPrivate().getEncoded()));
}
@Override
public void reload() {
log.info("[JwtService] 重新加载密钥对");
run();
}
public void loadPublicKey() {
final byte[] pub = Base91.decode(RedisUtil.StringOps.get(publicCacheKey));
publicKey = KeyUtil.generatePublicKey(AlgorithmUtil.getAlgorithm(jwtConfig.getAlgorithm()), pub);
}
public void loadPrivateKey() {
final byte[] pri = Base91.decode(RedisUtil.StringOps.get(privateCacheKey));
privateKey = KeyUtil.generatePrivateKey(AlgorithmUtil.getAlgorithm(jwtConfig.getAlgorithm()), pri);
}
public void loadKeyPair() {
log.info("[JwtService] 载入密钥对信息");
loadPublicKey();
loadPrivateKey();
keyPair = new KeyPair(publicKey, privateKey);
}
@Override
public void run(String... args) {
publicCacheKey = jwtConfig.getPublicCacheKey();
privateCacheKey = jwtConfig.getPrivateCacheKey();
log.info("JwtConfig.publicCacheKey {}", publicCacheKey);
log.info("JwtConfig.privateCacheKey {}", privateCacheKey);
log.info("JwtConfig.expire {}", jwtConfig.getExpire());
init();
loadKeyPair();
}
public JWTSigner getSignSigner() {
return JWTSignerUtil.createSigner(jwtConfig.getAlgorithm(), privateKey);
}
private JWTSigner getVerifySigner() {
return JWTSignerUtil.createSigner(jwtConfig.getAlgorithm(), keyPair);
}
public String generateToken(Claims claims) {
JWTSigner signer = getSignSigner();
JWT jwt = JWT.create()
.setSigner(signer)
.setIssuedAt(DateUtil.date())
.setExpiresAt(DateUtil.offset(DateUtil.date(), DateField.SECOND, (int) jwtConfig.getExpire().getSeconds()));
Map<String, Object> map = ClaimConvertor.INSTANCE.toMap(claims);
map.forEach(jwt::setPayload);
return jwt.sign();
}
public boolean verifyToken(String token) {
JWTSigner signer = getVerifySigner();
try {
JWT jwt = JWT.of(token).setSigner(signer);
return jwt.verify() && jwt.validate(0);
} catch (Exception e) {
log.warn(e.getMessage());
return false;
}
}
@Nullable
@Override
public Claims parseToken(String token) {
if (!verifyToken(token)) {
return null;
}
try {
JWT jwt = JWT.of(token).setSigner(getVerifySigner());
return ClaimConvertor.INSTANCE.toClaims(jwt.getPayloads().getRaw());
} catch (Exception e) {
log.warn("{}", e.getMessage());
return null;
}
}
}

View File

@ -0,0 +1,31 @@
package cn.skcks.matrix.v2.services.orm;
import cn.skcks.matrix.v2.orm.mybatis.operation.OperateTableMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@Order(0)
@RequiredArgsConstructor
public class OrmService {
private final OperateTableMapper mapper;
@PostConstruct
public void init() {
log.info("[orm] 自动建表");
log.info("[orm] 创建 User 表");
mapper.createUserTable();
log.info("[orm] 创建 Role 表");
mapper.createRoleTable();
log.info("[orm] 创建 LocationRecord 表");
mapper.createLocationRecordTable();
}
}

View File

@ -0,0 +1,9 @@
package cn.skcks.matrix.v2.services.role;
import lombok.Data;
@Data
public class RoleRoutePermission {
private String url;
private String method;
}

View File

@ -0,0 +1,28 @@
package cn.skcks.matrix.v2.services.role;
import cn.skcks.matrix.v2.model.service.ServiceResult;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.Role;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface RoleService {
PageInfo<Role> getRoles(int page, int size, boolean all);
void initRole(String role, String name, boolean active);
void grantRoleForUser(String userId, String role, String system) throws Exception;
void revokeRoleForUser(String userId, String role, String system) throws Exception;
List<String> getUserRoles(String userId, String system);
List<User> getRoleUsers(String role, String system);
ServiceResult<Void> addRole(String roleName, String role, String system, List<RoleRoutePermission> permissions) throws Exception;
ServiceResult<Void> delRole(String role, String system) throws Exception;
}

View File

@ -0,0 +1,197 @@
package cn.skcks.matrix.v2.services.role;
import cn.hutool.core.util.IdUtil;
import cn.skcks.matrix.v2.model.service.ServiceResult;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.RoleDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.RoleMapper;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.UserDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.UserMapper;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.Role;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import cn.skcks.matrix.v2.services.casbin.Permission;
import cn.skcks.matrix.v2.services.casbin.Permission.Identify;
import cn.skcks.matrix.v2.services.user.UserService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
@Slf4j
@Service
@RequiredArgsConstructor
public class RoleServiceImpl implements RoleService {
private final RoleMapper roleMapper;
private final CasbinService casbinService;
private final UserService userService;
private final UserMapper userMapper;
@Override
public PageInfo<Role> getRoles(int page, int size, boolean all) {
PageInfo<Role> pageInfo;
if (all) {
pageInfo = new PageInfo<>();
List<Role> roles = roleMapper.select(u -> u.orderBy(RoleDynamicSqlSupport.id.descending()));
pageInfo.setList(roles);
pageInfo.setTotal(roles.size());
pageInfo.setPageSize(roles.size());
pageInfo.setPageNum(1);
pageInfo.setPages(1);
return pageInfo;
}
try (Page<Role> rolePage = PageHelper.startPage(page, size)) {
pageInfo = rolePage.doSelectPageInfo(() -> roleMapper.select(u -> u.orderBy(RoleDynamicSqlSupport.id.descending())));
}
List<Role> roles = pageInfo.getList();
log.info("共 {} 条, 每页 {} 条, 第 {}/{} 页, {} 条数据",
pageInfo.getTotal(),
pageInfo.getPageSize(),
pageInfo.getPageNum(),
pageInfo.getPages(),
roles.size());
return pageInfo;
}
@Override
public void initRole(String role, String name, boolean active) {
Optional<Role> roleModel = roleMapper.selectOne(s -> s
.where(RoleDynamicSqlSupport.role.role, SqlBuilder.isEqualTo(role))
.and(RoleDynamicSqlSupport.systemId, SqlBuilder.isEqualTo(CasbinService.DEFAULT_SYSTEM))
.limit(1));
if (roleModel.isEmpty()) {
Role r = new Role();
r.setId(IdUtil.getSnowflakeNextId());
r.setActive(active);
r.setRole(role);
r.setName(name);
r.setSystemId(CasbinService.DEFAULT_SYSTEM);
roleMapper.insert(r);
}
}
private boolean existRole(String role, String system) {
Optional<Role> roleModel = roleMapper.selectOne(s -> s
.where(RoleDynamicSqlSupport.role.role, SqlBuilder.isEqualTo(role))
.and(RoleDynamicSqlSupport.systemId, SqlBuilder.isEqualTo(system))
.limit(1));
return roleModel.isPresent();
}
private void checkRoleAndUser(String userId, String role, String system) throws Exception {
if (!userService.exist(userId)) {
throw new Exception("用户不存在");
}
if (!existRole(role, system)) {
throw new Exception("角色不存在");
}
}
@Override
public void grantRoleForUser(String userId, String role, String system) throws Exception {
checkRoleAndUser(userId, role, system);
final String roleName = Identify.getRoleName(Identify.ROLE, role);
casbinService.grantRole(userId, roleName, system);
}
@Override
public void revokeRoleForUser(String userId, String role, String system) throws Exception {
checkRoleAndUser(userId, role, system);
final String roleName = Identify.getRoleName(Identify.ROLE, role);
casbinService.revokeRole(userId, roleName, system);
}
@Override
public List<String> getUserRoles(String userId, String system) {
return casbinService.getUserRoles(userId, system)
.parallelStream()
.map(Identify::getRawRoleName)
.toList();
}
@Override
public List<User> getRoleUsers(String role, String system) {
List<Long> userIds = casbinService.getRoleUsers(role, system)
.parallelStream()
.map(Identify::getRawRoleName)
.map(Long::valueOf)
.toList();
if (userIds.size() > 0) {
return userMapper.select(u -> u.where(UserDynamicSqlSupport.id, SqlBuilder.isIn(userIds)));
} else {
return Collections.emptyList();
}
}
@Override
@Transactional
@ShardingSphereTransactionType
public ServiceResult<Void> delRole(String role, String system) throws Exception {
ServiceResult.ServiceResultBuilder<Void> result = ServiceResult.builder();
if (!existRole(role, system)) {
return result.exception(MessageFormat.format("系统 {0} 角色 {1} 不存在", system, role)).build();
}
List<String> relation = casbinService.getRelationForRoleInSystem(role, system);
log.info("{} 关联的用户/角色 {}", role, relation);
if (relation.size() > 0) {
return result.exception(MessageFormat.format("系统 {0} 角色 {1} 已有用户/角色关联", system, role)).build();
}
casbinService.delRole(role, system);
roleMapper.delete((r) ->
r.where(RoleDynamicSqlSupport.role.role, SqlBuilder.isEqualTo(role))
.and(RoleDynamicSqlSupport.systemId, SqlBuilder.isEqualTo(system))
);
return result.build();
}
@Override
@Transactional
@ShardingSphereTransactionType
public ServiceResult<Void> addRole(String roleName, String role, String system, List<RoleRoutePermission> routePermissions) throws Exception {
ServiceResult.ServiceResultBuilder<Void> result = ServiceResult.builder();
if (existRole(role, system)) {
return result.exception(MessageFormat.format("系统 {0} 角色 {1} 已存在", system, role))
.build();
}
Role r = new Role();
r.setId(IdUtil.getSnowflakeNextId());
r.setActive(true);
r.setRole(role);
r.setName(roleName);
r.setSystemId(system);
roleMapper.insert(r);
ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() * 2);
ForkJoinTask<Void> task = pool.submit(() -> {
routePermissions.parallelStream()
.map(item -> new Permission(Identify.ROLE, role, system, item.getUrl(), item.getMethod()))
.forEach(casbinService::addRole);
return null;
});
task.get();
return result.build();
}
}

View File

@ -0,0 +1,20 @@
package cn.skcks.matrix.v2.services.route;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import javax.annotation.Nullable;
import java.util.List;
@Service
public interface RouteService{
List<RouteInfo> getRoutes();
@Nullable
RouteInfo getRouteInfo(HttpServletRequest request);
List<RouteInfo> getRouteInfoList(RequestMappingInfo info, HandlerMethod method);
}

View File

@ -0,0 +1,94 @@
package cn.skcks.matrix.v2.services.route;
import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;
import javax.annotation.Nullable;
import java.util.*;
@Slf4j
@Service
@AllArgsConstructor
public class RouteServiceImpl implements RouteService {
private WebApplicationContext applicationContext;
public List<RouteInfo> getRouteInfoList(RequestMappingInfo info, HandlerMethod method) {
ArrayList<RouteInfo> routes = new ArrayList<>();
PathPatternsRequestCondition pathPatternsRequestCondition = info.getPathPatternsCondition();
if (pathPatternsRequestCondition == null) {
return routes;
}
Set<PathPattern> pathPatterns = pathPatternsRequestCondition.getPatterns();
String uri = pathPatterns.size() > 0 ? pathPatterns.stream().toList().get(0).getPatternString() : "";
RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
if (methodsCondition.getMethods().size() == 0) {
for (HttpMethod httpMethod : HttpMethod.values()) {
RouteInfo route = RouteInfo.builder()
.controller(method.getMethod().getDeclaringClass().getName())
.methodName(method.getMethod().getName())
.requestUrl(uri)
.requestMethod(httpMethod.name())
.build();
routes.add(route);
}
} else {
for (RequestMethod requestMethod : methodsCondition.getMethods()) {
RouteInfo route = RouteInfo.builder()
.controller(method.getMethod().getDeclaringClass().getName())
.methodName(method.getMethod().getName())
.requestUrl(uri)
.requestMethod(requestMethod.name())
.build();
routes.add(route);
}
}
return routes;
}
@Nullable
public RouteInfo getRouteInfo(HttpServletRequest request) {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
RequestMappingInfo info = entry.getKey();
HandlerMethod method = entry.getValue();
// 从请求获取匹配的 RequestMappingInfo
RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
if (matchingInfo != null) {
List<RouteInfo> routeInfos = getRouteInfoList(matchingInfo, method);
return routeInfos.size() > 0 ? routeInfos.get(0) : null;
}
}
return null;
}
@Override
public List<RouteInfo> getRoutes() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
Set<RouteInfo> list = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
RequestMappingInfo info = entry.getKey();
HandlerMethod method = entry.getValue();
list.addAll(getRouteInfoList(info, method));
}
return list.parallelStream().toList();
}
}

View File

@ -0,0 +1,22 @@
package cn.skcks.matrix.v2.services.user;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import com.github.pagehelper.PageInfo;
import javax.annotation.Nullable;
public interface UserService {
PageInfo<User> getUsers(int page, int size);
@Nullable
User getUser(String account);
boolean exist(String userName, String email);
boolean exist(String userId);
boolean add(User ...users);
boolean add(User user);
boolean update(User user);
}

View File

@ -0,0 +1,105 @@
package cn.skcks.matrix.v2.services.user;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.UserDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.UserMapper;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
public PageInfo<User> getUsers(int page, int size){
PageInfo<User> pageInfo;
try(Page<User> userPage = PageHelper.startPage(page,size)){
pageInfo = userPage.doSelectPageInfo(()-> userMapper.select(u->u.orderBy(UserDynamicSqlSupport.id.descending())));
}
List<User> users = pageInfo.getList();
log.info("共 {} 条, 每页 {} 条, 第 {}/{} 页, {} 条数据",
pageInfo.getTotal(),
pageInfo.getPageSize(),
pageInfo.getPageNum(),
pageInfo.getPages(),
users.size());
return pageInfo;
}
@Nullable
@Override
public User getUser(String account) {
if(!exist(account,account)){
return null;
}
List<User> users = userMapper.select(u->u.where(UserDynamicSqlSupport.userName, SqlBuilder.isEqualToWhenPresent(account))
.or(UserDynamicSqlSupport.email,SqlBuilder.isEqualToWhenPresent(account)));
if(users.size() != 1){
return null;
}
return users.get(0);
}
public boolean exist(String userName,String email){
if(StringUtils.isBlank(userName) && StringUtils.isBlank(email)){
return false;
}
Optional<User> user = userMapper.selectOne(u->u.where(UserDynamicSqlSupport.userName, SqlBuilder.isEqualToWhenPresent(userName))
.or(UserDynamicSqlSupport.email,SqlBuilder.isEqualToWhenPresent(email))
.limit(1));
return user.isPresent();
}
public boolean exist(String userId){
if(StringUtils.isBlank(userId)){
return false;
}
Optional<User> user = userMapper.selectOne(u-> u.where(UserDynamicSqlSupport.id,SqlBuilder.isEqualTo(Long.valueOf(userId)))
.limit(1));
return user.isPresent();
}
@Override
@Transactional
@ShardingSphereTransactionType
public boolean add(User... users) {
return userMapper.insertMultiple(Arrays.asList(users)) > 0;
}
@Override
@Transactional
@ShardingSphereTransactionType
public boolean add(User user) {
return userMapper.insert(user) > 0;
}
@Override
public boolean update(User user) {
if(user.getId() == null){
return false;
}
return userMapper.updateByPrimaryKey(user) > 0;
}
}

View File

@ -24,6 +24,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.skcks.matrix.v2</groupId>
<artifactId>services</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>

View File

@ -42,7 +42,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 12341234
url: jdbc:mysql://10.10.10.200:3306/matrix?createDatabaseIfNotExist=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
url: jdbc:mysql://10.10.10.200:3306/matrix_v2?createDatabaseIfNotExist=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
# jdbc-url: jdbc:mysql://10.10.10.100:3306/matrix?createDatabaseIfNotExist=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
rules:
@ -67,6 +67,12 @@ spring:
standard:
sharding-column: id
sharding-algorithm-name: general-id
location_record:
actual-data-sources: ds
sharding-strategy:
standard:
sharding-column: id
sharding-algorithm-name: general-id
sharding-algorithms:
general-id:
# 内置分片算法