feat: 项目初始化

This commit is contained in:
2025-11-28 17:35:39 +08:00
parent 3ea0202709
commit 32fba5f65d
41 changed files with 1305 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dolphin-common-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,8 @@
package day.gitlab.dolphin.common.core.constants;
public class Constants {
public static final String SUCCESS = "success";
public static final String FAILURE = "failure";
}

View File

@ -0,0 +1,55 @@
package day.gitlab.dolphin.common.core.entity;
import day.gitlab.dolphin.common.core.exception.BusinessException;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.function.Supplier;
import static day.gitlab.dolphin.common.core.constants.Constants.FAILURE;
import static day.gitlab.dolphin.common.core.constants.Constants.SUCCESS;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private int code;
private String message;
private Object data;
public static Result success() {
return Result.builder().code(200).message(SUCCESS).build();
}
public static Result success(Object data) {
Result result = Result.builder().code(200).message(SUCCESS).build();
if (data instanceof Supplier) {
result.setData(((Supplier<?>) data).get());
} else {
result.setData(data);
}
return result;
}
public static Result failure() {
return Result.builder().code(500).message(FAILURE).build();
}
public static Result failure(String message) {
return Result.builder().code(500).message(message).build();
}
public static Result failure(int code, String message) {
return Result.builder().code(code).message(message).build();
}
public static Result failure(BusinessException businessException) {
return Result.builder().code(businessException.getCode()).message(businessException.getMessage()).build();
}
}

View File

@ -0,0 +1,14 @@
package day.gitlab.dolphin.common.core.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
}

View File

