From e23f3f777d6496a9bc41997179fb85083b13745e Mon Sep 17 00:00:00 2001
From: wenxin <1731551615@qq.com>
Date: Fri, 6 Dec 2024 17:49:39 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9E=97=E6=A0=96=E4=BA=91SpringBoot=E4=BE=9D?=
=?UTF-8?q?=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 38 +++++
README.md | 71 +++++++++
pom.xml | 81 +++++++++++
.../linxyun/core/common/entity/Result.java | 57 ++++++++
.../linxyun/core/common/entity/UserAuth.java | 16 +++
.../linxyun/core/common/enums/ErrorCode.java | 117 +++++++++++++++
.../linxyun/core/common/enums/FileType.java | 27 ++++
.../core/config/AutoConfiguration.java | 8 ++
.../core/config/WebInterceptorConfig.java | 39 +++++
.../core/interceptor/SecurityInterceptor.java | 105 ++++++++++++++
.../core/properties/LinxyunProperties.java | 88 ++++++++++++
.../java/com/linxyun/core/utils/ApiUtils.java | 136 ++++++++++++++++++
.../com/linxyun/core/utils/FileUtils.java | 94 ++++++++++++
.../com/linxyun/core/utils/HttpUtils.java | 83 +++++++++++
.../com/linxyun/core/utils/StreamUtils.java | 19 +++
.../com/linxyun/core/utils/TimeUtils.java | 25 ++++
.../java/com/linxyun/core/utils/URLUtils.java | 35 +++++
src/main/resources/META-INF/spring.factories | 1 +
src/main/resources/application.yml | 0
19 files changed, 1040 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 pom.xml
create mode 100644 src/main/java/com/linxyun/core/common/entity/Result.java
create mode 100644 src/main/java/com/linxyun/core/common/entity/UserAuth.java
create mode 100644 src/main/java/com/linxyun/core/common/enums/ErrorCode.java
create mode 100644 src/main/java/com/linxyun/core/common/enums/FileType.java
create mode 100644 src/main/java/com/linxyun/core/config/AutoConfiguration.java
create mode 100644 src/main/java/com/linxyun/core/config/WebInterceptorConfig.java
create mode 100644 src/main/java/com/linxyun/core/interceptor/SecurityInterceptor.java
create mode 100644 src/main/java/com/linxyun/core/properties/LinxyunProperties.java
create mode 100644 src/main/java/com/linxyun/core/utils/ApiUtils.java
create mode 100644 src/main/java/com/linxyun/core/utils/FileUtils.java
create mode 100644 src/main/java/com/linxyun/core/utils/HttpUtils.java
create mode 100644 src/main/java/com/linxyun/core/utils/StreamUtils.java
create mode 100644 src/main/java/com/linxyun/core/utils/TimeUtils.java
create mode 100644 src/main/java/com/linxyun/core/utils/URLUtils.java
create mode 100644 src/main/resources/META-INF/spring.factories
create mode 100644 src/main/resources/application.yml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..83cfb9d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,71 @@
+# Spring-boot-starter-linxyun 林栖云依赖
+
+- 拉取项目
+
+ ```
+ git clone http://codegit.linxyun.com/wenxin/spring-boot-starter-linxyun.git
+ ```
+- 添加到本地 maven 仓库
+
+ ```
+ mvn clean install -Dmaven.test.skip=true
+ ```
+- 在项目中添加 maven 依赖
+
+ ```
+
+ com.linxyun
+ spring-boot-starter-linxyun
+ 1.0.0
+
+ ```
+- 添加配置文件
+ - application.yml
+
+ ```
+ linxyun:
+ url: http://www.linxyun.com
+ # 企业编码
+ ent-code: 57
+ # 项目名称
+ project: homeworkor
+ # 角色
+ role:
+ 1101:
+ - /class/**
+ 1102:
+ - /user
+ # 白名单
+ white-list:
+ - /swagger-ui.html
+ - /v3/api-docs/**
+ - /doc.html
+ - /error
+ upload:
+ # 图片压缩质量 0~1
+ img-quality: 0.5
+ ```
+ - application.properties
+ ```
+ # linxyun 配置
+ linxyun.url=http://www.linxyun.com
+ linxyun.ent-code=56
+ linxyun.project=smartroadlamp
+
+ # 角色配置
+ linxyun.role.1101[0]=/**
+ linxyun.role.1102[0]=/user
+
+ # 白名单
+ linxyun.white-list[0]=/swagger-ui.html
+ linxyun.white-list[1]=/v3/api-docs/**
+ linxyun.white-list[2]=/doc.html
+ linxyun.white-list[3]=/error
+
+ # 上传配置
+ linxyun.upload.img-quality=0.5
+ ```
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..15f5152
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,81 @@
+
+ 4.0.0
+
+ com.linxyun
+ spring-boot-starter-linxyun
+ 1.0.0
+
+ org.springframework.boot
+ spring-boot-starters
+ 2.1.0.RELEASE
+
+ Spring Boot Linxyun Starter
+ Starter for using Linxyun
+
+
+
+ Wen Xin
+ 1731551615@qq.com
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ provided
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.34
+
+
+
+ net.coobird
+ thumbnailator
+ 0.4.20
+
+
+
+ cn.hutool
+ hutool-http
+ 5.8.34
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.53
+
+
+
+ net.jodah
+ expiringmap
+ 0.5.11
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ 8
+ 8
+ UTF-8
+
+
diff --git a/src/main/java/com/linxyun/core/common/entity/Result.java b/src/main/java/com/linxyun/core/common/entity/Result.java
new file mode 100644
index 0000000..6e93293
--- /dev/null
+++ b/src/main/java/com/linxyun/core/common/entity/Result.java
@@ -0,0 +1,57 @@
+package com.linxyun.core.common.entity;
+
+import com.linxyun.core.common.enums.ErrorCode;
+import lombok.Data;
+import lombok.ToString;
+
+@Data
+@ToString
+public class Result {
+ public E data;
+ public String code;
+ public String msg;
+ public boolean success = true;
+
+ public Result(E data, String code, String msg, boolean success) {
+ this.data = data;
+ this.code = code;
+ this.msg = msg;
+ this.success = success;
+ }
+
+ public static Result error(ErrorCode codeEnum) {
+ return new Result<>(null, codeEnum.getCode(), codeEnum.getMessage(), false);
+ }
+
+ public static Result error(ErrorCode codeEnum, String message) {
+ return new Result<>(null, codeEnum.getCode(), message, false);
+ }
+
+ public static Result error(String code, String msg) {
+ return new Result<>(null, code, msg, false);
+ }
+
+ public static Result ok() {
+ return new Result<>(null, "0000", "操作成功", true);
+ }
+
+ public static Result ok(String msg) {
+ return new Result<>(null, "0000", msg, true);
+
+ }
+ public static Result ok(T data) {
+ return new Result<>(data, "0000", "操作成功", true);
+
+ }
+
+ public static Result ok(T data, String msg) {
+ return new Result<>(data, "0000", msg, true);
+ }
+
+ public Result() {
+ this.success = false;
+ this.code = "9999";
+ this.msg = "操作失败";
+ this.data = null;
+ }
+}
diff --git a/src/main/java/com/linxyun/core/common/entity/UserAuth.java b/src/main/java/com/linxyun/core/common/entity/UserAuth.java
new file mode 100644
index 0000000..b198c2c
--- /dev/null
+++ b/src/main/java/com/linxyun/core/common/entity/UserAuth.java
@@ -0,0 +1,16 @@
+package com.linxyun.core.common.entity;
+
+import lombok.Data;
+
+@Data
+public class UserAuth {
+ private String ContainerID;
+ private String EntCode;
+ private String LoginEntCode;
+ private String LoginIP;
+ private String LoginNodeID;
+ private String LoginTime;
+ private String UserID;
+ private String UserName;
+ private String UserRoles;
+}
diff --git a/src/main/java/com/linxyun/core/common/enums/ErrorCode.java b/src/main/java/com/linxyun/core/common/enums/ErrorCode.java
new file mode 100644
index 0000000..a06de95
--- /dev/null
+++ b/src/main/java/com/linxyun/core/common/enums/ErrorCode.java
@@ -0,0 +1,117 @@
+package com.linxyun.core.common.enums;
+
+import lombok.Getter;
+
+
+@Getter
+public enum ErrorCode {
+
+ // 用户不存在或密码不匹配
+ USER_NOT_FOUND_OR_PASSWORD_MISMATCH("1006", "用户不存在或密码不匹配"),
+
+ // 参数错误
+ PARAMETER_ERROR("204", "参数错误"),
+
+ // URI 不存在
+ URI_NOT_FOUND("240", "URI不存在"),
+
+ // URI 已经存在
+ URI_ALREADY_EXISTS("241", "URI已经存在"),
+
+ // 服务的路由异常,请联系管理员
+ ROUTING_EXCEPTION("320", "服务的路由异常,请联系管理员"),
+
+ // 处理超时,服务配置出错,请联系管理员
+ TIMEOUT_ERROR("998", "处理超时,服务配置出错,请联系管理员"),
+
+ // 登录超时,请重新登录
+ LOGIN_TIMEOUT("9998", "登录超时,请重新登录"),
+
+ // 登录超时,请重新登录
+ LOGIN_TIMEOUT_2("401", "登录超时,请重新登录"),
+
+ // 操作失败
+ OPERATION_FAIL("1005", "操作失败"),
+
+ // 用户已经存在
+ USER_ALREADY_EXISTS("1007", "用户已经存在"),
+
+ // 操作ID配置错误,请联系管理员
+ OPERATION_ID_ERROR("1008", "操作ID配置错误,请联系管理员"),
+
+ // 用户ID参数错误
+ USER_ID_PARAMETER_ERROR("1009", "用户ID参数错误"),
+
+ // 用户邮箱地址错误
+ USER_EMAIL_ERROR("1011", "用户邮箱地址错误"),
+
+ // 邮箱地址已经存在
+ EMAIL_ALREADY_EXISTS("1012", "邮箱地址已经存在"),
+
+ // 手机号码格式不正确
+ PHONE_NUMBER_FORMAT_ERROR("1013", "手机号码格式不正确"),
+
+ // 手机号码已经存在
+ PHONE_NUMBER_ALREADY_EXISTS("1014", "手机号码已经存在"),
+
+ // 登录验证出错
+ LOGIN_VALIDATION_ERROR("1020", "登录验证出错"),
+
+ // 用户没有登录
+ USER_NOT_LOGGED_IN("1021", "用户没有登录"),
+
+ // 用户状态不正常
+ USER_STATUS_ERROR("1022", "用户状态不正常"),
+
+ // 发送验证码失败
+ SEND_VERIFICATION_CODE_FAIL("1023", "发送验证码失败"),
+
+ // 验证码验证失败
+ VERIFICATION_CODE_FAIL("1024", "验证码验证失败"),
+
+ USER_NO_AUTHORITY("1025", "用户没有权限"),
+
+ // 用户组已经存在
+ USER_GROUP_ALREADY_EXISTS("1030", "用户组已经存在"),
+
+ // 用户组不存在
+ USER_GROUP_NOT_EXISTS("1031", "用户组不经存在"),
+
+ // 操作出错
+ OPERATION_ERROR("1999", "操作出错"),
+
+ // 前端处理验证数据出错
+ FRONTEND_VALIDATION_ERROR("886000", "前端处理验证数据出错"),
+
+ // 请先删除内容
+ DELETE_CONTENT_FIRST("2012", "请先删除内容"),
+ // 文件不存在
+ FILE_NOT_FOUND("3000", "文件不存在"),
+ // 文件大小超出限制
+ FILE_SIZE_EXCEEDED("3001", "文件大小超出限制"),
+ // 文件上传失败
+ FILE_UPLOAD_FAILED("3002", "文件上传失败"),
+ // 请求失败
+ REQUEST_FAILED("3003", "请求失败"),
+ NONE("6666666", "站位");
+
+
+
+ private final String code;
+ private final String message;
+
+ ErrorCode(String code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ // 根据错误代码获取对应的错误信息
+ public static String getMessageByCode(String code) {
+ for (ErrorCode errorCode : values()) {
+ if (errorCode.getCode().equals(code)) {
+ return errorCode.getMessage();
+ }
+ }
+ return "未知错误";
+ }
+}
diff --git a/src/main/java/com/linxyun/core/common/enums/FileType.java b/src/main/java/com/linxyun/core/common/enums/FileType.java
new file mode 100644
index 0000000..b5ffbd2
--- /dev/null
+++ b/src/main/java/com/linxyun/core/common/enums/FileType.java
@@ -0,0 +1,27 @@
+package com.linxyun.core.common.enums;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public enum FileType {
+ IMAGE(Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp")),
+ VIDEO(Arrays.asList(".mp4", ".mkv", ".mov", ".avi")),
+ AUDIO(Arrays.asList(".mp3", ".wav", ".ogg")),
+ DOCUMENT(Arrays.asList(".pdf", ".doc", ".docx", ".xls", ".xlsx")),
+ OTHER(new ArrayList<>());
+
+ private final List extensions;
+
+ FileType(List extensions) {
+ this.extensions = extensions;
+ }
+ public static FileType fromExtension(String fileName) {
+ String extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
+ return Arrays.stream(values())
+ .filter(type -> type.extensions.contains(extension))
+ .findFirst()
+ .orElse(OTHER);
+ }
+}
+
diff --git a/src/main/java/com/linxyun/core/config/AutoConfiguration.java b/src/main/java/com/linxyun/core/config/AutoConfiguration.java
new file mode 100644
index 0000000..a83c608
--- /dev/null
+++ b/src/main/java/com/linxyun/core/config/AutoConfiguration.java
@@ -0,0 +1,8 @@
+package com.linxyun.core.config;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.stereotype.Component;
+
+@ComponentScan(basePackages = "com.linxyun.core")
+public class AutoConfiguration {
+}
diff --git a/src/main/java/com/linxyun/core/config/WebInterceptorConfig.java b/src/main/java/com/linxyun/core/config/WebInterceptorConfig.java
new file mode 100644
index 0000000..6ec3b56
--- /dev/null
+++ b/src/main/java/com/linxyun/core/config/WebInterceptorConfig.java
@@ -0,0 +1,39 @@
+package com.linxyun.core.config;
+
+import com.linxyun.core.interceptor.SecurityInterceptor;
+import com.linxyun.core.properties.LinxyunProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Slf4j
+@Configuration
+@RequiredArgsConstructor
+public class WebInterceptorConfig implements WebMvcConfigurer {
+
+ private final LinxyunProperties linxyunProperties;
+
+ private final SecurityInterceptor securityInterceptor;
+
+ // 添加拦截器
+ @Override
+ public void addInterceptors(@NotNull InterceptorRegistry registry) {
+ log.info("添加鉴权拦截器:Linxyun Security Interceptor");
+ List whiteList = linxyunProperties.getWhiteList();
+ // 添加默认的静态资源路径排除
+ whiteList.add("/static/**");
+ whiteList.add("/public/**");
+ whiteList.add("/resources/**");
+ whiteList.add("/META-INF/resources/**");
+ whiteList.add("/webjars/**");
+ log.info("白名单:{}", whiteList);
+ registry.addInterceptor(securityInterceptor)
+ .excludePathPatterns(whiteList)
+ .addPathPatterns("/**");
+ }
+}
diff --git a/src/main/java/com/linxyun/core/interceptor/SecurityInterceptor.java b/src/main/java/com/linxyun/core/interceptor/SecurityInterceptor.java
new file mode 100644
index 0000000..5033c54
--- /dev/null
+++ b/src/main/java/com/linxyun/core/interceptor/SecurityInterceptor.java
@@ -0,0 +1,105 @@
+package com.linxyun.core.interceptor;
+
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.linxyun.core.common.entity.Result;
+import com.linxyun.core.common.entity.UserAuth;
+import com.linxyun.core.common.enums.ErrorCode;
+import com.linxyun.core.properties.LinxyunProperties;
+import com.linxyun.core.utils.ApiUtils;
+import com.linxyun.core.utils.URLUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SecurityInterceptor implements HandlerInterceptor {
+
+ private final LinxyunProperties linxyunProperties;
+
+
+ // 生命周期: 拦截器在请求处理之前调用,只有返回true才会继续调用下一个拦截器或者处理器,否则不会调用
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ log.info("鉴权拦截:{} {}", request.getMethod(), request.getRequestURI());
+
+ // 获取请求头上的Token
+ String token = request.getHeader("Token");
+ if (StringUtils.isEmpty(token)) {
+ token = request.getParameter("Token");
+ }
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("application/json; charset=utf-8");
+ Result result;
+ if (StringUtils.isEmpty(token)) {
+ log.info("请求头中无Authorization信息");
+ result = Result.error(ErrorCode.USER_NOT_LOGGED_IN);
+ response.getWriter().write(JSON.toJSONString(result));
+ return false;
+ }
+ UserAuth userAuth = ApiUtils.getUserAuth(token);
+ if (userAuth == null) {
+ // 如果为空,说明 token 无效
+ log.info("Token 无效:{}", token);
+ result = Result.error(ErrorCode.LOGIN_VALIDATION_ERROR);
+ response.getWriter().write(JSON.toJSONString(result));
+ return false;
+ }
+ String userRole = userAuth.getUserRoles();
+ if (StringUtils.isEmpty(userRole)) {
+ log.info("用户权限为空:{}", userAuth.getUserRoles());
+ result = Result.error(ErrorCode.LOGIN_VALIDATION_ERROR);
+ response.getWriter().write(JSON.toJSONString(result));
+ return false;
+ }
+ if (!linxyunProperties.getEntCode().equals(userAuth.getEntCode())) {
+ log.info("用户企业编码错误:{}", userAuth.getEntCode());
+ result = Result.error(ErrorCode.LOGIN_VALIDATION_ERROR);
+ response.getWriter().write(JSON.toJSONString(result));
+ return false;
+ }
+ // 验证用户是否拥有权限
+ Map> roleMap = linxyunProperties.getRole();
+ if (!roleMap.containsKey(userAuth.getUserRoles())) {
+ log.info("用户权限未在系统权限中:{}", userAuth.getUserRoles());
+ result = Result.error(ErrorCode.LOGIN_VALIDATION_ERROR);
+ response.getWriter().write(JSON.toJSONString(result));
+ return false;
+ }
+ List pathList = roleMap.get(userRole);
+ // 是否有权限访问该路径
+ if (!URLUtils.isUriAuthorized(request.getRequestURI(), pathList)) {
+ log.info("用户无权访问该路径:{}", request.getRequestURI());
+ result = Result.error(ErrorCode.USER_NO_AUTHORITY);
+ response.getWriter().write(JSON.toJSONString(result));
+ return false;
+ }
+ log.info("鉴权通过:{} {}", token, request.getRequestURI());
+ return true;
+ }
+
+ // 生命周期: 拦截器在请求处理之后调用,但是此时还没有返回视图,只有返回true才会继续调用下一个拦截器或者处理器,否则不会调用
+ @Override
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+
+ }
+
+ // 生命周期: 拦截器在视图渲染之后调用,即在视图渲染完成之后,页面响应给客户端之前,只有返回true才会继续调用下一个拦截器或者处理器,否则不会调用
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+
+ }
+
+
+}
diff --git a/src/main/java/com/linxyun/core/properties/LinxyunProperties.java b/src/main/java/com/linxyun/core/properties/LinxyunProperties.java
new file mode 100644
index 0000000..7681eb9
--- /dev/null
+++ b/src/main/java/com/linxyun/core/properties/LinxyunProperties.java
@@ -0,0 +1,88 @@
+package com.linxyun.core.properties;
+
+import lombok.Data;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import javax.annotation.PostConstruct;
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "linxyun")
+@Validated
+public class LinxyunProperties implements ApplicationContextAware {
+
+ private static ApplicationContext context;
+
+ /**
+ * url
+ */
+ private String url = "http://www.linxyun.com";
+ /**
+ * 企业编码
+ */
+ @NotNull(message = "The 'entCode' property is mandatory")
+ private String entCode;
+ /**
+ * 项目名称
+ */
+ @NotNull(message = "The 'project' property is mandatory")
+ private String project;
+ /**
+ * 角色权限
+ */
+ private Map> role = new HashMap<>();
+ /**
+ * 白名单
+ */
+ private List whiteList = new ArrayList<>();
+ /**
+ * 上传配置
+ */
+ private Upload upload = new Upload();
+
+ @Data
+ public static class Upload {
+ private Float imgQuality = 1.0f;
+ }
+
+ @Value("${server.servlet.context-path}")
+ private String contextPath;
+
+ @PostConstruct
+ public void init() {
+ // 解决 springboot 设置 context-path,导致权限路由验证失效
+ if (contextPath != null && !contextPath.isEmpty()) {
+ for (String key : role.keySet()) {
+ List paths = role.get(key);
+ for (int i = 0; i < paths.size(); i++) {
+ String path = paths.get(i);
+ if (!path.startsWith(contextPath)) {
+ paths.set(i, contextPath + path);
+ }
+ }
+ }
+ }
+ }
+
+ // 静态方法来访问实例
+ public static LinxyunProperties getInstance() {
+ return context.getBean(LinxyunProperties.class);
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ context = applicationContext;
+ }
+}
+
diff --git a/src/main/java/com/linxyun/core/utils/ApiUtils.java b/src/main/java/com/linxyun/core/utils/ApiUtils.java
new file mode 100644
index 0000000..954b67f
--- /dev/null
+++ b/src/main/java/com/linxyun/core/utils/ApiUtils.java
@@ -0,0 +1,136 @@
+package com.linxyun.core.utils;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSONObject;
+import com.linxyun.core.common.entity.UserAuth;
+import com.linxyun.core.properties.LinxyunProperties;
+import lombok.extern.slf4j.Slf4j;
+import net.jodah.expiringmap.ExpirationPolicy;
+import net.jodah.expiringmap.ExpiringMap;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+
+@Slf4j
+public class ApiUtils {
+
+ private final static ExpiringMap USER_AUTH_MAP = ExpiringMap.builder()
+ // 设置最大值,添加第11个entry时,会导致第1个立马过期(即使没到过期时间)。默认 Integer.MAX_VALUE
+ .maxSize(10)
+ // 允许 Map 元素具有各自的到期时间,并允许更改到期时间。
+ .variableExpiration()
+ // 设置过期时间,如果key不设置过期时间,key永久有效。
+ .expiration(1, TimeUnit.DAYS)
+ .asyncExpirationListener((key, value) -> log.info("USER_AUTH_MAP key数据被删除了 -> key={}, value={}", key, value))
+ //设置 Map 的过期策略
+ .expirationPolicy(ExpirationPolicy.CREATED)
+ .build();
+
+ private static final LinxyunProperties linxyunProperties = LinxyunProperties.getInstance();
+ private static final String url = linxyunProperties.getUrl();
+ private static final String projectName = linxyunProperties.getProject();
+
+
+ /**
+ * 单点登录
+ */
+ public static UserAuth userLoginAuth(String token) {
+ try {
+ if (StringUtils.isEmpty(token)) {
+ return null;
+ }
+ String url = getApiUrl("userLoginAuth");
+ JSONObject body = new JSONObject();
+ body.put("LoginID", token);
+ JSONObject result = HttpUtils.post(url, body);
+ if (result == null) {
+ log.error("LinxyunUtils-userLoginAuth result is null");
+ return null;
+ }
+ log.info("LinxyunUtils-userLoginAuth result: {}", result);
+ if (!result.getBoolean("success")) {
+ String msg = result.getString("msg");
+ log.error("LinxyunUtils-userLoginAuth result is not success: {}", msg);
+ return null;
+ }
+ String data = result.getString("data");
+ if (StringUtils.isEmpty(data)) {
+ log.error("LinxyunUtils-userLoginAuth result.data is null");
+ return null;
+ }
+ UserAuth userAuth = JSONObject.parseObject(data, UserAuth.class);
+ USER_AUTH_MAP.put(token, userAuth);
+ return userAuth;
+ } catch (Exception e) {
+ log.error("linxyunUtils.userLoginAuth error: {}", e.getMessage());
+ return null;
+ }
+ }
+
+ public static UserAuth getUserAuth(String token) {
+ if (StringUtils.isEmpty(token)) {
+ return null;
+ }
+ boolean isExist = USER_AUTH_MAP.containsKey(token);
+ if (isExist) {
+ // 存在,直接获取缓存的数据
+ return USER_AUTH_MAP.get(token);
+ }
+ return userLoginAuth(token);
+ }
+
+ public static String getApiUrl(String path) {
+ String baseUrl = url + "/" + projectName;
+ if (StringUtils.isEmpty(path)) {
+ return baseUrl;
+ }
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ if (!path.endsWith(".action")) {
+ path += ".action";
+ }
+ return baseUrl + path;
+ }
+
+ /**
+ * 使用 OkHttp 上传文件
+ *
+ * @param fileBytes 文件字节数组
+ * @param fileName 文件名
+ * @return 上传成功时返回服务器响应的 "data" 字段;否则返回 null
+ */
+ public static String uploadFile(byte[] fileBytes, String fileName) {
+ String uploadUrl = url + "/eslithe/uploadFile.action";
+
+ // 创建请求
+ HttpRequest request = HttpUtil.createPost(uploadUrl);
+ request.form("file", fileBytes, fileName);
+ // 执行请求
+ try (HttpResponse response = request.execute()) {
+ if (response.isOk() && response.body() != null) {
+ String responseBody = response.body();
+ log.info("FileUploadTool uploadFile result: {}", responseBody);
+
+ // 解析响应
+ JSONObject jsonObject = JSONObject.parseObject(responseBody);
+ if (jsonObject != null
+ && jsonObject.getBoolean("success") != null
+ && jsonObject.getBoolean("success")
+ && jsonObject.containsKey("data")) {
+ return jsonObject.getString("data");
+ } else {
+ log.error("FileUploadTool uploadFile failed: {}", responseBody);
+ return null;
+ }
+ } else {
+ log.error("FileUploadTool uploadFile failed, HTTP code: {}", response.getStatus());
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/linxyun/core/utils/FileUtils.java b/src/main/java/com/linxyun/core/utils/FileUtils.java
new file mode 100644
index 0000000..f3ceb2e
--- /dev/null
+++ b/src/main/java/com/linxyun/core/utils/FileUtils.java
@@ -0,0 +1,94 @@
+package com.linxyun.core.utils;
+
+import cn.hutool.http.HttpResponse;
+import com.linxyun.core.common.enums.FileType;
+import com.linxyun.core.properties.LinxyunProperties;
+import lombok.extern.slf4j.Slf4j;
+import net.coobird.thumbnailator.Thumbnails;
+
+import java.io.*;
+
+@Slf4j
+public class FileUtils {
+
+ private static final LinxyunProperties linxyunProperties = LinxyunProperties.getInstance();
+
+ /**
+ * 下载文件到指定目录
+ * @param fileUrl 文件 URL
+ * @param targetDir 本地目标目录
+ * @param fileName 保存的文件名
+ * @throws IOException 如果发生 IO 异常
+ */
+ public static void downloadFile(String fileUrl, String targetDir, String fileName) {
+ // 执行请求
+ try (HttpResponse response = HttpUtils.getResp(fileUrl)) {
+ if (!response.isOk()) {
+ log.info("文件下载失败, HTTP 响应码: {}", response.getStatus());
+ return;
+ }
+ // 确保目标目录存在
+ File directory = new File(targetDir);
+ if (!directory.exists() && !directory.mkdirs()) {
+ log.error("无法创建目标目录: {}", targetDir);
+ return;
+ }
+ // 获取输入流并保存文件
+ byte[] bytes = response.body().getBytes();
+ FileOutputStream outputStream = new FileOutputStream(new File(directory, fileName));
+ outputStream.write(bytes);
+ log.info("文件下载完成: {}", targetDir + File.separator + fileName);
+ } catch (FileNotFoundException e) {
+ log.error("文件不存在", e);
+ } catch (IOException e) {
+ log.error("文件下载异常", e);
+ }
+ }
+
+ /**
+ * 下载指定 URL 的文件,并将内容转化为字节数组。
+ *
+ * @param fileUrl 文件的 URL
+ * @param fileName 文件名(可以用来关联下载到本地的文件)
+ * @return 文件内容的字节数组
+ */
+
+ public static String uploadFileByUrl(String fileUrl, String fileName) {
+ FileType fileType = FileType.fromExtension(fileName);
+ // 执行请求
+ try (HttpResponse response = HttpUtils.getResp(fileUrl)) {
+ if (!response.isOk()) {
+ log.error("Failed to download file: {}", response.body());
+ return null;
+ }
+ // 获取文件内容作为字节数组
+ try (InputStream inputStream = response.bodyStream()) {
+ // 如果是图片,进行压缩
+ if (fileType == FileType.IMAGE) {
+ try (ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream()) {
+ log.info("压缩图片:{} 图片质量: {}", fileUrl, linxyunProperties.getUpload().getImgQuality());
+ // 压缩图片
+ Thumbnails.of(inputStream)
+ .outputQuality(linxyunProperties.getUpload().getImgQuality()) // 设置输出质量 (0~1)
+ .scale(1.0) // 保持原尺寸(如需缩放可调整 scale 值或使用 size 方法)
+ .toOutputStream(compressedOutputStream);
+
+ // 日志输出原始大小和压缩后大小
+ log.info("图片原始大小: {} 字节", response.contentLength());
+ log.info("图片压缩后大小: {} 字节", compressedOutputStream.size());
+ // 上传压缩后的图片
+ return ApiUtils.uploadFile(compressedOutputStream.toByteArray(), fileName);
+ }
+ } else {
+ // 非图片直接上传
+ log.info("上传其它文件:{}", fileUrl);
+ byte[] fileBytes = StreamUtils.readAllBytes(inputStream);
+ return ApiUtils.uploadFile(fileBytes, fileName);
+ }
+ }
+ } catch (IOException e) {
+ log.error("FileUploadTool uploadFileByUrl exception: ", e);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/linxyun/core/utils/HttpUtils.java b/src/main/java/com/linxyun/core/utils/HttpUtils.java
new file mode 100644
index 0000000..617df7b
--- /dev/null
+++ b/src/main/java/com/linxyun/core/utils/HttpUtils.java
@@ -0,0 +1,83 @@
+package com.linxyun.core.utils;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import java.io.IOException;
+import java.util.Map;
+
+
+@Slf4j
+public class HttpUtils {
+
+ // 通用请求方法,处理所有的 GET 和 POST 请求
+ private static JSONObject executeRequest(HttpRequest HttpRequest) {
+ try (HttpResponse response = HttpRequest.execute()) {
+ if (!response.isOk()) {
+ log.error("HttpRequest failed: {} {}", response.getStatus(), response.body());
+ return null;
+ }
+ if (response.body() == null) {
+ return null;
+ }
+ return JSONObject.parseObject(response.body());
+ }
+ }
+
+ // GET 请求
+ public static JSONObject get(String url) {
+ return executeRequest(HttpUtil.createGet(url));
+ }
+
+ public static HttpResponse getResp(String url) throws IOException {
+ return HttpUtil.createGet(url).execute();
+ }
+
+ public static HttpResponse postResp(String url, JSONObject body) throws IOException {
+ return HttpUtil.createPost(url).body(body.toJSONString()).execute();
+ }
+
+ public static JSONObject get(String url, Map params) {
+ String urlWithParams = buildUrlWithParams(url, params);
+ return get(urlWithParams);
+ }
+
+ public static JSONObject get(String url, Map params, Map headers) throws IOException {
+ String urlWithParams = buildUrlWithParams(url, params);
+ return get(urlWithParams, headers);
+ }
+
+
+ // POST 请求
+ public static JSONObject post(String url, JSONObject body) {
+ HttpRequest request = HttpUtil.createPost(url).body(body.toJSONString());
+ return executeRequest(request);
+ }
+
+ public static JSONObject post(String url, JSONObject body, Map params) {
+ String urlWithParams = buildUrlWithParams(url, params);
+ return post(urlWithParams, body);
+ }
+
+
+ public static JSONObject post(String url, Map params, JSONObject body, Map headers) {
+ String urlWithParams = buildUrlWithParams(url, params);
+ HttpRequest request = HttpUtil.createPost(urlWithParams).body(body.toJSONString()).addHeaders(headers);
+ return executeRequest(request);
+ }
+
+ // 构建带参数的 URL
+ private static String buildUrlWithParams(String url, Map params) {
+ if (params == null || params.isEmpty()) {
+ return url;
+ }
+ StringBuilder sb = new StringBuilder(url);
+ sb.append("?");
+ for (Map.Entry entry : params.entrySet()) {
+ sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
+ }
+ return sb.substring(0, sb.length() - 1);
+ }
+}
diff --git a/src/main/java/com/linxyun/core/utils/StreamUtils.java b/src/main/java/com/linxyun/core/utils/StreamUtils.java
new file mode 100644
index 0000000..db6e0d1
--- /dev/null
+++ b/src/main/java/com/linxyun/core/utils/StreamUtils.java
@@ -0,0 +1,19 @@
+package com.linxyun.core.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class StreamUtils {
+
+ public static byte[] readAllBytes(InputStream inputStream) throws IOException {
+ try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ byteArrayOutputStream.write(buffer, 0, bytesRead);
+ }
+ return byteArrayOutputStream.toByteArray();
+ }
+ }
+}
diff --git a/src/main/java/com/linxyun/core/utils/TimeUtils.java b/src/main/java/com/linxyun/core/utils/TimeUtils.java
new file mode 100644
index 0000000..cd8f0bd
--- /dev/null
+++ b/src/main/java/com/linxyun/core/utils/TimeUtils.java
@@ -0,0 +1,25 @@
+package com.linxyun.core.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+
+@Slf4j
+public class TimeUtils {
+ static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); // 定义日期格式
+ public static Date toDate(String dateStr) {
+ try {
+ return dateFormat.parse(dateStr);
+ } catch (Exception e) {
+ log.error("时间转换异常", e);
+ }
+ return null;
+ }
+
+ public static String getCurrentTime() {
+ Date now = new Date();
+ return dateFormat.format(now);
+ }
+}
diff --git a/src/main/java/com/linxyun/core/utils/URLUtils.java b/src/main/java/com/linxyun/core/utils/URLUtils.java
new file mode 100644
index 0000000..370bddf
--- /dev/null
+++ b/src/main/java/com/linxyun/core/utils/URLUtils.java
@@ -0,0 +1,35 @@
+package com.linxyun.core.utils;
+
+import org.springframework.util.AntPathMatcher;
+
+import java.util.List;
+
+public class URLUtils {
+
+ private static final AntPathMatcher pathMatcher = new AntPathMatcher();
+
+ /**
+ * 检查请求的 URI 是否符合角色对应的路径列表
+ *
+ * @param requestUri 需要匹配的 URI
+ * @param pathList 角色对应的路径列表
+ * @return true 如果匹配;否则 false
+ */
+ public static boolean isUriAuthorized(String requestUri, List pathList) {
+ // 获取请求的 URI
+ if (requestUri == null || requestUri.isEmpty()) {
+ return false;
+ }
+ if (pathList == null) {
+ return false;
+ }
+ // 遍历路径列表,检查是否有匹配项
+ for (String pathPattern : pathList) {
+ if (pathMatcher.match(pathPattern, requestUri)) {
+ return true;
+ }
+ }
+ // 如果没有匹配到,返回 false
+ return false;
+ }
+}
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..91f9e2d
--- /dev/null
+++ b/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.linxyun.core.config.AutoConfiguration
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..e69de29