commit e23f3f777d6496a9bc41997179fb85083b13745e Author: wenxin <1731551615@qq.com> Date: Fri Dec 6 17:49:39 2024 +0800 林栖云SpringBoot依赖 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