@ -0,0 +1,21 @@
package day.gitlab.dolphin.common.core.exception;
import day.gitlab.dolphin.common.core.entity.Result;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Component
@ControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = BusinessException.class)
public Result handleBusinessException(BusinessException e) {
return Result.failure(e);
}
@ExceptionHandler(value = Exception.class)
public Result handleException(Exception e) {
return Result.failure(e.getMessage());
}
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dolphin-common-security</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,36 @@
package day.gitlab.dolphin.common.security;
import lombok.Getter;
import java.util.List;
@Getter
public class Authentication {
private final UserPrincipal userPrincipal;
private final List<String> userAuthorities;
public Authentication(UserPrincipal userPrincipal, List<String> userAuthorities) {
this.userPrincipal = userPrincipal;
this.userAuthorities = userAuthorities;
}
public boolean hasAllAuthorities(String[] userAuthorities) {
for (String userAuthority : userAuthorities) {
if (!this.userAuthorities.contains(userAuthority)) {
return false;
}
}
return true;
}
public boolean hasAnyAuthorities(String[] userAuthorities) {
for (String userAuthority : userAuthorities) {
if (this.userAuthorities.contains(userAuthority)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,58 @@
package day.gitlab.dolphin.common.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import day.gitlab.dolphin.common.core.entity.Result;
import day.gitlab.dolphin.common.core.exception.BusinessException;
import day.gitlab.dolphin.common.security.annotation.AuthorityIgnoreInitializer;
import day.gitlab.dolphin.common.security.config.SecurityConfig;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class AuthenticationFilter extends OncePerRequestFilter {
private final SecurityConfig securityConfig;
private final AuthorityIgnoreInitializer authorityIgnoreInitializer;
private final AuthenticationInitialize authenticationInitialize;
public AuthenticationFilter(SecurityConfig securityConfig,
AuthorityIgnoreInitializer authorityIgnoreInitializer,
AuthenticationInitialize authenticationInitialize) {
this.securityConfig = securityConfig;
this.authorityIgnoreInitializer = authorityIgnoreInitializer;
this.authenticationInitialize = authenticationInitialize;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
// 在未启用或是忽略校验的地址时直接放行
if (!securityConfig.isEnabled() || authorityIgnoreInitializer.isIgnoreUrl(request)) {
filterChain.doFilter(request, response);
}
try {
Authentication initialize = authenticationInitialize.initialize(request);
SecurityContextHolder.setAuthentication(initialize);
filterChain.doFilter(request, response);
} catch (BusinessException e) {
response.setStatus(e.getCode());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(Result.failure(e)));
} finally {
SecurityContextHolder.clearAuthentication();
}
}
}

View File

@ -0,0 +1,8 @@
package day.gitlab.dolphin.common.security;
import jakarta.servlet.http.HttpServletRequest;
public interface AuthenticationInitialize {
Authentication initialize(HttpServletRequest request);
}

View File

@ -0,0 +1,10 @@
package day.gitlab.dolphin.common.security;
import java.util.List;
public interface AuthenticationProvider {
UserPrincipal getUserPrincipal(String userId);
List<String> getUserAuthorities(String userId);
}

View File

@ -0,0 +1,20 @@
package day.gitlab.dolphin.common.security;
public class SecurityContextHolder {
private static final ThreadLocal<Authentication> CONTEXT = new ThreadLocal<>();
private SecurityContextHolder() {}
public static Authentication getAuthentication() {
return CONTEXT.get();
}
public static void setAuthentication(Authentication authentication) {
CONTEXT.set(authentication);
}
public static void clearAuthentication() {
CONTEXT.remove();
}
}

View File

@ -0,0 +1,19 @@
package day.gitlab.dolphin.common.security;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserPrincipal {
private String id;
private String username;
private String nickname;
}

View File

@ -0,0 +1,18 @@
package day.gitlab.dolphin.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 使用该注解注解的类会进行权限校验。
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorityCheck {
AuthorityType type() default AuthorityType.AND;
String[] value();
}

View File

@ -0,0 +1,32 @@
package day.gitlab.dolphin.common.security.annotation;
import day.gitlab.dolphin.common.security.Authentication;
import day.gitlab.dolphin.common.security.SecurityContextHolder;
import day.gitlab.dolphin.common.security.exception.NotAuthorityException;
import day.gitlab.dolphin.common.security.exception.NotLoginException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AuthorityCheckAspect {
@Before("@annotation(authorityCheck)")
public void check(JoinPoint joinPoint, AuthorityCheck authorityCheck) {
Authentication authentication = SecurityContextHolder.getAuthentication();
if (authentication == null) {
throw new NotLoginException();
}
// AuthorityCheck的type如果是AND输出1
if (authorityCheck.type() == AuthorityType.AND) {
if (!authentication.hasAllAuthorities(authorityCheck.value())) {
throw new NotAuthorityException(authorityCheck.value());
}
} else if (authorityCheck.type() == AuthorityType.OR) {
if (!authentication.hasAnyAuthorities(authorityCheck.value())) {
throw new NotAuthorityException(authorityCheck.value());
}
}
}
}

View File

@ -0,0 +1,14 @@
package day.gitlab.dolphin.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 使用该注解注解的类或方法会忽略校验
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorityIgnore {
}

View File

@ -0,0 +1,101 @@
package day.gitlab.dolphin.common.security.annotation;
import day.gitlab.dolphin.common.security.config.SecurityConfig;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class AuthorityIgnoreInitializer implements ApplicationContextAware {
private final List<IgnoreUrl> ignoreUrls = new ArrayList<>();
private SecurityConfig securityConfig;
@Override
public void setApplicationContext(@NonNull ApplicationContext ctx) throws BeansException {
String contextPath = ctx.getEnvironment().getProperty("server.servlet.context-path");
RequestMappingHandlerMapping requestMappingHandlerMapping = ctx.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
if (StringUtils.hasText(securityConfig.getIgnoreUrls())) {
Arrays.stream(securityConfig.getIgnoreUrls().split(","))
.filter(StringUtils::hasText)
.map(s -> {
String prefixUrl = StringUtils.hasLength(contextPath) ? contextPath : "";
prefixUrl = prefixUrl.endsWith("/") ? prefixUrl.substring(0, prefixUrl.length() - 1) : prefixUrl;
return s.startsWith("/") ? prefixUrl + s : prefixUrl + "/" + s;
})
.map(IgnoreUrl::new)
.forEach(url -> this.ignoreUrls.add(url));
}
for (RequestMappingInfo mappingInfo : handlerMethods.keySet()) {
HandlerMethod handlerMethod = handlerMethods.get(mappingInfo);
Class<?> beanClass = handlerMethod.getBeanType();
RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
if (requestMapping == null) {
continue;
}
AuthorityIgnore authorityIgnore = handlerMethod.getMethodAnnotation(AuthorityIgnore.class);
if (authorityIgnore == null && !beanClass.isAnnotationPresent(AuthorityIgnore.class)) {
continue;
}
System.out.println("beanClass:" + beanClass.getCanonicalName());
System.out.println("method:" + handlerMethod.getMethod().getName() + " -> " + mappingInfo.getPatternValues());
List<String> urlPrefix = Arrays.stream(requestMapping.method())
.map(RequestMethod::name)
.map(StringBuffer::new)
.map(sb -> sb.append(":"))
.map(sb -> StringUtils.hasText(contextPath) ? sb.append(contextPath) : sb)
.map(StringBuffer::toString)
.map(sb -> sb.endsWith("/") ? sb.substring(0, sb.length() - 1) : sb)
.collect(Collectors.toList());
if (urlPrefix.isEmpty()) {
String prefixUrl = StringUtils.hasText(contextPath) ? contextPath : "/";
if (prefixUrl.endsWith("/")) {
prefixUrl = prefixUrl.substring(0, prefixUrl.length() - 1);
}
urlPrefix.add(prefixUrl);
}
urlPrefix.stream()
.flatMap(sub -> mappingInfo.getPatternValues().stream().filter(StringUtils::hasText).map(s -> sub + s))
.map(IgnoreUrl::new)
.forEach(url -> this.ignoreUrls.add(url));
System.out.println("ignoreUrls: " + ignoreUrls);
}
}
public boolean isIgnoreUrl(HttpServletRequest request) {
String method = request.getMethod();
String url = request.getRequestURI();
return this.ignoreUrls.stream().anyMatch(ignoreUrl -> ignoreUrl.isIgnoreUrl(method, url));
}
@Autowired
public void setSecurityConfig(SecurityConfig securityConfig) {
this.securityConfig = securityConfig;
}
}

View File

@ -0,0 +1,6 @@
package day.gitlab.dolphin.common.security.annotation;
public enum AuthorityType {
AND,
OR
}

View File

@ -0,0 +1,54 @@
package day.gitlab.dolphin.common.security.annotation;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.AntPathMatcher;
@Setter
@Getter
public class IgnoreUrl {
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
private String method;
private String url;
public IgnoreUrl(String url) {
String urlUpperCase = url.toUpperCase();
if (urlUpperCase.startsWith("GET:")) {
this.method = "GET";
} else if (urlUpperCase.startsWith("HEAD:")) {
this.method = "HEAD";
} else if (urlUpperCase.startsWith("POST:")) {
this.method = "POST";
} else if (urlUpperCase.startsWith("PUT:")) {
this.method = "PUT";
} else if (urlUpperCase.startsWith("PATCH:")) {
this.method = "PATCH";
} else if (urlUpperCase.startsWith("DELETE:")) {
this.method = "DELETE";
} else if (urlUpperCase.startsWith("OPTIONS:")) {
this.method = "OPTIONS";
} else if (urlUpperCase.startsWith("TRACE:")) {
this.method = "TRACE";
} else {
this.method = null;
}
this.url = url.substring(urlUpperCase.indexOf(":") + 1);
}
public boolean isIgnoreUrl(String method, String url) {
if (!PATH_MATCHER.match(this.url, url)) {
return false;
}
return this.method == null || this.method.equalsIgnoreCase(method);
}
@Override
public String toString() {
return method == null ? url : method + ":" + url;
}
}

View File

@ -0,0 +1,24 @@
package day.gitlab.dolphin.common.security.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Setter
@Getter
@Component
@ConfigurationProperties("dolphin.security")
public class SecurityConfig {
private boolean enabled = true;
private String ignoreUrls = "";
private String secret = "dolphin";
private long expire = 3600 * 1000L;
private long refreshTokenExpire = 7 * 24 * 3600 * 1000L;
}

View File

@ -0,0 +1,12 @@
package day.gitlab.dolphin.common.security.exception;
import day.gitlab.dolphin.common.core.exception.BusinessException;
import lombok.Getter;
@Getter
public class AuthenticationException extends BusinessException {
public AuthenticationException(int code, String message) {
super(code, message);
}
}

View File

@ -0,0 +1,14 @@
package day.gitlab.dolphin.common.security.exception;
import lombok.Getter;
@Getter
public class InvalidTokenException extends AuthenticationException {
private final String token;
public InvalidTokenException(String token) {
super(401, "Unauthorized: Invalid token");
this.token = token;
}
}

View File

@ -0,0 +1,14 @@
package day.gitlab.dolphin.common.security.exception;
import lombok.Getter;
@Getter
public class NotAuthorityException extends AuthenticationException {
private final String[] userAuthorities;
public NotAuthorityException(String[] userAuthorities) {
super(403, "Forbidden: Insufficient permissions");
this.userAuthorities = userAuthorities;
}
}

View File

@ -0,0 +1,8 @@
package day.gitlab.dolphin.common.security.exception;
public class NotLoginException extends AuthenticationException{
public NotLoginException() {
super(401, "Unauthorized");
}
}

View File

@ -0,0 +1,20 @@
package day.gitlab.dolphin.common.security.exception;
import lombok.Getter;
import java.util.Date;
@Getter
public class TokenExpiredException extends AuthenticationException {
private final String token;
private final Date expiration;
public TokenExpiredException(String token, Date expiration) {
super(401, "Unauthorized: Token expired");
this.token = token;
this.expiration = expiration;
}
}

View File

@ -0,0 +1,70 @@
package day.gitlab.dolphin.common.security.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.UUID;
public class Jwt {
private final String secret;
private final long expire;
private final long refreshTokenExpire;
public Jwt(String secret, long expire, long refreshTokenExpire) {
this.secret = secret;
this.expire = expire;
this.refreshTokenExpire = refreshTokenExpire;
}
public String getTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
return token.substring(7);
}
return null;
}
public String generateAccessToken(String userId) {
return Jwts.builder()
.subject(userId)
.id(UUID.randomUUID().toString())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expire))
.claim("type", "access")
.signWith(getSigningKey())
.compact();
}
public String generateRefreshToken(String userId) {
return Jwts.builder()
.subject(userId)
.id(UUID.randomUUID().toString())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + refreshTokenExpire))
.claim("type", "refresh")
.signWith(getSigningKey())
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
private SecretKey getSigningKey() {
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}
}

