新增权限拦截,异常发送邮箱

This commit is contained in:
wenxin 2024-12-05 20:06:14 +08:00
parent 12e8ddf9f4
commit 59f4c176ec
21 changed files with 771 additions and 138 deletions

16
pom.xml
View File

@ -18,12 +18,26 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>expiringmap</artifactId>
<version>0.5.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>

View File

@ -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);

View File

@ -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();
}

View File

@ -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}")

View File

@ -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() + ":服务器主动要求更换另一个服务器");
}
}
}

View File

@ -68,6 +68,8 @@ public enum ErrorCode {
// 验证码验证失败
VERIFICATION_CODE_FAIL("1024", "验证码验证失败"),
USER_NO_AUTHORITY("1025", "用户没有权限"),
// 用户组已经存在
USER_GROUP_ALREADY_EXISTS("1030", "用户组已经存在"),

View File

@ -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);
}
}

View File

@ -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<String, List<String>> role = Map.of();
/**
* 白名单
*/
private List<String> 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<String> 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);
}
}
}
}
}
}

View File

@ -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 {
}

View File

@ -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<String> 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("/**");
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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<String, List<String>> 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<String> 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 {
}
}

View File

@ -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<HomeworkAnswerMapper,
log.error("msgId: " + msgId + " not found");
return Result.error(ErrorCode.PARAMETER_ERROR);
}
if (!(teacherQqMsgs.getMsgContent().contains(answerMsg.getFile()) && teacherQqMsgs.getRemarks().contains(answerMsg.getContent()))) {
if (!StringUtils.isEmpty(answerMsg.getFile()) ) {
if (StringUtils.isEmpty(teacherQqMsgs.getMsgContent())) {
return Result.error(ErrorCode.PARAMETER_ERROR);
} else {
if (!teacherQqMsgs.getMsgContent().contains(answerMsg.getFile())) {
return Result.error(ErrorCode.PARAMETER_ERROR);
}
}
}
if (!StringUtils.isEmpty(answerMsg.getContent())) {
if (StringUtils.isEmpty(teacherQqMsgs.getRemarks())) {
return Result.error(ErrorCode.PARAMETER_ERROR);
} else {
if (!teacherQqMsgs.getRemarks().contains(answerMsg.getContent())) {
return Result.error(ErrorCode.PARAMETER_ERROR);
}
}
}
HomeworkAnswer homeworkAnswer = new HomeworkAnswer();
homeworkAnswer.setMemberId(memberId);
if (StringUtils.isEmpty(title)) {
@ -54,13 +72,13 @@ public class HomeworkAnswerServiceImpl extends ServiceImpl<HomeworkAnswerMapper,
}
homeworkAnswer.setCreateType(1); // 班级可看
homeworkAnswer.setStudentId(teacherQqMsgs.getClassId());
homeworkAnswer.setRemark(answerMsg.getContent().replace("|||", "")); // 文字;
homeworkAnswer.setCreateTime(TimeUtils.getCurrentTime());
if(answerMsg.getFile() != null) {
homeworkAnswer.setContentType(1);
homeworkAnswer.setRemark(answerMsg.getContent());
homeworkAnswer.setContentFileId(answerMsg.getFile()); // 文件资源
} else {
homeworkAnswer.setContentType(0);
homeworkAnswer.setContent(answerMsg.getContent().replace("|||", "")); // 文字
}
int row = homeworkAnswerMapper.insert(homeworkAnswer);
if (row < 1) {

View File

@ -1,25 +1,23 @@
package com.linxyun.homework.utils;
import com.alibaba.fastjson2.JSONObject;
import com.linxyun.homework.common.enums.FileType;
import com.linxyun.homework.common.properties.LinxyunProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.stereotype.Component;
import java.io.*;
@Slf4j
@Component
@RequiredArgsConstructor
public class FileUtils {
private static final OkHttpClient client = new OkHttpClient();
@Value("${upload.base-url}")
private String baseUrl;
private final LinxyunProperties linxyunProperties;
@Value(value = "${upload.img-quality}")
private Float imgQuality = 1.0f;
/**
* 下载文件到指定目录
@ -29,17 +27,11 @@ public class FileUtils {
* @throws IOException 如果发生 IO 异常
*/
public void downloadFile(String fileUrl, String targetDir, String fileName) throws IOException {
// 构建请求
Request request = new Request.Builder()
.url(fileUrl)
.build();
// 执行请求
try (Response response = client.newCall(request).execute()) {
try (Response response = HttpUtils.getResp(fileUrl)) {
if (!response.isSuccessful()) {
throw new IOException("文件下载失败, HTTP 响应码: " + response.code());
}
// 确保目标目录存在
File directory = new File(targetDir);
if (!directory.exists() && !directory.mkdirs()) {
@ -56,8 +48,7 @@ public class FileUtils {
outputStream.write(buffer, 0, bytesRead);
}
}
System.out.println("文件下载完成: " + targetDir + File.separator + fileName);
log.info("文件下载完成: {}", targetDir + File.separator + fileName);
}
}
@ -71,15 +62,10 @@ public class FileUtils {
public String uploadFileByUrl(String fileUrl, String fileName) {
FileType fileType = FileType.fromExtension(fileName);
// 创建 GET 请求
Request request = new Request.Builder()
.url(fileUrl)
.build();
// 执行请求
try (Response response = client.newCall(request).execute()) {
try (Response response = HttpUtils.getResp(fileUrl)) {
if (!response.isSuccessful()) {
log.error("Failed to download file: " + response.message());
log.error("Failed to download file: {}", response.message());
return null;
}
// 获取文件内容作为字节数组
@ -88,23 +74,23 @@ public class FileUtils {
// 如果是图片进行压缩
if (fileType == FileType.IMAGE) {
try (ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream()) {
log.info("压缩图片:" + fileUrl + " 图片质量:" + imgQuality);
log.info("压缩图片:{} 图片质量: {}", fileUrl, linxyunProperties.getUpload().getImgQuality());
// 压缩图片
Thumbnails.of(inputStream)
.outputQuality(imgQuality) // 设置输出质量 (0~1)
.outputQuality(linxyunProperties.getUpload().getImgQuality()) // 设置输出质量 (0~1)
.scale(1.0) // 保持原尺寸如需缩放可调整 scale 值或使用 size 方法
.toOutputStream(compressedOutputStream);
// 日志输出原始大小和压缩后大小
log.info("图片原始大小: " + responseBody.contentLength() + " 字节");
log.info("图片压缩后大小: " + compressedOutputStream.size() + " 字节");
log.info("图片原始大小: {} 字节", responseBody.contentLength());
log.info("图片压缩后大小: {} 字节", compressedOutputStream.size());
// 上传压缩后的图片
return uploadFile(compressedOutputStream.toByteArray(), fileName);
return LinxyunUtils.uploadFile(compressedOutputStream.toByteArray(), fileName);
}
} else {
// 非图片直接上传
log.info("上传其它文件:" + fileUrl);
return uploadFile(inputStream.readAllBytes(), fileName);
log.info("上传其它文件:{}", fileUrl);
return LinxyunUtils.uploadFile(inputStream.readAllBytes(), fileName);
}
}
} catch (IOException e) {
@ -112,57 +98,4 @@ public class FileUtils {
return null;
}
}
/**
* 使用 OkHttp 上传文件
*
* @param fileBytes 文件字节数组
* @param fileName 文件名
* @return 上传成功时返回服务器响应的 "data" 字段否则返回 null
*/
public String uploadFile(byte[] fileBytes, String fileName) {
String uploadUrl = baseUrl + "/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();
// 创建请求
Request request = new Request.Builder()
.url(uploadUrl)
.post(requestBody)
.build();
// 执行请求
try (Response response = client.newCall(request).execute()) {
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;
}
}
}

View File

@ -0,0 +1,109 @@
package com.linxyun.homework.utils;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
public class HttpUtils {
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
// 通用请求方法处理所有的 GET POST 请求
private static JSONObject executeRequest(Request request) throws IOException {
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
log.error("Request failed: {} {}", response.code(), response.message());
return null;
}
if (response.body() == null) {
return null;
}
return com.alibaba.fastjson2.JSONObject.parseObject(response.body().string());
}
}
// GET 请求
public static JSONObject get(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
return executeRequest(request);
}
public static Response getResp(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
return client.newCall(request).execute();
}
public static Response postResp(String url, RequestBody requestBody) throws IOException {
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
return client.newCall(request).execute();
}
public static JSONObject get(String url, Map<String, String> params) throws IOException {
String urlWithParams = buildUrlWithParams(url, params);
return get(urlWithParams);
}
public static JSONObject get(String url, Map<String, String> 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<String, String> 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<String, String> 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<String, String> params) {
HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
}
}
return urlBuilder.build().toString();
}
}

View File

@ -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<String, UserAuth> 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);
}
}

View File

@ -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<String> 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;
}
}

View File

@ -0,0 +1,30 @@
# 应用服务 WEB 访问端口
server:
port: 8080
servlet:
context-path: /customService
spring:
datasource: #定义数据源
#127.0.0.1为本机测试的ip3306是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

View File

@ -0,0 +1,30 @@
# 应用服务 WEB 访问端口
server:
port: 8080
servlet:
context-path: /customService
spring:
datasource: #定义数据源
#127.0.0.1为本机测试的ip3306是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

View File

@ -1,29 +1,28 @@
# 应用服务 WEB 访问端口
server:
port: 8080
servlet:
context-path: /customService
spring:
profiles:
active: dev
spring: #springboot的配置
datasource: #定义数据源
#127.0.0.1为本机测试的ip3306是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