From 59f4c176ec24e8b4310eb9f44d5944fc71300b46 Mon Sep 17 00:00:00 2001 From: wenxin <1731551615@qq.com> Date: Thu, 5 Dec 2024 20:06:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9D=83=E9=99=90=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=EF=BC=8C=E5=BC=82=E5=B8=B8=E5=8F=91=E9=80=81=E9=82=AE?= =?UTF-8?q?=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 16 +- .../linxyun/homework/HomeworkApplication.java | 2 + .../java/com/linxyun/homework/bot/MyBot.java | 17 +- ...ler.java => AppointGroupEventHandler.java} | 2 +- .../bot/handler/BotStatusHandler.java | 44 +++++ .../homework/common/enums/ErrorCode.java | 4 +- .../exception/GlobalExceptionHandler.java | 29 +++- .../common/properties/LinxyunProperties.java | 71 ++++++++ .../homework/config/SecurityConfig.java | 16 ++ .../linxyun/homework/config/WebConfig.java | 30 ++++ .../domain/dto/ClassGroupTeacher.java | 2 + .../linxyun/homework/domain/dto/UserAuth.java | 17 ++ .../interceptor/SecurityInterceptor.java | 101 +++++++++++ .../impl/HomeworkAnswerServiceImpl.java | 26 ++- .../com/linxyun/homework/utils/FileUtils.java | 101 ++--------- .../com/linxyun/homework/utils/HttpUtils.java | 109 ++++++++++++ .../linxyun/homework/utils/LinxyunUtils.java | 162 ++++++++++++++++++ .../com/linxyun/homework/utils/URLUtils.java | 35 ++++ src/main/resources/application-dev.yml | 30 ++++ src/main/resources/application-prod.yml | 30 ++++ src/main/resources/application.yml | 65 +++---- 21 files changed, 771 insertions(+), 138 deletions(-) rename src/main/java/com/linxyun/homework/bot/handler/{MyEventHandler.java => AppointGroupEventHandler.java} (99%) create mode 100644 src/main/java/com/linxyun/homework/bot/handler/BotStatusHandler.java create mode 100644 src/main/java/com/linxyun/homework/common/properties/LinxyunProperties.java create mode 100644 src/main/java/com/linxyun/homework/config/SecurityConfig.java create mode 100644 src/main/java/com/linxyun/homework/domain/dto/UserAuth.java create mode 100644 src/main/java/com/linxyun/homework/interceptor/SecurityInterceptor.java create mode 100644 src/main/java/com/linxyun/homework/utils/HttpUtils.java create mode 100644 src/main/java/com/linxyun/homework/utils/LinxyunUtils.java create mode 100644 src/main/java/com/linxyun/homework/utils/URLUtils.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-prod.yml diff --git a/pom.xml b/pom.xml index 0721092..51301d1 100644 --- a/pom.xml +++ b/pom.xml @@ -18,12 +18,26 @@ org.springframework.boot spring-boot-starter-web - org.springframework.boot spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-validation + + + + net.jodah + expiringmap + 0.5.11 + + + org.springframework.boot + spring-boot-configuration-processor + true + mysql mysql-connector-java diff --git a/src/main/java/com/linxyun/homework/HomeworkApplication.java b/src/main/java/com/linxyun/homework/HomeworkApplication.java index ce3d99d..850d153 100644 --- a/src/main/java/com/linxyun/homework/HomeworkApplication.java +++ b/src/main/java/com/linxyun/homework/HomeworkApplication.java @@ -3,9 +3,11 @@ package com.linxyun.homework; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication @MapperScan("com.linxyun.homework.mapper") +@EnableConfigurationProperties public class HomeworkApplication { public static void main(String[] args) { SpringApplication.run(HomeworkApplication.class, args); diff --git a/src/main/java/com/linxyun/homework/bot/MyBot.java b/src/main/java/com/linxyun/homework/bot/MyBot.java index a7239ee..5fb6e5c 100644 --- a/src/main/java/com/linxyun/homework/bot/MyBot.java +++ b/src/main/java/com/linxyun/homework/bot/MyBot.java @@ -1,9 +1,10 @@ package com.linxyun.homework.bot; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.linxyun.homework.bot.handler.MyEventHandler; +import com.linxyun.homework.bot.handler.AppointGroupEventHandler; +import com.linxyun.homework.bot.handler.BotStatusHandler; import com.linxyun.homework.domain.dto.ClassGroupTeacher; -import com.linxyun.homework.domain.po.Class;; +import com.linxyun.homework.domain.po.Class; import com.linxyun.homework.mapper.ClassMapper; import com.linxyun.homework.mapper.ClassQqGroupTeacherMapper; import lombok.RequiredArgsConstructor; @@ -24,6 +25,8 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +; + @Slf4j @Component @RequiredArgsConstructor @@ -37,7 +40,10 @@ public class MyBot implements ApplicationRunner { private final ClassQqGroupTeacherMapper classQqGroupTeacherMapper; private final ClassMapper classMapper; - private final MyEventHandler myEventHandler; + + private final AppointGroupEventHandler appointGroupEventHandler; + + private final BotStatusHandler botStatusHandler; /* 登录QQ */ @Value("${mirai.bot.qq}") @@ -100,7 +106,10 @@ public class MyBot implements ApplicationRunner { } return false; } - ).registerListenerHost(myEventHandler); + ).registerListenerHost(appointGroupEventHandler); + eventChannel + .filterIsInstance(BotEvent.class) + .registerListenerHost(botStatusHandler); new Thread(bot::join).start(); } diff --git a/src/main/java/com/linxyun/homework/bot/handler/MyEventHandler.java b/src/main/java/com/linxyun/homework/bot/handler/AppointGroupEventHandler.java similarity index 99% rename from src/main/java/com/linxyun/homework/bot/handler/MyEventHandler.java rename to src/main/java/com/linxyun/homework/bot/handler/AppointGroupEventHandler.java index 195cfef..f45f873 100644 --- a/src/main/java/com/linxyun/homework/bot/handler/MyEventHandler.java +++ b/src/main/java/com/linxyun/homework/bot/handler/AppointGroupEventHandler.java @@ -26,7 +26,7 @@ import java.util.UUID; @Component @Slf4j @RequiredArgsConstructor -public class MyEventHandler extends SimpleListenerHost { +public class AppointGroupEventHandler extends SimpleListenerHost { private final FileUtils fileUtils; private final TeacherQqMsgsMapper teacherQqMsgsMapper; @Value("${mirai.bot.listening-type}") diff --git a/src/main/java/com/linxyun/homework/bot/handler/BotStatusHandler.java b/src/main/java/com/linxyun/homework/bot/handler/BotStatusHandler.java new file mode 100644 index 0000000..949bf64 --- /dev/null +++ b/src/main/java/com/linxyun/homework/bot/handler/BotStatusHandler.java @@ -0,0 +1,44 @@ +package com.linxyun.homework.bot.handler; + +import com.linxyun.homework.utils.EmailUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.mamoe.mirai.event.EventHandler; +import net.mamoe.mirai.event.SimpleListenerHost; +import net.mamoe.mirai.event.events.BotOfflineEvent; +import net.mamoe.mirai.event.events.BotOnlineEvent; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class BotStatusHandler extends SimpleListenerHost { + + private final EmailUtils emailUtils; + + // 机器人上线通知 + @EventHandler + public void onMessage(@NotNull BotOnlineEvent event) { + log.info("机器人上线通知:{}", event); + emailUtils.sendEmail("机器人上线通知", "机器人" + event.getBot().getId() + "上线了"); + } + + // 机器人离线通知 + @EventHandler + public void onMessage(@NotNull BotOfflineEvent event) { + log.error("机器人离线通知:{}", event); + if (event instanceof BotOfflineEvent.Active) { + emailUtils.sendEmail("机器人离线通知", "机器人" + event.getBot().getId() + ":主动离线了"); + } + if (event instanceof BotOfflineEvent.Force) { + emailUtils.sendEmail("机器人离线通知", "机器人" + event.getBot().getId() + ":被挤下线了"); + } + if (event instanceof BotOfflineEvent.Dropped) { + emailUtils.sendEmail("机器人离线通知", "机器人" + event.getBot().getId() + ":被服务器断开或因网络问题而掉线了"); + } + if (event instanceof BotOfflineEvent.RequireReconnect) { + emailUtils.sendEmail("机器人离线通知", "机器人" + event.getBot().getId() + ":服务器主动要求更换另一个服务器"); + } + } +} diff --git a/src/main/java/com/linxyun/homework/common/enums/ErrorCode.java b/src/main/java/com/linxyun/homework/common/enums/ErrorCode.java index 1c9a79f..f3e7a5c 100644 --- a/src/main/java/com/linxyun/homework/common/enums/ErrorCode.java +++ b/src/main/java/com/linxyun/homework/common/enums/ErrorCode.java @@ -68,7 +68,9 @@ public enum ErrorCode { // 验证码验证失败 VERIFICATION_CODE_FAIL("1024", "验证码验证失败"), - // 用户组已经存在 + USER_NO_AUTHORITY("1025", "用户没有权限"), + + // 用户组已经存在 USER_GROUP_ALREADY_EXISTS("1030", "用户组已经存在"), // 用户组不存在 diff --git a/src/main/java/com/linxyun/homework/common/exception/GlobalExceptionHandler.java b/src/main/java/com/linxyun/homework/common/exception/GlobalExceptionHandler.java index df31a1f..5e28a3e 100644 --- a/src/main/java/com/linxyun/homework/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/linxyun/homework/common/exception/GlobalExceptionHandler.java @@ -1,24 +1,45 @@ package com.linxyun.homework.common.exception; +import com.linxyun.homework.common.properties.LinxyunProperties; import com.linxyun.homework.utils.EmailUtils; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; -import javax.annotation.Resource; +import java.io.PrintWriter; +import java.io.StringWriter; @Slf4j @ControllerAdvice +@RequiredArgsConstructor public class GlobalExceptionHandler { - @Resource - private EmailUtils emailUtils; + private final EmailUtils emailUtils; + + private final LinxyunProperties linxyunProperties; + + @ExceptionHandler(value = Exception.class) @ResponseBody public void exceptionHandler(Exception e) { - emailUtils.sendEmail("异常捕获", e.getMessage()); + String subject = "系统异常-" + linxyunProperties.getProject(); + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + String stackTrace = stringWriter.toString(); + String emailContent = String.format( + "异常捕获:\n消息: %s\n原因: %s\n堆栈信息:\n%s", + e.getMessage(), + e.getCause(), + stackTrace + ); + emailUtils.sendEmail(subject, emailContent); + + // 记录日志 log.error("ExceptionHandler exception: {}", e.getMessage()); + log.error("完整堆栈信息: {}", stackTrace); } + } \ No newline at end of file diff --git a/src/main/java/com/linxyun/homework/common/properties/LinxyunProperties.java b/src/main/java/com/linxyun/homework/common/properties/LinxyunProperties.java new file mode 100644 index 0000000..36cdbd6 --- /dev/null +++ b/src/main/java/com/linxyun/homework/common/properties/LinxyunProperties.java @@ -0,0 +1,71 @@ +package com.linxyun.homework.common.properties; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.PostConstruct; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +@Data +@Component +@ConfigurationProperties(prefix = "linxyun") +@Validated +public class LinxyunProperties { + + /** + * 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 = Map.of(); + /** + * 白名单 + */ + private List whiteList = List.of(); + /** + * 上传配置 + */ + 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); + } + } + } + } + } +} + diff --git a/src/main/java/com/linxyun/homework/config/SecurityConfig.java b/src/main/java/com/linxyun/homework/config/SecurityConfig.java new file mode 100644 index 0000000..cf27437 --- /dev/null +++ b/src/main/java/com/linxyun/homework/config/SecurityConfig.java @@ -0,0 +1,16 @@ +package com.linxyun.homework.config; + + +import com.linxyun.homework.domain.dto.UserAuth; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + + +@Configuration +public class SecurityConfig { + + +} + diff --git a/src/main/java/com/linxyun/homework/config/WebConfig.java b/src/main/java/com/linxyun/homework/config/WebConfig.java index ff496c0..79f8a60 100644 --- a/src/main/java/com/linxyun/homework/config/WebConfig.java +++ b/src/main/java/com/linxyun/homework/config/WebConfig.java @@ -1,11 +1,25 @@ package com.linxyun.homework.config; +import com.linxyun.homework.common.properties.LinxyunProperties; +import com.linxyun.homework.interceptor.SecurityInterceptor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.List; +@Slf4j @Configuration +@RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { + + private final LinxyunProperties linxyunProperties; + + private final SecurityInterceptor securityInterceptor; + // 全局跨域 @Override public void addCorsMappings(CorsRegistry registry) { @@ -16,4 +30,20 @@ public class WebConfig implements WebMvcConfigurer { .maxAge(3600) .allowedHeaders("*"); } + + // 添加拦截器 + @Override + public void addInterceptors(@NotNull InterceptorRegistry registry) { + 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/homework/domain/dto/ClassGroupTeacher.java b/src/main/java/com/linxyun/homework/domain/dto/ClassGroupTeacher.java index 7fbfa39..a2b4911 100644 --- a/src/main/java/com/linxyun/homework/domain/dto/ClassGroupTeacher.java +++ b/src/main/java/com/linxyun/homework/domain/dto/ClassGroupTeacher.java @@ -1,8 +1,10 @@ package com.linxyun.homework.domain.dto; import lombok.Data; +import lombok.ToString; @Data +@ToString public class ClassGroupTeacher { private String teacherGroupId; private String teacherNickname; diff --git a/src/main/java/com/linxyun/homework/domain/dto/UserAuth.java b/src/main/java/com/linxyun/homework/domain/dto/UserAuth.java new file mode 100644 index 0000000..50acba8 --- /dev/null +++ b/src/main/java/com/linxyun/homework/domain/dto/UserAuth.java @@ -0,0 +1,17 @@ +package com.linxyun.homework.domain.dto; + +import lombok.Data; + +@Data +public class UserAuth { +// "ContainerID\": \"200\",\"EntCode\": \"57\",\"LoginEntCode\": \"8\",\"LoginIP\": \"\",\"LoginNodeID\": \"16\",\"LoginTime\": \"1733380482\",\"UserID\": \"18115602676\",\"UserName\": \"\",\"UserRoles\": \"1101\" + 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/homework/interceptor/SecurityInterceptor.java b/src/main/java/com/linxyun/homework/interceptor/SecurityInterceptor.java new file mode 100644 index 0000000..82241e0 --- /dev/null +++ b/src/main/java/com/linxyun/homework/interceptor/SecurityInterceptor.java @@ -0,0 +1,101 @@ +package com.linxyun.homework.interceptor; + + +import com.alibaba.fastjson2.JSON; +import com.linxyun.homework.common.entity.Result; +import com.linxyun.homework.common.enums.ErrorCode; +import com.linxyun.homework.common.properties.LinxyunProperties; +import com.linxyun.homework.domain.dto.UserAuth; +import com.linxyun.homework.utils.LinxyunUtils; +import com.linxyun.homework.utils.StringUtils; +import com.linxyun.homework.utils.URLUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +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("Authorization"); + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json; charset=utf-8"); + Result result = null; + 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 = LinxyunUtils.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); + 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/homework/service/impl/HomeworkAnswerServiceImpl.java b/src/main/java/com/linxyun/homework/service/impl/HomeworkAnswerServiceImpl.java index 61c48c3..9974f84 100644 --- a/src/main/java/com/linxyun/homework/service/impl/HomeworkAnswerServiceImpl.java +++ b/src/main/java/com/linxyun/homework/service/impl/HomeworkAnswerServiceImpl.java @@ -11,6 +11,7 @@ import com.linxyun.homework.mapper.TeacherQqMsgsMapper; import com.linxyun.homework.service.HomeworkAnswerService; import com.linxyun.homework.mapper.HomeworkAnswerMapper; import com.linxyun.homework.utils.StringUtils; +import com.linxyun.homework.utils.TimeUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -42,9 +43,26 @@ public class HomeworkAnswerServiceImpl extends ServiceImpl params) throws IOException { + String urlWithParams = buildUrlWithParams(url, params); + return get(urlWithParams); + } + + public static JSONObject get(String url, Map params, Headers headers) throws IOException { + String urlWithParams = buildUrlWithParams(url, params); + return get(urlWithParams, headers); + } + + public static JSONObject get(String url, Headers headers) throws IOException { + Request request = new Request.Builder().url(url).headers(headers).build(); + return executeRequest(request); + } + + // POST 请求 + public static JSONObject post(String url, JSONObject body) throws IOException { + RequestBody requestBody = RequestBody.create(body.toJSONString(), MediaType.parse("application/json")); + Request request = new Request.Builder().url(url).post(requestBody).build(); + return executeRequest(request); + } + + public static JSONObject post(String url, JSONObject body, Map params) throws IOException { + String urlWithParams = buildUrlWithParams(url, params); + RequestBody requestBody = RequestBody.create(body.toJSONString(), MediaType.parse("application/json")); + Request request = new Request.Builder().url(urlWithParams).post(requestBody).build(); + return executeRequest(request); + } + + public static JSONObject post(String url, JSONObject body, Headers headers) throws IOException { + RequestBody requestBody = RequestBody.create(body.toJSONString(), MediaType.parse("application/json")); + Request request = new Request.Builder().url(url).post(requestBody).headers(headers).build(); + return executeRequest(request); + } + + public static JSONObject post(String url, JSONObject body, Headers headers, Map params) throws IOException { + String urlWithParams = buildUrlWithParams(url, params); + RequestBody requestBody = RequestBody.create(body.toJSONString(), MediaType.parse("application/json")); + Request request = new Request.Builder().url(urlWithParams).post(requestBody).headers(headers).build(); + return executeRequest(request); + } + + // 构建带参数的 URL + private static String buildUrlWithParams(String url, Map params) { + HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder(); + if (params != null && !params.isEmpty()) { + for (Map.Entry entry : params.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + return urlBuilder.build().toString(); + } +} diff --git a/src/main/java/com/linxyun/homework/utils/LinxyunUtils.java b/src/main/java/com/linxyun/homework/utils/LinxyunUtils.java new file mode 100644 index 0000000..7fda069 --- /dev/null +++ b/src/main/java/com/linxyun/homework/utils/LinxyunUtils.java @@ -0,0 +1,162 @@ +package com.linxyun.homework.utils; + +import com.alibaba.fastjson2.JSONObject; +import com.linxyun.homework.common.properties.LinxyunProperties; +import com.linxyun.homework.domain.dto.UserAuth; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.jodah.expiringmap.ExpirationPolicy; +import net.jodah.expiringmap.ExpiringMap; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; + + +@Slf4j +@Component +@RequiredArgsConstructor +public class LinxyunUtils { + + 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 final LinxyunProperties linxyunProperties; + private static String url; + private static String projectName; + + @PostConstruct + public void init() { + log.info("LinxyunUtils init success"); + url = linxyunProperties.getUrl(); + projectName = linxyunProperties.getProject(); + } + + /** + * 单点登录 + * @param token + * @return + */ + public static UserAuth userLoginAuth(String token) { + try { + if (StringUtils.isEmpty(token)) { + return null; + } + String url = getApiUrl("userLoginAuth"); + JSONObject body = new JSONObject(Map.of("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"; + // 创建请求体 + RequestBody fileBody = RequestBody.create(fileBytes, MediaType.parse("application/octet-stream")); + MultipartBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", fileName, fileBody) + .build(); + + // 执行请求 + try (Response response = HttpUtils.postResp(uploadUrl, requestBody)) { + if (response.isSuccessful() && response.body() != null) { + String responseBody = response.body().string(); + 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.code()); + return null; + } + } catch (IOException ex) { + log.error("FileUploadTool uploadFile exception: ", ex); + return null; + } + } + + public static void main(String[] args) { + UserAuth userAuth = LinxyunUtils.userLoginAuth("LoginID_20241205143442_000430"); + System.out.println(userAuth); + } +} diff --git a/src/main/java/com/linxyun/homework/utils/URLUtils.java b/src/main/java/com/linxyun/homework/utils/URLUtils.java new file mode 100644 index 0000000..61a9303 --- /dev/null +++ b/src/main/java/com/linxyun/homework/utils/URLUtils.java @@ -0,0 +1,35 @@ +package com.linxyun.homework.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/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..172855e --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,30 @@ +# 应用服务 WEB 访问端口 +server: + port: 8080 + servlet: + context-path: /customService + +spring: + datasource: #定义数据源 + #127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西 + #useSSL也是某些高版本mysql需要问有没有用SSL连接 + url: jdbc:mysql://117.89.254.176:8846/homeworkor?serverTimezone=GMT%2B8&useSSL=FALSE + username: root #数据库用户名,root为管理员 + password: Home.lxy.com #该数据库用户的密码 + # 使用druid数据源 + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + mail: + host: smtp.163.com + port: 465 + protocol: smtps + username: wenxin_web@163.com + password: YCOUPMDBKDTQRAJR + default-encoding: UTF-8 + to-address: 1731551615@qq.com + +mirai: + bot: + qq: 418853946 + # 监听类型 1为监听指定群的聊天消息, 2为监听指定群指定QQ的聊天消息 + listening-type: 1 \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..50509bf --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,30 @@ +# 应用服务 WEB 访问端口 +server: + port: 8080 + servlet: + context-path: /customService + +spring: + datasource: #定义数据源 + #127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西 + #useSSL也是某些高版本mysql需要问有没有用SSL连接 + url: jdbc:mysql://127.0.0.1:3306/homeworkor?serverTimezone=GMT%2B8&useSSL=FALSE + username: root #数据库用户名,root为管理员 + password: Home.lxy.com #该数据库用户的密码 + # 使用druid数据源 + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + mail: + host: smtp.163.com + port: 465 + protocol: smtps + username: wenxin_web@163.com + password: YCOUPMDBKDTQRAJR + default-encoding: UTF-8 + to-address: 1731551615@qq.com + +mirai: + bot: + qq: 20243037 + # 监听类型 1为监听指定群的聊天消息, 2为监听指定群指定QQ的聊天消息 + listening-type: 1 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8b74d3e..306f2d9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,29 +1,28 @@ -# 应用服务 WEB 访问端口 -server: - port: 8080 - servlet: - context-path: /customService +spring: + profiles: + active: dev -spring: #springboot的配置 - datasource: #定义数据源 - #127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西 - #useSSL也是某些高版本mysql需要问有没有用SSL连接 -# url: jdbc:mysql://127.0.0.1:3306/homeworkor?serverTimezone=GMT%2B8&useSSL=FALSE - url: jdbc:mysql://117.89.254.176:8846/homeworkor?serverTimezone=GMT%2B8&useSSL=FALSE - - username: root #数据库用户名,root为管理员 - password: Home.lxy.com #该数据库用户的密码 - # 使用druid数据源 - type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - mail: - host: smtp.163.com - port: 465 - protocol: smtps - username: wenxin_web@163.com - password: YCOUPMDBKDTQRAJR - default-encoding: UTF-8 - to-address: 1731551615@qq.com +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 # mybatis-plus相关配置 mybatis-plus: @@ -46,7 +45,6 @@ mybatis-plus: # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl - logging: file: # 输出的log文件名 @@ -72,20 +70,9 @@ springdoc: - group: 'default' paths-to-match: '/**' packages-to-scan: com.linxyun.homework.controller + # knife4j的增强配置,不需要增强可以不配 knife4j: enable: true setting: - language: zh_cn - -mirai: - bot: -# qq: 20243037 -# qq: 939948263 - qq: 418853946 - # 监听类型 1为监听指定群的聊天消息, 2为监听指定群指定QQ的聊天消息 - listening-type: 1 -upload: - base-url: http://www.linxyun.com - # 图片压缩质量 0~1 - img-quality: 0.5 \ No newline at end of file + language: zh_cn \ No newline at end of file