View File

@ -0,0 +1,91 @@
package day.gitlab.dolphin.common.security.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import day.gitlab.dolphin.common.security.Authentication;
import day.gitlab.dolphin.common.security.AuthenticationInitialize;
import day.gitlab.dolphin.common.security.AuthenticationProvider;
import day.gitlab.dolphin.common.security.UserPrincipal;
import day.gitlab.dolphin.common.security.config.SecurityConfig;
import day.gitlab.dolphin.common.security.exception.InvalidTokenException;
import day.gitlab.dolphin.common.security.exception.NotLoginException;
import day.gitlab.dolphin.common.security.exception.TokenExpiredException;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Component
public class JwtAuthenticationInitialize implements AuthenticationInitialize {
private final SecurityConfig securityConfig;
private final StringRedisTemplate stringRedisTemplate;
private final AuthenticationProvider authenticationProvider;
public JwtAuthenticationInitialize(SecurityConfig securityConfig,
StringRedisTemplate stringRedisTemplate,
AuthenticationProvider authenticationProvider) {
this.securityConfig = securityConfig;
this.stringRedisTemplate = stringRedisTemplate;
this.authenticationProvider = authenticationProvider;
}
@Override
public Authentication initialize(HttpServletRequest request) {
Jwt jwt = new Jwt(securityConfig.getSecret(), securityConfig.getExpire(), securityConfig.getRefreshTokenExpire());
// 1、获取Token
String token = jwt.getTokenFromRequest(request);
if (token == null) {
throw new NotLoginException();
}
// 2、解析Token获取用户ID
String userId;
Date expiration;
try {
Claims claims = jwt.parseToken(token);
userId = claims.getSubject();
expiration = claims.getExpiration();
Objects.requireNonNull(userId);
Objects.requireNonNull(expiration);
} catch (Exception e) {
throw new InvalidTokenException(token);
}
// 3、判断是否过期
if (expiration.before(new Date())) {
throw new TokenExpiredException(token, expiration);
}
// 4、从Redis或数据库中加载用户信息
UserPrincipal userPrincipal;
List<String> userAuthorities;
try {
String userPrincipalJson = stringRedisTemplate.opsForValue().get("dolphin:user:info:" + userId);
ObjectMapper objectMapper = new ObjectMapper();
if (!StringUtils.hasText(userPrincipalJson)) {
userPrincipal = authenticationProvider.getUserPrincipal(userId);
stringRedisTemplate.opsForValue().set("dolphin:user:info:" + userId, objectMapper.writeValueAsString(userPrincipal));
} else {
userPrincipal = objectMapper.readValue(userPrincipalJson, UserPrincipal.class);
}
String userAuthJson = stringRedisTemplate.opsForValue().get("dolphin:user:auth:" + userId);
if (!StringUtils.hasText(userAuthJson)) {
userAuthorities = authenticationProvider.getUserAuthorities(userId);
stringRedisTemplate.opsForValue().set("dolphin:user:auth:" + userId, objectMapper.writeValueAsString(userAuthorities));
} else {
List<?> list = objectMapper.readValue(userAuthJson, List.class);
userAuthorities = list.stream().map(Object::toString).collect(Collectors.toList());
}
} catch (Exception e) {
throw new InvalidTokenException(token);
}
return new Authentication(userPrincipal, userAuthorities);
}
}

