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