diff --git a/auth-common/pom.xml b/auth-common/pom.xml new file mode 100644 index 0000000..737f179 --- /dev/null +++ b/auth-common/pom.xml @@ -0,0 +1,81 @@ + + 4.0.0 + + com.auth + auth-server + 0.0.1 + + + auth-common + jar + 0.0.1 + common + 公共的配置和实体类 + + + UTF-8 + 17 + 17 + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.projectlombok + lombok + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + com.mysql + mysql-connector-j + + + com.zaxxer + HikariCP + + + + + io.jsonwebtoken + jjwt + + + + + com.alibaba + easyexcel + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + diff --git a/auth-common/src/main/java/com/auth/common/config/ControllerStringParamTrimConfig.java b/auth-common/src/main/java/com/auth/common/config/ControllerStringParamTrimConfig.java new file mode 100644 index 0000000..4a7e97b --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/config/ControllerStringParamTrimConfig.java @@ -0,0 +1,50 @@ +package com.auth.common.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.InitBinder; + +import java.io.IOException; + +/** + * 去除前端传递的空格 + */ +@ControllerAdvice +public class ControllerStringParamTrimConfig { + + /** + * 创建 String trim 编辑器 + * 构造方法中 boolean 参数含义为如果是空白字符串,是否转换为null + * 即如果为true,那么 " " 会被转换为 null,否者为 "" + */ + @InitBinder + public void initBinder(WebDataBinder binder) { + StringTrimmerEditor propertyEditor = new StringTrimmerEditor(false); + // 为 String 类对象注册编辑器 + binder.registerCustomEditor(String.class, propertyEditor); + } + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + return jacksonObjectMapperBuilder -> { + // 为 String 类型自定义反序列化操作 + jacksonObjectMapperBuilder + .deserializerByType(String.class, new StdScalarDeserializer(String.class) { + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException { + // // 去除全部空格 + // return StringUtils.trimAllWhitespace(jsonParser.getValueAsString()); + + // 仅去除前后空格 + return jsonParser.getValueAsString().trim(); + } + }); + }; + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/config/MyBatisPlusFieldConfig.java b/auth-common/src/main/java/com/auth/common/config/MyBatisPlusFieldConfig.java new file mode 100644 index 0000000..c204ba6 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/config/MyBatisPlusFieldConfig.java @@ -0,0 +1,44 @@ +package com.auth.common.config; + +import com.auth.common.context.BaseContext; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 配置MP在修改和新增时的操作 + */ +@Component +public class MyBatisPlusFieldConfig implements MetaObjectHandler { + + /** + * 使用mp做添加操作时候,这个方法执行 + */ + @Override + public void insertFill(MetaObject metaObject) { + // 设置属性值 + this.strictInsertFill(metaObject, "isDeleted", Integer.class, 0); + this.setFieldValByName("createTime", LocalDateTime.now(), metaObject); + this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); + if (BaseContext.getUsername() != null) { + this.setFieldValByName("createUser", BaseContext.getUserId(), metaObject); + this.setFieldValByName("updateUser", BaseContext.getUserId(), metaObject); + } else { + this.setFieldValByName("createUser", 0L, metaObject); + this.setFieldValByName("updateUser", BaseContext.getUserId(), metaObject); + } + } + + /** + * 使用mp做修改操作时候,这个方法执行 + */ + @Override + public void updateFill(MetaObject metaObject) { + if (BaseContext.getUserId() != null) { + this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); + this.setFieldValByName("updateUser", BaseContext.getUserId(), metaObject); + } + } +} diff --git a/auth-common/src/main/java/com/auth/common/config/MybatisPlusConfig.java b/auth-common/src/main/java/com/auth/common/config/MybatisPlusConfig.java new file mode 100644 index 0000000..ebd027b --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/config/MybatisPlusConfig.java @@ -0,0 +1,35 @@ +package com.auth.common.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@EnableTransactionManagement +@Configuration +public class MybatisPlusConfig { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + // 拦截器 + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + + // 使用分页插件 + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + paginationInnerInterceptor.setDbType(DbType.MYSQL); + paginationInnerInterceptor.setMaxLimit(600L); + interceptor.addInnerInterceptor(paginationInnerInterceptor); + + // 乐观锁 + interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); + + // 防止全表删除 + interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); + + return interceptor; + } +} diff --git a/auth-common/src/main/java/com/auth/common/config/ThreadLocalCleanupInterceptor.java b/auth-common/src/main/java/com/auth/common/config/ThreadLocalCleanupInterceptor.java new file mode 100644 index 0000000..0d165e8 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/config/ThreadLocalCleanupInterceptor.java @@ -0,0 +1,23 @@ +package com.auth.common.config; + +import com.auth.common.context.BaseContext; +import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerInterceptor; + +@Configuration +public class ThreadLocalCleanupInterceptor implements HandlerInterceptor { + + // @Override + // public boolean preHandle(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Object handler) { + // return true; + // } + + @Override + public void afterCompletion(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Object handler, Exception ex) { + // 移除上下文存储内容 + BaseContext.removeUser(); + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/config/WebConfig.java b/auth-common/src/main/java/com/auth/common/config/WebConfig.java new file mode 100644 index 0000000..b0d9fc7 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/config/WebConfig.java @@ -0,0 +1,26 @@ +package com.auth.common.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final ThreadLocalCleanupInterceptor threadLocalCleanupInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(threadLocalCleanupInterceptor); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/context/BaseContext.java b/auth-common/src/main/java/com/auth/common/context/BaseContext.java new file mode 100644 index 0000000..f948f30 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/context/BaseContext.java @@ -0,0 +1,29 @@ +package com.auth.common.context; + + +public class BaseContext { + private static final ThreadLocal userId = new ThreadLocal<>(); + private static final ThreadLocal username = new ThreadLocal<>(); + + // 用户id相关 + public static Long getUserId() { + return userId.get(); + } + + public static void setUserId(Long _userId) { + userId.set(_userId); + } + + public static String getUsername() { + return username.get(); + } + + public static void setUsername(String _username) { + username.set(_username); + } + + public static void removeUser() { + username.remove(); + userId.remove(); + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/exception/AuthenticSecurityException.java b/auth-common/src/main/java/com/auth/common/exception/AuthenticSecurityException.java new file mode 100644 index 0000000..dc42d66 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/exception/AuthenticSecurityException.java @@ -0,0 +1,47 @@ +package com.auth.common.exception; + + +import com.auth.common.model.common.result.ResultCodeEnum; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@NoArgsConstructor +@Getter +@ToString +@Slf4j +public class AuthenticSecurityException extends RuntimeException { + // 状态码 + Integer code; + + // 描述信息 + String message = "服务异常"; + + // 返回结果状态 + ResultCodeEnum resultCodeEnum; + + public AuthenticSecurityException(Integer code, String message) { + super(message); + this.code = code; + this.message = message; + } + + public AuthenticSecurityException(String message) { + super(message); + this.message = message; + } + + public AuthenticSecurityException(ResultCodeEnum codeEnum) { + super(codeEnum.getMessage()); + this.code = codeEnum.getCode(); + this.message = codeEnum.getMessage(); + this.resultCodeEnum = codeEnum; + } + + public AuthenticSecurityException(String message, Exception exception) { + super(message); + this.message = message; + log.error(message, exception); + } +} diff --git a/auth-common/src/main/java/com/auth/common/exception/GlobalExceptionHandler.java b/auth-common/src/main/java/com/auth/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..4928eaa --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,102 @@ +package com.auth.common.exception; + +import com.auth.common.model.common.result.Result; +import com.auth.common.model.common.result.ResultCodeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.sql.SQLIntegrityConstraintViolationException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(AuthenticSecurityException.class) + @ResponseBody + public Result exceptionHandler(AuthenticSecurityException exception) { + String message = exception.getMessage(); + Integer code = exception.getCode(); + return Result.error(null, code, message); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseBody + public Result exceptionHandler(RuntimeException exception) { + String message = exception.getMessage(); + message = StringUtils.hasText(message) ? message : "服务器异常"; + log.error("发生业务异常: {}", exception.getMessage(), exception); + + // 💡IDEA:如果需要特殊情况的日志可以参考下面的代码 + // ========================================= + // StringWriter sw = new StringWriter(); + // e.printStackTrace(new PrintWriter(sw)); + // logger.error(sw.toString()); + // ========================================= + + // 解析异常 + String jsonParseError = "JSON parse error (.*)"; + Matcher jsonParseErrorMatcher = Pattern.compile(jsonParseError).matcher(message); + if (jsonParseErrorMatcher.find()) { + return Result.error(null, 500, "JSON解析异常 " + jsonParseErrorMatcher.group(1)); + } + + // 数据过大 + String dataTooLongError = "Data too long for column (.*?) at row 1"; + Matcher dataTooLongErrorMatcher = Pattern.compile(dataTooLongError).matcher(message); + if (dataTooLongErrorMatcher.find()) { + return Result.error(null, 500, dataTooLongErrorMatcher.group(1) + " 字段数据过大"); + } + + // 主键冲突 + String primaryKeyError = "Duplicate entry '(.*?)' for key .*"; + Matcher primaryKeyErrorMatcher = Pattern.compile(primaryKeyError).matcher(message); + if (primaryKeyErrorMatcher.find()) { + return Result.error(null, 500, "[" + primaryKeyErrorMatcher.group(1) + "]已存在"); + } + + log.error("GlobalExceptionHandler===>运行时异常信息:{}", message); + return Result.error(null, 500, message); + } + + // 表单验证字段 + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleValidationExceptions(MethodArgumentNotValidException ex) { + String errorMessage = ex.getBindingResult().getFieldErrors().stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .distinct() + .collect(Collectors.joining(", ")); + return Result.error(null, 201, errorMessage); + } + + // 特定异常处理 + @ExceptionHandler(ArithmeticException.class) + @ResponseBody + public Result error(ArithmeticException exception) { + log.error("GlobalExceptionHandler===>特定异常信息:{}", exception.getMessage()); + + return Result.error(null, 500, exception.getMessage()); + } + + // 处理SQL异常 + @ExceptionHandler(SQLIntegrityConstraintViolationException.class) + @ResponseBody + public Result exceptionHandler(SQLIntegrityConstraintViolationException exception) { + log.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.getMessage()); + + String message = exception.getMessage(); + if (message.contains("Duplicate entry")) { + // 错误信息 + return Result.error(ResultCodeEnum.USER_IS_EMPTY); + } else { + return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION); + } + } +} diff --git a/auth-common/src/main/java/com/auth/common/exception/MyAuthenticationException.java b/auth-common/src/main/java/com/auth/common/exception/MyAuthenticationException.java new file mode 100644 index 0000000..18f4ac8 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/exception/MyAuthenticationException.java @@ -0,0 +1,19 @@ +package com.auth.common.exception; + +import org.springframework.security.core.AuthenticationException; + +/** + * 自定义未认证异常 + */ +public class MyAuthenticationException extends AuthenticationException { + /** + * Constructs an {@code AuthenticationException} with the specified message and root + * cause. + * + * @param msg the detail message + * @param cause the root cause + */ + public MyAuthenticationException(String msg, Throwable cause) { + super(msg, cause); + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/bo/package-info.java b/auth-common/src/main/java/com/auth/common/model/bo/package-info.java new file mode 100644 index 0000000..a3a62f0 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/bo/package-info.java @@ -0,0 +1 @@ +package com.auth.common.model.bo; \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/common/BaseEntity.java b/auth-common/src/main/java/com/auth/common/model/common/BaseEntity.java new file mode 100644 index 0000000..c64d2e9 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/common/BaseEntity.java @@ -0,0 +1,48 @@ +package com.auth.common.model.common; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@Schema(name = "BaseEntity", title = "基础信息字段", description = "基础信息字段") +public class BaseEntity implements Serializable { + + @Schema(name = "id", title = "唯一标识") + @TableId(value = "id", type = IdType.ASSIGN_ID) + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + @Schema(name = "createTime", title = "创建时间") + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + @Schema(name = "updateTime", title = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + @Schema(name = "createUser", title = "创建用户") + @TableField(fill = FieldFill.INSERT) + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JsonSerialize(using = ToStringSerializer.class) + private Long createUser; + + @Schema(name = "updateUser", title = "操作用户") + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JsonSerialize(using = ToStringSerializer.class) + private Long updateUser; + +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/common/BaseVo.java b/auth-common/src/main/java/com/auth/common/model/common/BaseVo.java new file mode 100644 index 0000000..92e5b0a --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/common/BaseVo.java @@ -0,0 +1,23 @@ +package com.auth.common.model.common; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class BaseVo { + + @Schema(name = "createTime", title = "创建时间") + private LocalDateTime createTime; + + @Schema(name = "updateTime", title = "更新时间") + private LocalDateTime updateTime; + + @Schema(name = "createUser", title = "创建用户ID") + private Long createUser; + + @Schema(name = "updateUser", title = "更新用户ID") + private Long updateUser; + +} diff --git a/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java b/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java new file mode 100644 index 0000000..b16e7c7 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java @@ -0,0 +1,37 @@ +package com.auth.common.model.common.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 封装分页查询结果 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "PageResult 对象", title = "分页返回结果", description = "分页返回结果") +public class PageResult implements Serializable { + + @Schema(name = "pageNo", title = "当前页") + private Long pageNo; + + @Schema(name = "pageSize", title = "每页记录数") + private Long pageSize; + + @Schema(name = "pages", title = "总分页数") + private Long pages; + + @Schema(name = "total", title = "总记录数") + private Long total; + + @Schema(name = "list", title = "当前页数据集合") + private List list; + +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/common/result/Result.java b/auth-common/src/main/java/com/auth/common/model/common/result/Result.java new file mode 100644 index 0000000..b41948f --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/common/result/Result.java @@ -0,0 +1,174 @@ +package com.auth.common.model.common.result; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Result { + // 状态码 + private Integer code; + // 返回消息 + private String message; + // 返回数据 + private T data; + + /** + * * 自定义返回体 + * + * @param data 返回体 + * @return Result + */ + protected static Result build(T data) { + Result result = new Result<>(); + result.setData(data); + return result; + } + + /** + * * 自定义返回体,使用ResultCodeEnum构建 + * + * @param body 返回体 + * @param codeEnum 返回状态码 + * @return Result + */ + public static Result build(T body, ResultCodeEnum codeEnum) { + Result result = build(body); + result.setCode(codeEnum.getCode()); + result.setMessage(codeEnum.getMessage()); + return result; + } + + /** + * * 自定义返回体 + * + * @param body 返回体 + * @param code 返回状态码 + * @param message 返回消息 + * @return Result + */ + public static Result build(T body, Integer code, String message) { + Result result = build(body); + result.setCode(code); + result.setMessage(message); + result.setData(null); + return result; + } + + /** + * * 操作成功 + * + * @return Result + */ + public static Result success() { + return success(null, ResultCodeEnum.SUCCESS); + } + + /** + * * 操作成功 + * + * @param data baseCategory1List + */ + public static Result success(T data) { + return build(data, ResultCodeEnum.SUCCESS); + } + + /** + * * 操作成功-状态码 + * + * @param codeEnum 状态码 + */ + public static Result success(ResultCodeEnum codeEnum) { + return success(null, codeEnum); + } + + /** + * * 操作成功-自定义返回数据和状态码 + * + * @param data 返回体 + * @param codeEnum 状态码 + */ + public static Result success(T data, ResultCodeEnum codeEnum) { + return build(data, codeEnum); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param message 错误信息 + */ + public static Result success(T data, String message) { + return build(data, 200, message); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param code 状态码 + * @param message 错误信息 + */ + public static Result success(T data, Integer code, String message) { + return build(data, code, message); + } + + /** + * * 操作失败 + */ + public static Result error() { + return Result.build(null); + } + + /** + * * 操作失败-自定义返回数据 + * + * @param data 返回体 + */ + public static Result error(T data) { + return build(data, ResultCodeEnum.FAIL); + } + + /** + * * 操作失败-状态码 + * + * @param codeEnum 状态码 + */ + public static Result error(ResultCodeEnum codeEnum) { + return build(null, codeEnum); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param codeEnum 状态码 + */ + public static Result error(T data, ResultCodeEnum codeEnum) { + return build(data, codeEnum); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param code 状态码 + * @param message 错误信息 + */ + public static Result error(T data, Integer code, String message) { + return build(data, code, message); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param message 错误信息 + */ + public static Result error(T data, String message) { + return build(null, 500, message); + } +} diff --git a/auth-common/src/main/java/com/auth/common/model/common/result/ResultCodeEnum.java b/auth-common/src/main/java/com/auth/common/model/common/result/ResultCodeEnum.java new file mode 100644 index 0000000..be7e555 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/common/result/ResultCodeEnum.java @@ -0,0 +1,90 @@ +package com.auth.common.model.common.result; + +import lombok.Getter; + +/** + * 统一返回结果状态信息类 + */ +@Getter +public enum ResultCodeEnum { + // 成功操作 200 + SUCCESS(200, "操作成功"), + LOAD_FINISHED(200, "加载完成"), + ADD_SUCCESS(200, "添加成功"), + UPDATE_SUCCESS(200, "修改成功"), + DELETE_SUCCESS(200, "删除成功"), + ASSIGN_SUCCESS(200, "排序成功"), + SUCCESS_UPLOAD(200, "上传成功"), + SUCCESS_LOGOUT(200, "退出成功"), + EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"), + EMAIL_CODE_SEND_SUCCESS(200, "邮箱验证码已发送"), + + // 验证错误 201 + USERNAME_OR_PASSWORD_NOT_EMPTY(201, "用户名或密码不能为空"), + EMAIL_CODE_NOT_EMPTY(201, "邮箱验证码不能为空"), + SEND_EMAIL_CODE_NOT_EMPTY(201, "请先发送邮箱验证码"), + EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"), + LOGIN_ERROR(500, "账号或密码错误"), + LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"), + GET_BUCKET_EXCEPTION(201, "获取文件信息失败"), + SEND_MAIL_CODE_ERROR(201, "邮件发送失败"), + EMAIL_CODE_EMPTY(201, "邮箱验证码过期或不存在"), + EMAIL_EXIST(201, "邮箱已存在"), + REQUEST_IS_EMPTY(201, "请求数据为空"), + DATA_TOO_LARGE(201, "请求数据为空"), + UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD(201, "新密码与密码相同"), + + // 数据相关 206 + ILLEGAL_REQUEST(206, "非法请求"), + REPEAT_SUBMIT(206, "重复提交"), + DATA_ERROR(206, "数据异常"), + EMAIL_USER_TEMPLATE_IS_EMPTY(206, "邮件模板为空"), + EMAIL_TEMPLATE_IS_EMPTY(206, "邮件模板为空"), + EMAIL_USER_IS_EMPTY(206, "关联邮件用户配置为空"), + DATA_EXIST(206, "数据已存在"), + DATA_NOT_EXIST(206, "数据不存在"), + ALREADY_USER_EXCEPTION(206, "用户已存在"), + USER_IS_EMPTY(206, "用户不存在"), + FILE_NOT_EXIST(206, "文件不存在"), + NEW_PASSWORD_SAME_OLD_PASSWORD(206, "新密码不能和旧密码相同"), + MISSING_TEMPLATE_FILES(206, "缺少模板文件"), + + // 身份过期 208 + LOGIN_AUTH(208, "请先登陆"), + AUTHENTICATION_EXPIRED(208, "身份验证过期"), + SESSION_EXPIRATION(208, "会话过期"), + + // 209 + THE_SAME_USER_HAS_LOGGED_IN(209, "相同用户已登录"), + + // 提示错误 + UPDATE_ERROR(216, "修改失败"), + URL_ENCODE_ERROR(216, "URL编码失败"), + ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"), + FETCH_USERINFO_ERROR(219, "获取用户信息失败"), + ILLEGAL_DATA_REQUEST(219, "非法数据请求"), + CLASS_NOT_FOUND(219, "类名不存在"), + ADMIN_ROLE_CAN_NOT_DELETED(219, "无法删除admin角色"), + ROUTER_RANK_NEED_LARGER_THAN_THE_PARENT(219, "设置路由等级需要大于或等于父级的路由等级"), + + // 无权访问 403 + FAIL_NO_ACCESS_DENIED(403, "无权访问"), + FAIL_NO_ACCESS_DENIED_USER_OFFLINE(403, "用户强制下线"), + TOKEN_PARSING_FAILED(403, "token解析失败"), + FAIL_NO_ACCESS_DENIED_USER_LOCKED(403, "该账户已封禁"), + + // 系统错误 500 + UNKNOWN_EXCEPTION(500, "服务异常"), + SERVICE_ERROR(500, "服务异常"), + UPLOAD_ERROR(500, "上传失败"), + FAIL(500, "失败"), + ; + + private final Integer code; + private final String message; + + ResultCodeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/constant/FileStorageConstant.java b/auth-common/src/main/java/com/auth/common/model/constant/FileStorageConstant.java new file mode 100644 index 0000000..3d1145f --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/constant/FileStorageConstant.java @@ -0,0 +1,35 @@ +package com.auth.common.model.constant; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class FileStorageConstant { + public static final String FAVICON = "favicon"; + public static final String AVATAR = "avatar"; + public static final String MESSAGE = "message"; + public static final String CAROUSEL = "carousel"; + public static final String BACKUP = "backup"; + public static final String IMAGES = "images"; + public static final String VIDEO = "video"; + public static final Map typeMap = new HashMap<>(); + + static { + typeMap.put(FAVICON, "/" + FAVICON + "/"); + typeMap.put(AVATAR, "/" + AVATAR + "/"); + typeMap.put(MESSAGE, "/" + MESSAGE + "/"); + typeMap.put(CAROUSEL, "/" + CAROUSEL + "/"); + typeMap.put(BACKUP, "/" + BACKUP + "/"); + typeMap.put(IMAGES, "/" + IMAGES + "/"); + typeMap.put(VIDEO, "/" + VIDEO + "/"); + typeMap.put("default", "/" + "default" + "/"); + } + + public static String getType(String type) { + String value = typeMap.get(type); + if (value != null) return value; + throw new RuntimeException("上传类型错误或缺失"); + } +} diff --git a/auth-common/src/main/java/com/auth/common/model/constant/FileType.java b/auth-common/src/main/java/com/auth/common/model/constant/FileType.java new file mode 100644 index 0000000..f196148 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/constant/FileType.java @@ -0,0 +1,12 @@ +package com.auth.common.model.constant; + +public class FileType { + public static final String JPG = "jpg"; + public static final String PNG = "png"; + public static final String GIF = "gif"; + public static final String BMP = "bmp"; + public static final String TIFF = "tiff"; + public static final String JSON = "json"; + public static final String EXCEL = "excel"; + +} diff --git a/auth-common/src/main/java/com/auth/common/model/constant/LocalDateTimeConstant.java b/auth-common/src/main/java/com/auth/common/model/constant/LocalDateTimeConstant.java new file mode 100644 index 0000000..79e916a --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/constant/LocalDateTimeConstant.java @@ -0,0 +1,11 @@ +package com.auth.common.model.constant; + +import lombok.Data; + +@Data +public class LocalDateTimeConstant { + public static final String YYYY_MM_DD = "yyyy-MM-dd"; + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + public static final String YYYY_MM_DD_HH_MM_SS_SLASH = "yyyy/MM/dd HH:mm:ss"; + public static final String YYYY_MM_DD_HH_MM_SS_UNDERLINE = "yyyy_MM_dd_HH_mm_ss_SSS"; +} diff --git a/auth-common/src/main/java/com/auth/common/model/constant/RedisUserConstant.java b/auth-common/src/main/java/com/auth/common/model/constant/RedisUserConstant.java new file mode 100644 index 0000000..1783e41 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/constant/RedisUserConstant.java @@ -0,0 +1,67 @@ +package com.auth.common.model.constant; + +import lombok.Data; + +/** + * Redis用户前缀设置 + */ +@Data +public class RedisUserConstant { + // 过期时间 + public static final Long REDIS_EXPIRATION_TIME = 7L;// 7 天/分钟 Redis过期 + public static final Integer Cookie_EXPIRATION_TIME = 5 * 60 * 60;// cookies 过期时间 5 分钟 + public static final String WEB_CONFIG_KEY = "webConfig::platformConfig";// web配置 + + /* 用户登录前缀 */ + private static final String USER_LOGIN_INFO_PREFIX = "user::loginInfo::"; + + /* 用户邮箱验证码前缀 */ + private static final String USER_EMAIL_CODE_PREFIX = "user::emailCode::"; + + /* 用户角色前缀 */ + private static final String USER_ROLES_CODE_PREFIX = "user::roles::"; + + /* 用户权限前缀 */ + private static final String USER_PERMISSION_CODE_PREFIX = "user::permission::"; + + /** + * 用户登录前缀 + * + * @param user 用户名信息 + * @return 格式化后缓存前缀 + */ + public static String getUserLoginInfoPrefix(String user) { + return USER_LOGIN_INFO_PREFIX + user; + } + + /** + * 用户邮箱前缀 + * + * @param user 用户信息 + * @return 邮箱验证码前缀 + */ + public static String getUserEmailCodePrefix(String user) { + return USER_EMAIL_CODE_PREFIX + user; + } + + /** + * 用户角色前缀 + * + * @param user 用户信息 + * @return 格式化后用户角色前缀 + */ + public static String getUserRolesCodePrefix(String user) { + return USER_ROLES_CODE_PREFIX + user; + } + + /** + * 用户权限前缀 + * + * @param user 用户信息 + * @return 格式化后用户权限前缀 + */ + public static String getUserPermissionCodePrefix(String user) { + return USER_PERMISSION_CODE_PREFIX + user; + } + +} diff --git a/auth-common/src/main/java/com/auth/common/model/constant/SecurityConfigConstant.java b/auth-common/src/main/java/com/auth/common/model/constant/SecurityConfigConstant.java new file mode 100644 index 0000000..64f756c --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/constant/SecurityConfigConstant.java @@ -0,0 +1,12 @@ +package com.auth.common.model.constant; + +import java.util.ArrayList; +import java.util.List; + +public class SecurityConfigConstant { + + /* 可以放行的权限 */ + public static List PERMIT_ACCESS_LIST = new ArrayList<>() {{ + add("admin"); + }}; +} diff --git a/auth-common/src/main/java/com/auth/common/model/constant/UserConstant.java b/auth-common/src/main/java/com/auth/common/model/constant/UserConstant.java new file mode 100644 index 0000000..f97b37f --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/constant/UserConstant.java @@ -0,0 +1,12 @@ +package com.auth.common.model.constant; + +import lombok.Data; + +@Data +public class UserConstant { + public static final String USER_AVATAR = "https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132"; + public static final String PERSON_DESCRIPTION = "这个人很懒没有介绍..."; + public static final String LOGIN = "login"; + public static final String LOGOUT = "logout"; + public static final String FORCE_LOGOUT = "force_logout"; +} diff --git a/auth-common/src/main/java/com/auth/common/model/dto/DictDto.java b/auth-common/src/main/java/com/auth/common/model/dto/DictDto.java new file mode 100644 index 0000000..e00182d --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/dto/DictDto.java @@ -0,0 +1,49 @@ +package com.auth.common.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "DictDTO对象", title = "系统数据字典表", description = "系统数据字典表的DTO对象") +public class DictDto { + + @Schema(name = "dictType", title = "字典类型") + private String dictType; + + @Schema(name = "dictCode", title = "字典编码") + private String dictCode; + + @Schema(name = "dictValue", title = "字典值") + private String dictValue; + + @Schema(name = "dictLabel", title = "字典标签") + private String dictLabel; + + @Schema(name = "cssClass", title = "样式类名(前端显示使用)") + private String cssClass; + + @Schema(name = "listClass", title = "表格回显样式") + private String listClass; + + @Schema(name = "isDefault", title = "是否默认(0=否 1=是)") + private Boolean isDefault; + + @Schema(name = "orderNum", title = "排序序号") + private Integer orderNum; + + @Schema(name = "status", title = "状态(0=停用 1=启用)") + private Boolean status; + + @Schema(name = "remark", title = "备注说明") + private String remark; + + @Schema(name = "isDeleted", title = "删除标志(0=未删除 1=已删除)") + private Boolean isDeleted; + +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/dto/package-info.java b/auth-common/src/main/java/com/auth/common/model/dto/package-info.java new file mode 100644 index 0000000..37db3e2 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/dto/package-info.java @@ -0,0 +1 @@ +package com.auth.common.model.dto; \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/entity/DictEntity.java b/auth-common/src/main/java/com/auth/common/model/entity/DictEntity.java new file mode 100644 index 0000000..ab6f6da --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/entity/DictEntity.java @@ -0,0 +1,50 @@ +package com.auth.common.model.entity; + +import com.auth.common.model.common.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(chain = true) +@TableName("sys_dict") +@Schema(name = "Dict对象", title = "系统数据字典表", description = "系统数据字典表的实体类对象") +public class DictEntity extends BaseEntity { + + @Schema(name = "dictType", title = "字典类型") + private String dictType; + + @Schema(name = "dictCode", title = "字典编码") + private String dictCode; + + @Schema(name = "dictValue", title = "字典值") + private String dictValue; + + @Schema(name = "dictLabel", title = "字典标签") + private String dictLabel; + + @Schema(name = "cssClass", title = "样式类名(前端显示使用)") + private String cssClass; + + @Schema(name = "listClass", title = "表格回显样式") + private String listClass; + + @Schema(name = "isDefault", title = "是否默认(0=否 1=是)") + private Boolean isDefault; + + @Schema(name = "orderNum", title = "排序序号") + private Integer orderNum; + + @Schema(name = "status", title = "状态(0=停用 1=启用)") + private Boolean status; + + @Schema(name = "remark", title = "备注说明") + private String remark; + + @Schema(name = "isDeleted", title = "删除标志(0=未删除 1=已删除)") + private Boolean isDeleted; + +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/entity/package-info.java b/auth-common/src/main/java/com/auth/common/model/entity/package-info.java new file mode 100644 index 0000000..4609b49 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/entity/package-info.java @@ -0,0 +1 @@ +package com.auth.common.model.entity; \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/enums/LoginEnums.java b/auth-common/src/main/java/com/auth/common/model/enums/LoginEnums.java new file mode 100644 index 0000000..904d23b --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/enums/LoginEnums.java @@ -0,0 +1,18 @@ +package com.auth.common.model.enums; + +import lombok.Getter; + +@Getter +public enum LoginEnums { + // 邮箱登录请求 + EMAIL_STRATEGY("email"), + // 默认登录请求 + default_STRATEGY("default"), + ; + + private final String value; + + LoginEnums(String value) { + this.value = value; + } +} diff --git a/auth-common/src/main/java/com/auth/common/model/value/package-info.java b/auth-common/src/main/java/com/auth/common/model/value/package-info.java new file mode 100644 index 0000000..dfa3647 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/value/package-info.java @@ -0,0 +1,8 @@ +/** + * 何时使用值对象 + * 当需要表示一个没有独立业务标识的概念时 + * 当这个概念需要封装相关行为和验证逻辑时 + * 当这个概念可能被多个实体共享使用时 + * 当这个概念需要保证不变性和线程安全时 + */ +package com.auth.common.model.value; \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/model/vo/DictVo.java b/auth-common/src/main/java/com/auth/common/model/vo/DictVo.java new file mode 100644 index 0000000..0b6fdf1 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/model/vo/DictVo.java @@ -0,0 +1,48 @@ +package com.auth.common.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(name = "DictVO对象", title = "系统数据字典表", description = "系统数据字典表的VO对象") +public class DictVo { + + @Schema(name = "dictType", title = "字典类型") + private String dictType; + + @Schema(name = "dictCode", title = "字典编码") + private String dictCode; + + @Schema(name = "dictValue", title = "字典值") + private String dictValue; + + @Schema(name = "dictLabel", title = "字典标签") + private String dictLabel; + + @Schema(name = "cssClass", title = "样式类名(前端显示使用)") + private String cssClass; + + @Schema(name = "listClass", title = "表格回显样式") + private String listClass; + + @Schema(name = "isDefault", title = "是否默认(0=否 1=是)") + private Boolean isDefault; + + @Schema(name = "orderNum", title = "排序序号") + private Integer orderNum; + + @Schema(name = "status", title = "状态(0=停用 1=启用)") + private Boolean status; + + @Schema(name = "remark", title = "备注说明") + private String remark; + + @Schema(name = "isDeleted", title = "删除标志(0=未删除 1=已删除)") + private Boolean isDeleted; + +} + diff --git a/auth-common/src/main/java/com/auth/common/pattern/export/ExcelZipExportStrategy.java b/auth-common/src/main/java/com/auth/common/pattern/export/ExcelZipExportStrategy.java new file mode 100644 index 0000000..4d9f640 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/pattern/export/ExcelZipExportStrategy.java @@ -0,0 +1,35 @@ +package com.auth.common.pattern.export; + +import com.alibaba.excel.EasyExcel; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class ExcelZipExportStrategy implements ExportStrategy> { + + private final Class clazz; + private final String sheetName; + + public ExcelZipExportStrategy(Class clazz, String sheetName) { + this.clazz = clazz; + this.sheetName = sheetName; + } + + @Override + public void export(List data, ZipOutputStream zipOutputStream, String filename) { + try { + ByteArrayOutputStream excelOutputStream = new ByteArrayOutputStream(); + EasyExcel.write(excelOutputStream, clazz).sheet(sheetName).doWrite(data); + + // 将Excel写入到Zip中 + ZipEntry zipEntry = new ZipEntry(filename); + zipOutputStream.putNextEntry(zipEntry); + zipOutputStream.write(excelOutputStream.toByteArray()); + zipOutputStream.closeEntry(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/pattern/export/ExportStrategy.java b/auth-common/src/main/java/com/auth/common/pattern/export/ExportStrategy.java new file mode 100644 index 0000000..5b47928 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/pattern/export/ExportStrategy.java @@ -0,0 +1,8 @@ +package com.auth.common.pattern.export; + +import java.io.IOException; +import java.util.zip.ZipOutputStream; + +public interface ExportStrategy { + void export(T data, ZipOutputStream zipOutputStream, String filename) throws IOException; +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/pattern/export/JsonZipExportStrategy.java b/auth-common/src/main/java/com/auth/common/pattern/export/JsonZipExportStrategy.java new file mode 100644 index 0000000..df62a6d --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/pattern/export/JsonZipExportStrategy.java @@ -0,0 +1,23 @@ +package com.auth.common.pattern.export; + + +import com.alibaba.fastjson2.JSON; + +import java.nio.charset.StandardCharsets; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class JsonZipExportStrategy implements ExportStrategy { + + @Override + public void export(Object data, ZipOutputStream zipOutputStream, String filename) { + try { + ZipEntry zipEntry = new ZipEntry(filename); + zipOutputStream.putNextEntry(zipEntry); + zipOutputStream.write(JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8)); + zipOutputStream.closeEntry(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/pattern/processor/TreeProcessor.java b/auth-common/src/main/java/com/auth/common/pattern/processor/TreeProcessor.java new file mode 100644 index 0000000..d62eb98 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/pattern/processor/TreeProcessor.java @@ -0,0 +1,18 @@ +package com.auth.common.pattern.processor; + +import java.util.List; + +/* 构建树型结构模板处理器 */ +public abstract class TreeProcessor { + public final List process(List list) { + List roots = findRoots(list); + for (T root : roots) { + buildChildren(root, list); + } + return roots; + } + + protected abstract List findRoots(List list); + + protected abstract void buildChildren(T parent, List list); +} diff --git a/auth-common/src/main/java/com/auth/common/utils/FileUtil.java b/auth-common/src/main/java/com/auth/common/utils/FileUtil.java new file mode 100644 index 0000000..a5e4856 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/utils/FileUtil.java @@ -0,0 +1,55 @@ +package com.auth.common.utils; + + +import org.springframework.http.HttpHeaders; + +/* + * 文件工具类 + * 格式化字节大小 + */ +public class FileUtil { + /** + * 获取文件大小字符串 + * + * @param fileSize 文件大小 + * @return 格式化后文件大小 + */ + public static String getSize(Long fileSize) { + double fileSizeInKB = fileSize / 1024.00; + double fileSizeInMB = fileSizeInKB / 1024; + double fileSizeInGB = fileSizeInMB / 1024; + + String size; + if (fileSizeInGB >= 1) { + fileSizeInGB = Double.parseDouble(String.format("%.2f", fileSizeInGB)); + size = fileSizeInGB + "GB"; + } else if (fileSizeInMB >= 1) { + fileSizeInMB = Double.parseDouble(String.format("%.2f", fileSizeInMB)); + size = fileSizeInMB + "MB"; + } else if (fileSizeInKB >= 1) { + fileSizeInKB = Double.parseDouble(String.format("%.2f", fileSizeInKB)); + size = fileSizeInKB + "KB"; + } else { + size = fileSize + "B"; + } + + return size; + } + + /** + * 构建二进制文件响应头 + * + * @param filename 文件名 + * @return HttpHeaders + */ + public static HttpHeaders buildHttpHeadersByBinary(String filename) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Disposition", "attachment; filename=" + filename); + headers.add("Cache-Control", "no-cache, no-store, must-revalidate"); + headers.add("Pragma", "no-cache"); + headers.add("Expires", "0"); + + return headers; + } + +} diff --git a/auth-common/src/main/java/com/auth/common/utils/JwtTokenUtil.java b/auth-common/src/main/java/com/auth/common/utils/JwtTokenUtil.java new file mode 100644 index 0000000..3404d5d --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/utils/JwtTokenUtil.java @@ -0,0 +1,225 @@ +package com.auth.common.utils; + +import com.auth.common.exception.AuthenticSecurityException; +import com.auth.common.model.common.result.ResultCodeEnum; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import javax.crypto.SecretKey; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Slf4j +public class JwtTokenUtil { + + /** + * 使用默认主题,默认秘钥,自定义时间,创建集合形式token + * + * @param map 集合 + * @param day 过期时间 + * @return token + */ + public static String createTokenWithMap(Map map, String subject, SecretKey key, Long day) { + return Jwts.builder() + .subject(subject) + .signWith(key) + .expiration(new Date(System.currentTimeMillis() + day)) + .claims(map) + .id(UUID.randomUUID().toString()) + .compressWith(Jwts.ZIP.GZIP).compact(); + } + + /** + * 使用自定义主题,自定义时间,创建集合形式token + * + * @param map 集合 + * @param subject 主题 + * @param time 过期时间 + * @return token + */ + public static String createTokenWithMap(Map map, String subject, SecretKey key, Date time) { + return Jwts.builder() + .subject(subject) + .expiration(time) + .claims(map) + .id(UUID.randomUUID().toString()) + .signWith(key) + .compressWith(Jwts.ZIP.GZIP).compact(); + } + + /** + * 创建集合形式token + * + * @param map 集合 + * @param subject 主题 + * @param day 过期时间 + * @return token + */ + public static String createTokenWithMap(Map map, String subject, SecretKey key, Integer day) { + return Jwts.builder() + .subject(subject) + .expiration(new Date(System.currentTimeMillis() + day)) + .claims(map) + .id(UUID.randomUUID().toString()) + .signWith(key) + .compressWith(Jwts.ZIP.GZIP).compact(); + } + + /** + * 根据用户名和ID创建token + * + * @param userId 用户ID + * @param username 用户名 + * @param day 过期时间 + * @return token值 + */ + public static String createToken(Long userId, String username, String subject, SecretKey key, Long day) { + return Jwts.builder() + .subject(subject) + .expiration(new Date(System.currentTimeMillis() + day)) + .claim("userId", userId) + .claim("username", username) + .id(UUID.randomUUID().toString()) + .signWith(key) + .compressWith(Jwts.ZIP.GZIP).compact(); + } + + /** + * 根据用户名和ID创建token + * 在载体中添加角色和权限 + * + * @param userId 用户ID + * @param username 用户名 + * @param second 过期时间 + * @return token值 + */ + public static String createToken(Long userId, String username, + List roles, List permissions, + String subject, SecretKey key, Long second) { + // 传进来的是秒,转成未来过期时间 + LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(second); + Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + + // 转成过期时间 + String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + + return Jwts.builder() + .subject(subject) + .expiration(date) + .claim("expiredTime", format) + .claim("userId", userId) + .claim("username", username) + .claim("roles", roles) + .claim("permissions", permissions) + .id(UUID.randomUUID().toString()) + .signWith(key) + .compressWith(Jwts.ZIP.GZIP).compact(); + } + + /** + * 使用token获取map集合,使用默认秘钥 + * + * @param token token + * @return map集合 + */ + public static Map getMapByToken(String token, SecretKey key) { + try { + if (!StringUtils.hasText(token)) + throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + // 将 body 值转为map + return Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload(); + + } catch (Exception exception) { + throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + } + } + + private static String getSubjectByTokenHandler(String token, SecretKey key) { + try { + if (!StringUtils.hasText(token)) + throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + Jws claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token); + Claims body = claimsJws.getPayload(); + + return body.getSubject(); + + } catch (Exception exception) { + throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + } + } + + /** + * 根据token获取主题 + * + * @param token token + * @return 主题 + */ + public static String getSubjectByToken(String token, SecretKey key) { + return getSubjectByTokenHandler(token, key); + } + + /** + * 根据token获取用户ID + * + * @param token token + * @return 用户ID + */ + public static Long getUserId(String token, SecretKey key) { + try { + if (!StringUtils.hasText(token)) throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + + Jws claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token); + Claims claims = claimsJws.getPayload(); + + return Long.valueOf(String.valueOf(claims.get("userId"))); + } catch (Exception exception) { + throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + } + } + + /** + * 根据token获取用户名 + * + * @param token token + * @return 用户名 + */ + public static String getUsername(String token, SecretKey key) { + try { + if (!StringUtils.hasText(token)) return ""; + + Jws claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token); + Claims claims = claimsJws.getPayload(); + return (String) claims.get("username"); + } catch (Exception exception) { + throw new AuthenticSecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED); + } + } + + /** + * 判断是否过期 + * + * @param token token + * @return 是否过期 + * @throws RuntimeException ⚠️解析失败和过期都属于异常类型 + */ + public static boolean isExpired(String token, SecretKey key) { + try { + Jws claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token); + Date expiration = claimsJws.getPayload().getExpiration(); + + return expiration != null && expiration.before(new Date()); + } catch (RuntimeException exception) { + // ResultCodeEnum codeEnum = ResultCodeEnum.AUTHENTICATION_EXPIRED; + // throw new IllegalArgumentException(codeEnum.getMessage(), exception); + return true; + } + } +} \ No newline at end of file diff --git a/auth-common/src/main/java/com/auth/common/utils/ResponseUtil.java b/auth-common/src/main/java/com/auth/common/utils/ResponseUtil.java new file mode 100644 index 0000000..1d2c970 --- /dev/null +++ b/auth-common/src/main/java/com/auth/common/utils/ResponseUtil.java @@ -0,0 +1,26 @@ +package com.auth.common.utils; + +import com.auth.common.model.common.result.Result; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; + +import java.io.IOException; + +public class ResponseUtil { + + public static void out(HttpServletResponse response, Result result) { + try { + ObjectMapper mapper = new ObjectMapper(); + + // 注册JavaTimeModule模块 + mapper.registerModule(new JavaTimeModule()); + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpStatus.OK.value()); + mapper.writeValue(response.getWriter(), result); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file