18
dolphin-commons/pom.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dolphin-commons</artifactId>
<packaging>pom</packaging>
<modules>
<module>dolphin-common-core</module>
<module>dolphin-common-security</module>
</modules>
</project>

91
dolphin-compose/pom.xml Normal file
View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dolphin-compose</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Project -->
<dependency>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-common-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-module-rbac</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JDBC Drivers -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package day.gitlab.dolphin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DolphinApplication {
public static void main(String[] args) {
SpringApplication.run(DolphinApplication.class, args);
}
}

View File

@ -0,0 +1,26 @@
package day.gitlab.dolphin.authorize;
import day.gitlab.dolphin.common.security.AuthenticationProvider;
import day.gitlab.dolphin.common.security.UserPrincipal;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class DolphinAuthenticationProvider implements AuthenticationProvider {
@Override
public UserPrincipal getUserPrincipal(String userId) {
UserPrincipal userPrincipal = new UserPrincipal();
userPrincipal.setId(userId);
userPrincipal.setUsername(userId);
userPrincipal.setNickname(userId);
return userPrincipal;
}
@Override
public List<String> getUserAuthorities(String userId) {
return new ArrayList<>();
}
}

View File

@ -0,0 +1,21 @@
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: dolphin-compose
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://110.42.60.129:10002/postgres
username: postgres
password: postgres
data:
redis:
host: 110.42.60.129
port: 10001
database: 0
password: 123456

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-modules</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dolphin-module-rbac</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-common-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,14 @@
package day.gitlab.dolphin.rbac.controller;
import day.gitlab.dolphin.rbac.service.RegionService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/region")
public class RegionController {
@Resource
private RegionService regionService;
}

View File

@ -0,0 +1,39 @@
package day.gitlab.dolphin.rbac.entity;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.keygen.KeyGenerators;
import lombok.Data;
import java.sql.Timestamp;
@Data
@Table("sys_rbac_region")
public class Region {
@Id(keyType = KeyType.Generator, value = KeyGenerators.flexId)
private String id;
private String parentId;
private String parentCode;
private String rootId;
private String rootCode;
private String name;
private String code;
private String extCode;
private Integer sort;
private String description;
private Timestamp createTime;
private Timestamp updateTime;
}

View File

@ -0,0 +1,19 @@
package day.gitlab.dolphin.rbac.entity;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.keygen.KeyGenerators;
@Table("sys_rbac_user")
public class User {
@Id(keyType = KeyType.Generator, value = KeyGenerators.flexId)
private String id;
private String username;
private String password;
private String nickname;
}

View File

@ -0,0 +1,9 @@
package day.gitlab.dolphin.rbac.mapper;
import com.mybatisflex.core.BaseMapper;
import day.gitlab.dolphin.rbac.entity.Region;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RegionMapper extends BaseMapper<Region> {
}

View File

@ -0,0 +1,7 @@
package day.gitlab.dolphin.rbac.service;
import com.mybatisflex.core.service.IService;
import day.gitlab.dolphin.rbac.entity.Region;
public interface RegionService extends IService<Region> {
}

View File

@ -0,0 +1,11 @@
package day.gitlab.dolphin.rbac.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import day.gitlab.dolphin.rbac.entity.Region;
import day.gitlab.dolphin.rbac.mapper.RegionMapper;
import day.gitlab.dolphin.rbac.service.RegionService;
import org.springframework.stereotype.Service;
@Service
public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> implements RegionService {
}

17
dolphin-modules/pom.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dolphin-modules</artifactId>
<packaging>pom</packaging>
<modules>
<module>dolphin-module-rbac</module>
</modules>
</project>

102
pom.xml Normal file
View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>day.gitlab</groupId>
<artifactId>dolphin-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dolphin-backend</name>
<packaging>pom</packaging>
<properties>
<java.version>25</java.version>
<spring-boot.version>3.5.8</spring-boot.version>
<mybatis-flex.version>1.11.4</mybatis-flex.version>
<jjwt.version>0.13.0</jjwt.version>
</properties>
<modules>
<module>dolphin-commons</module>
<module>dolphin-modules</module>
<module>dolphin-compose</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MyBatis Flex -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-dependencies</artifactId>
<version>${mybatis-flex.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
-->
</project>