opt:1.增加后端管理功能。2.优化系统日志。
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -66,6 +66,13 @@
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证 安全框架, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.34.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.nl.apt15e.config.SpringContextHolder;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.nl.apt15e.common.excel.ErrorHandlingListener;
|
||||
import org.nl.apt15e.common.excel.ErrorInfoListener;
|
||||
import org.nl.apt15e.config.file.FileProperties;
|
||||
import org.nl.apt15e.config.language.LangProcess;
|
||||
import org.nl.apt15e.util.FileConstant;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -104,7 +105,7 @@ public class AnomalyInfoController {
|
||||
|
||||
try {
|
||||
// 创建上传目录
|
||||
File uploadDir = new File(properties.getPath().getPath());
|
||||
File uploadDir = new File(FileConstant.ERROR_IMAGE_PATH);
|
||||
if (!uploadDir.exists()) {
|
||||
uploadDir.mkdirs();
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class MapInfoServiceImpl extends ServiceImpl<MapInfoMapper, MapInfo> impl
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("同步地图失败:{}",e.getMessage());
|
||||
throw new BadRequestException(LangProcess.msg("failed"));
|
||||
throw new BadRequestException(LangProcess.msg("failed")+":"+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class RouteInfo {
|
||||
private Double end_y;
|
||||
|
||||
/**
|
||||
* 导航模式 前进模式, 后退模式 ,无头模式
|
||||
* 导航模式 前进模式, 后退模式 ,双向模式
|
||||
*/
|
||||
private String navigation_mode;
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ public class TeachingServiceImpl implements TeachingService {
|
||||
try {
|
||||
response = HttpRequest.post(URLConstant.RCS_IP_PORT+"/map/uploadFile")
|
||||
.setConnectionTimeout(3000)
|
||||
.setReadTimeout(3000)
|
||||
.setReadTimeout(30000)
|
||||
.header("token", "admin123")
|
||||
.header("name", "lx-script")
|
||||
.header("Content-Type", "multipart/form-data;charset=UTF-8")
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.nl.apt15e.apt.teaching.service.impl.TeachingServiceImpl;
|
||||
import org.nl.apt15e.common.BadRequestException;
|
||||
import org.nl.apt15e.config.file.FileProperties;
|
||||
import org.nl.apt15e.config.language.LangProcess;
|
||||
import org.nl.apt15e.util.FileConstant;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -35,10 +36,8 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
@@ -69,6 +68,8 @@ public class ProcessZip {
|
||||
|
||||
private static final String PGM_MAGIC_NUMBER = "P5";
|
||||
|
||||
private List<RouteInfo> routeInfoList = new ArrayList<>();
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void processZipResponse(byte[] zipBytes) {
|
||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(zipBytes);
|
||||
@@ -97,6 +98,8 @@ public class ProcessZip {
|
||||
|
||||
String line;
|
||||
int lineNumber = 0;
|
||||
// 清除路径信息
|
||||
routeInfoList.clear();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lineNumber++;
|
||||
// 5. 处理每一行数据
|
||||
@@ -105,6 +108,12 @@ public class ProcessZip {
|
||||
if (lineNumber == 0) {
|
||||
throw new BadRequestException(LangProcess.msg("error_map_pack_isNull"));
|
||||
}
|
||||
// 批量保存路径信息
|
||||
if (ObjectUtil.isNotEmpty(routeInfoList)){
|
||||
routeInfoList = mergeBidirectionalRoutes(routeInfoList);
|
||||
routeInfoService.saveBatch(routeInfoList);
|
||||
}
|
||||
|
||||
}
|
||||
// 点云图 .pgm 文件转换 .png
|
||||
if (entryName.toLowerCase().endsWith(".pgm")) {
|
||||
@@ -325,7 +334,8 @@ public class ProcessZip {
|
||||
routeInfo.setEnd_y(Double.valueOf(processData[7]));
|
||||
routeInfo.setNavigation_mode(processData[16].replaceAll("^\"|\"$", ""));
|
||||
routeInfo.setRoute_type(processData[17].replaceAll("^\"|\"$", ""));
|
||||
routeInfoService.save(routeInfo);
|
||||
routeInfoList.add(routeInfo);
|
||||
// routeInfoService.save(routeInfo);
|
||||
|
||||
System.out.printf("行号 %d - 有效数据: %s%n", lineNumber, routeInfo);
|
||||
}
|
||||
@@ -457,7 +467,7 @@ public class ProcessZip {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是图片文件
|
||||
* 判断是否是图片文件或者视频文件
|
||||
* @param filename
|
||||
* @return
|
||||
*/
|
||||
@@ -468,7 +478,8 @@ public class ProcessZip {
|
||||
lowerCaseFilename.endsWith(".png") ||
|
||||
lowerCaseFilename.endsWith(".gif") ||
|
||||
lowerCaseFilename.endsWith(".bmp") ||
|
||||
lowerCaseFilename.endsWith(".webp");
|
||||
lowerCaseFilename.endsWith(".webp")||
|
||||
lowerCaseFilename.endsWith(".mp4");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,7 +488,7 @@ public class ProcessZip {
|
||||
* @return
|
||||
*/
|
||||
private boolean isFileExists(String filename) {
|
||||
File targetFile = new File(properties.getPath().getPath() + filename);
|
||||
File targetFile = new File(FileConstant.ERROR_IMAGE_PATH+ "/" + filename);
|
||||
return targetFile.exists();
|
||||
}
|
||||
|
||||
@@ -489,7 +500,7 @@ public class ProcessZip {
|
||||
*/
|
||||
private boolean saveImageFile(InputStream inputStream, String filename) {
|
||||
try {
|
||||
File outputFile = new File(properties.getPath().getPath() + filename);
|
||||
File outputFile = new File(FileConstant.ERROR_IMAGE_PATH + "/" + filename);
|
||||
|
||||
// 确保目录存在
|
||||
File parentDir = outputFile.getParentFile();
|
||||
@@ -504,4 +515,47 @@ public class ProcessZip {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<RouteInfo> mergeBidirectionalRoutes(List<RouteInfo> originalData) {
|
||||
List<RouteInfo> mergedData = new ArrayList<>();
|
||||
Map<String, RouteInfo> routeMap = new HashMap<>();
|
||||
Set<Integer> processedIds = new HashSet<>();
|
||||
|
||||
// 第一次遍历:构建快速查找映射
|
||||
for (RouteInfo route : originalData) {
|
||||
String key = route.getStart_id() + "-" + route.getEnd_id();
|
||||
routeMap.put(key, route);
|
||||
}
|
||||
|
||||
// 第二次遍历:查找并合并双向路径
|
||||
for (RouteInfo route : originalData) {
|
||||
if (processedIds.contains(route.getRoute_id())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// String forwardKey = route.getStart_id() + "-" + route.getEnd_id();
|
||||
String reverseKey = route.getEnd_id() + "-" + route.getStart_id();
|
||||
|
||||
// 检查是否存在精确反向路径
|
||||
if (routeMap.containsKey(reverseKey)) {
|
||||
RouteInfo reverseRoute = routeMap.get(reverseKey);
|
||||
|
||||
if (!processedIds.contains(reverseRoute.getRoute_id())) {
|
||||
// 选择routeId较小的作为主记录,导航模式改为2(双向)
|
||||
RouteInfo bidirectionalRoute = route.getRoute_id() < reverseRoute.getRoute_id() ? route : reverseRoute;
|
||||
bidirectionalRoute.setNavigation_mode("2");
|
||||
mergedData.add(bidirectionalRoute);
|
||||
|
||||
processedIds.add(route.getRoute_id());
|
||||
processedIds.add(reverseRoute.getRoute_id());
|
||||
}
|
||||
} else {
|
||||
// 没有反向路径,直接添加
|
||||
mergedData.add(route);
|
||||
processedIds.add(route.getRoute_id());
|
||||
}
|
||||
}
|
||||
|
||||
return mergedData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,13 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.nl.apt15e.common.logging.annotation.Log;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
@@ -44,23 +37,63 @@ public class LogAspect {
|
||||
|
||||
/**
|
||||
* 环绕通知
|
||||
* @param joinPoint
|
||||
* @param point
|
||||
* @return
|
||||
* @throws Throwable
|
||||
*/
|
||||
@Around("logPointCut()")
|
||||
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
|
||||
// String params = JSONObject.toJSONString(joinPoint.getArgs());
|
||||
public Object logAround(ProceedingJoinPoint point) throws Throwable {
|
||||
//// MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
//
|
||||
//
|
||||
Object result;
|
||||
// log.info("【日志注解】开始执行 -- {}:{} {}", className, methodName, params);
|
||||
result = joinPoint.proceed();
|
||||
// log.info("返回参数:{}" ,JSONObject.toJSONString(result));
|
||||
//// String params = JSONObject.toJSONString(joinPoint.getArgs());
|
||||
////
|
||||
////
|
||||
// Object result;
|
||||
//// log.info("【日志注解】开始执行 -- {}:{} {}", className, methodName, params);
|
||||
// result = joinPoint.proceed();
|
||||
//// log.info("返回参数:{}" ,JSONObject.toJSONString(result));
|
||||
// return result;
|
||||
Object result = null;
|
||||
long beginTime = System.currentTimeMillis();
|
||||
try {
|
||||
// 执行方法
|
||||
result = point.proceed();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 执行时长(毫秒)
|
||||
long time = System.currentTimeMillis() - beginTime;
|
||||
// 保存日志
|
||||
saveLog(point, time);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
Log logAnnotation = method.getAnnotation(Log.class);
|
||||
// 注解上的描述
|
||||
String description = "";
|
||||
if (logAnnotation != null) {
|
||||
description = logAnnotation.value();
|
||||
}
|
||||
// 请求的方法名
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = signature.getName();
|
||||
// 请求的方法参数值
|
||||
Object[] args = joinPoint.getArgs();
|
||||
// 请求的方法参数名称
|
||||
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
|
||||
String[] paramNames = u.getParameterNames(method);
|
||||
String params = "";
|
||||
if (args != null && paramNames != null) {
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
params += " " + paramNames[i] + ": " + args[i];
|
||||
}
|
||||
}
|
||||
log.info("【日志注解】开始执行 -- 描述:{} -- 类方法:{}.{}() -- 参数:{} -- 执行时长:{}毫秒", description, className, methodName, params, time);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.nl.apt15e.config.satoken;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/22
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfig implements WebMvcConfigurer {
|
||||
|
||||
@Resource
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new SaInterceptor(handle -> {
|
||||
SaRouter
|
||||
.match("/**")
|
||||
.check(r -> StpUtil.checkLogin());
|
||||
}))
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(securityProperties.getExcludes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.nl.apt15e.config.satoken;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/22
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "security")
|
||||
public class SecurityProperties {
|
||||
|
||||
/**
|
||||
* 排除路径
|
||||
*/
|
||||
private String[] excludes;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.nl.apt15e.manage.logging.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import org.nl.apt15e.common.BadRequestException;
|
||||
import org.nl.apt15e.common.logging.annotation.Log;
|
||||
import org.nl.apt15e.manage.logging.dto.LogFileDTO;
|
||||
import org.nl.apt15e.manage.logging.service.LogService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/11/3
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/logs")
|
||||
public class LogController {
|
||||
|
||||
@Autowired
|
||||
private LogService logService;
|
||||
|
||||
/**
|
||||
* 获取日志文件列表
|
||||
*/
|
||||
@Log("获取日志文件列表")
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<Object> getLogFiles() {
|
||||
try {
|
||||
return new ResponseEntity<>(logService.getLogFiles(), HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
throw new BadRequestException("获取日志文件失败:"+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载日志文件
|
||||
*/
|
||||
@Log("下载日志文件")
|
||||
@GetMapping("/download/{fileName}")
|
||||
public ResponseEntity<Resource> downloadLogFile(@PathVariable String fileName) {
|
||||
try {
|
||||
File file = logService.getLogFile(fileName);
|
||||
|
||||
Path filePath = file.toPath();
|
||||
Resource resource = new InputStreamResource(Files.newInputStream(filePath));
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + file.getName() + "\"")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(file.length())
|
||||
.body(resource);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.nl.apt15e.manage.logging.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/11/3
|
||||
*/
|
||||
@Data
|
||||
public class LogFileDTO {
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private Long fileSize;
|
||||
private LocalDateTime lastModified;
|
||||
private String readableSize;
|
||||
|
||||
public LogFileDTO(String fileName, String filePath, Long fileSize, LocalDateTime lastModified) {
|
||||
this.fileName = fileName;
|
||||
this.filePath = filePath;
|
||||
this.fileSize = fileSize;
|
||||
this.lastModified = lastModified;
|
||||
this.readableSize = formatFileSize(fileSize);
|
||||
}
|
||||
|
||||
private String formatFileSize(long size) {
|
||||
if (size <= 0) return "0 B";
|
||||
final String[] units = new String[]{"B", "KB", "MB", "GB"};
|
||||
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
|
||||
return String.format("%.1f %s", size / Math.pow(1024, digitGroups), units[digitGroups]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.nl.apt15e.manage.logging.service;
|
||||
|
||||
import org.nl.apt15e.manage.logging.dto.LogFileDTO;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/11/3
|
||||
*/
|
||||
@Service
|
||||
public class LogService {
|
||||
|
||||
@Value("${customize.log-dir}")
|
||||
private String logDir;
|
||||
|
||||
/**
|
||||
* 获取日志文件列表
|
||||
*/
|
||||
public List<LogFileDTO> getLogFiles() {
|
||||
List<LogFileDTO> logFiles = new ArrayList<>();
|
||||
File directory = new File(logDir);
|
||||
|
||||
if (!directory.exists() || !directory.isDirectory()) {
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
File[] files = directory.listFiles((dir, name) ->
|
||||
name.endsWith(".log") || name.endsWith(".txt"));
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
LogFileDTO logFile = new LogFileDTO(
|
||||
file.getName(),
|
||||
file.getAbsolutePath(),
|
||||
file.length(),
|
||||
LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(file.lastModified()),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
);
|
||||
logFiles.add(logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按修改时间倒序排列
|
||||
logFiles.sort((a, b) -> b.getLastModified().compareTo(a.getLastModified()));
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志文件
|
||||
*/
|
||||
public File getLogFile(String fileName) {
|
||||
// 防止路径遍历攻击
|
||||
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
|
||||
throw new IllegalArgumentException("Invalid file name");
|
||||
}
|
||||
|
||||
File file = new File(logDir, fileName);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
throw new RuntimeException("File not found: " + fileName);
|
||||
}
|
||||
|
||||
// 确保文件在日志目录内
|
||||
if (!file.getAbsolutePath().startsWith(new File(logDir).getAbsolutePath())) {
|
||||
throw new SecurityException("Access denied");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.nl.apt15e.manage.secutiry.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.nl.apt15e.common.BadRequestException;
|
||||
import org.nl.apt15e.config.language.LangProcess;
|
||||
import org.nl.apt15e.manage.secutiry.dto.AuthUserDto;
|
||||
import org.nl.apt15e.manage.secutiry.service.AuthorizationService;
|
||||
import org.nl.apt15e.util.RsaUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/22
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthorizationController {
|
||||
|
||||
@Resource
|
||||
private AuthorizationService authorizationService;
|
||||
|
||||
@PostMapping(value = "/login")
|
||||
public ResponseEntity<Object> login(@RequestBody AuthUserDto user) {
|
||||
|
||||
if (ObjectUtil.isEmpty(user)) {
|
||||
throw new BadRequestException(LangProcess.msg("param_is_null"));
|
||||
}
|
||||
return new ResponseEntity<>(authorizationService.login(user), HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.nl.apt15e.manage.secutiry.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
@Data
|
||||
public class AuthUserDto {
|
||||
|
||||
private String userName;
|
||||
|
||||
private String password;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.nl.apt15e.manage.secutiry.service;
|
||||
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.SneakyThrows;
|
||||
import org.nl.apt15e.common.BadRequestException;
|
||||
import org.nl.apt15e.config.language.LangProcess;
|
||||
import org.nl.apt15e.manage.secutiry.dto.AuthUserDto;
|
||||
import org.nl.apt15e.manage.user.dao.User;
|
||||
import org.nl.apt15e.manage.user.service.UserService;
|
||||
import org.nl.apt15e.util.RsaUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
@Service
|
||||
public class AuthorizationService{
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
@SneakyThrows
|
||||
public Map<String, Object> login(AuthUserDto user){
|
||||
// 密码解密 - 前端的加密规则: encrypt(根据实际更改)
|
||||
String password = RsaUtils.decryptByPrivateKey(RsaUtils.privateKey, user.getPassword());
|
||||
user.setPassword(password);
|
||||
User userData = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUserName, user.getUserName()));
|
||||
if (ObjectUtil.isEmpty(userData) || !userData.getPassword().equals(SaSecureUtil.md5BySalt(user.getPassword(),"salt"))) {
|
||||
throw new BadRequestException(LangProcess.msg("password_error"));
|
||||
}
|
||||
StpUtil.login(userData.getUser_id(),new SaLoginModel()
|
||||
.setDevice("PC"));
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("user",userData);
|
||||
data.put("token",StpUtil.getTokenValue());
|
||||
return SaResult.data(data);
|
||||
}
|
||||
}
|
||||
22
src/main/java/org/nl/apt15e/manage/user/dao/User.java
Normal file
22
src/main/java/org/nl/apt15e/manage/user/dao/User.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.nl.apt15e.manage.user.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
@Data
|
||||
@TableName("user")
|
||||
public class User {
|
||||
|
||||
@TableId(value = "user_id")
|
||||
private String user_id;
|
||||
|
||||
private String userName;
|
||||
|
||||
private String password;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.nl.apt15e.manage.user.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.nl.apt15e.manage.secutiry.dto.AuthUserDto;
|
||||
import org.nl.apt15e.manage.user.dao.User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
public interface UserService extends IService<User> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.nl.apt15e.manage.user.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.nl.apt15e.manage.secutiry.dto.AuthUserDto;
|
||||
import org.nl.apt15e.manage.user.dao.User;
|
||||
import org.nl.apt15e.manage.user.service.UserService;
|
||||
import org.nl.apt15e.manage.user.service.mapper.UserMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
@Service
|
||||
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.nl.apt15e.manage.user.service.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.nl.apt15e.manage.user.dao.User;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
}
|
||||
27
src/main/java/org/nl/apt15e/util/FileConstant.java
Normal file
27
src/main/java/org/nl/apt15e/util/FileConstant.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.nl.apt15e.util;
|
||||
|
||||
import org.nl.apt15e.config.file.FileProperties;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/11/3
|
||||
**/
|
||||
@Component
|
||||
public class FileConstant {
|
||||
|
||||
|
||||
public static String ERROR_IMAGE_PATH = "";
|
||||
|
||||
@Resource
|
||||
private FileProperties properties;
|
||||
|
||||
@Value("${customize.errorImage-dir}")
|
||||
public void setErrorImagePath(String errorImagePath) {
|
||||
FileConstant.ERROR_IMAGE_PATH = properties.getPath().getPath()+errorImagePath;
|
||||
}
|
||||
|
||||
}
|
||||
20
src/main/java/org/nl/apt15e/util/MD5Util.java
Normal file
20
src/main/java/org/nl/apt15e/util/MD5Util.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.nl.apt15e.util;
|
||||
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
|
||||
/**
|
||||
* @author dsh
|
||||
* 2025/10/23
|
||||
*/
|
||||
public class MD5Util {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
String str = "noblelift";
|
||||
|
||||
String salt = "salt";
|
||||
|
||||
String value = SaSecureUtil.md5BySalt(str,salt);
|
||||
|
||||
System.out.println("加密后的值:"+value);
|
||||
}
|
||||
}
|
||||
90
src/main/java/org/nl/apt15e/util/RsaUtils.java
Normal file
90
src/main/java/org/nl/apt15e/util/RsaUtils.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package org.nl.apt15e.util;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.security.*;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* @author liejiu
|
||||
*/
|
||||
@Component
|
||||
public class RsaUtils {
|
||||
|
||||
public static String privateKey;
|
||||
|
||||
@Value("${rsa.private_key}")
|
||||
public void setPrivateKey(String privateKey) {
|
||||
RsaUtils.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("\n");
|
||||
RsaKeyPair keyPair = generateKeyPair();
|
||||
System.out.println("公钥:" + keyPair.getPublicKey());
|
||||
System.out.println("私钥:" + keyPair.getPrivateKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建RSA密钥对
|
||||
*
|
||||
* @return /
|
||||
* @throws NoSuchAlgorithmException /
|
||||
*/
|
||||
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(1024);
|
||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
|
||||
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
|
||||
return new RsaKeyPair(publicKeyString, privateKeyString);
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA密钥对对象
|
||||
*/
|
||||
public static class RsaKeyPair {
|
||||
|
||||
private final String publicKey;
|
||||
private final String privateKey;
|
||||
|
||||
public RsaKeyPair(String publicKey, String privateKey) {
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 私钥解密
|
||||
*
|
||||
* @param privateKeyText 私钥
|
||||
* @param text 待解密的文本
|
||||
* @return /
|
||||
* @throws Exception /
|
||||
*/
|
||||
public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
|
||||
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
|
||||
return new String(result);
|
||||
}
|
||||
}
|
||||
@@ -67,3 +67,20 @@ file:
|
||||
# 文件大小 /M
|
||||
maxSize: 100
|
||||
avatarMaxSize: 5
|
||||
|
||||
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: Authorization
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
activity-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: false
|
||||
# token风格
|
||||
token-style: random-128
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
@@ -67,3 +67,20 @@ file:
|
||||
# 文件大小 /M
|
||||
maxSize: 100
|
||||
avatarMaxSize: 5
|
||||
|
||||
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: Authorization
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
activity-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: false
|
||||
# token风格
|
||||
token-style: random-128
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
@@ -25,3 +25,34 @@ logging:
|
||||
file:
|
||||
path: logs
|
||||
config: classpath:logback-spring.xml
|
||||
|
||||
customize:
|
||||
log-dir: ${logging.file.path}/root
|
||||
errorImage-dir: ErrorImage
|
||||
|
||||
#密码加密传输,前端公钥加密,后端私钥解密
|
||||
rsa:
|
||||
private_key: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMjhH5OmC7osPfdelXwkX1uTW1vuj8miZIU546Y6dy7TI2RF04rcW2eneBqrvF/8Ni1b+A+bqAJfMi01TDBmdyp+7vyZzUPRXv02HpI/ZM9dkQhv2m4VnTNnWOM0mY/7fJtDfLXhfDNmBMz7h57oDUFV0ESQtp5i4K7hlIVeezijAgMBAAECgYBVuPQfruksPnsHGB7UhjUHQD/pYEmN8zXQQJ7sLeD0Y3ej78RRaq268xVm1Eij8V4xRyD5kCRHNtaTwj3MHb3V68QC3amBR392yay4+S5ZOEPOjZ64hMSZWPezHbemLMjtUn1NR1k1aGfaiGPEk3npiXJHf9ZXU2GGglLJ50eP+QJBAPD3TTRtOVuTIPQS+m0PEeW6ALZDp3EONoqOMePuGLqj38QjwV+jGdxzA+Hhsf8oQo+QG2S3YPph09TFZc3dvj0CQQDVaY9TGeDyxf2DDL/oBFecIkixkV/AWRJb9CrDCZ8H9K1xWuEmhIW1pFJ+gmjbZccZQe/n1M+tRKq9DPaXsiBfAkANgE27GkOUdfHquwV9BtMh5AIWNEQ1eW5k5QK2mqiYDIaFHtu+2Ayi5W7aQSMQANl54cEnK38riD+uNEE3/6yhAkAN3ZfkTFAjNd3sv81QI8gVatzSPKG9+4uH0etdVKiyeaEzNjZerEmLrat2cL6jUo+HApO1ukvr9AQr2EXFQVt9AkEAoQcTx50AK6osp15kAoOQH5NV7TF2qoDbkNfp4xJReLlNpn9oPh2CNQOuVfna5gDQmkaEaJwwVuBvWN5RPrp2HA==
|
||||
|
||||
# sa-token白名单配置
|
||||
security:
|
||||
# 排除路径
|
||||
excludes:
|
||||
# 认证
|
||||
- /auth/login
|
||||
# apt屏幕操作
|
||||
# - /anomalyInfo/**
|
||||
- /api/rcs/**
|
||||
- /mapInfo/**
|
||||
- /api/operate/**
|
||||
- /teaching/**
|
||||
- /vehicle/**
|
||||
- /webSocket/**
|
||||
- /file/**
|
||||
- /routeInfo/**
|
||||
- /station/**
|
||||
# 静态资源
|
||||
- /*.html
|
||||
- /**/*.html
|
||||
- /**/*.css
|
||||
- /**/*.js
|
||||
@@ -4,3 +4,4 @@ successful = 操作成功!
|
||||
failed = 操作失败!
|
||||
latest = 已经是最新的!
|
||||
param_is_null = 参数为空!
|
||||
password_error = 账号或密码错误
|
||||
@@ -4,3 +4,4 @@ successful = successful!
|
||||
failed = Failed!
|
||||
latest = Already up to date!
|
||||
param_is_null = The parameter is empty!
|
||||
password_error = Wrong account or password
|
||||
@@ -4,3 +4,4 @@ successful = 操作成功!
|
||||
failed = 操作失败!
|
||||
latest = 已经是最新的!
|
||||
param_is_null = 参数为空!
|
||||
password_error = 账号或密码错误
|
||||
@@ -24,11 +24,15 @@
|
||||
<!-- 日志首次输出的文件地址 -->
|
||||
<!-- <file>${LOG_HOME}/info.log</file>-->
|
||||
<!-- 滚动输出策略:基于时间创建日志文件 ,这样第二天输出的日志,就会按照 fileNamePattern 新建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${LOG_HOME}/root/info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>30</maxHistory>
|
||||
<fileNamePattern>${LOG_HOME}/root/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<!--日志文件保留天数-->
|
||||
<maxHistory>15</maxHistory>
|
||||
<!--单个日志最大容量 至少10MB才能看得出来-->
|
||||
<maxFileSize>20MB</maxFileSize>
|
||||
<!--所有日志最多占多大容量-->
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<!-- 日志内容输出格式设置为定义好的 log.pattern-->
|
||||
@@ -53,17 +57,17 @@
|
||||
<!-- <appender-ref ref="console"/>-->
|
||||
<!-- </appender>-->
|
||||
|
||||
<!-- <appender name="async_file_info" class="ch.qos.logback.classic.AsyncAppender">-->
|
||||
<!-- <discardingThreshold>0</discardingThreshold>-->
|
||||
<!-- <queueSize>256</queueSize>-->
|
||||
<!-- <appender-ref ref="file_info"/>-->
|
||||
<!-- </appender>-->
|
||||
<appender name="async_file_info" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<queueSize>256</queueSize>
|
||||
<appender-ref ref="file_info"/>
|
||||
</appender>
|
||||
|
||||
<!--系统操作日志 root 根路径的日志级别 info -->
|
||||
<root level="info">
|
||||
<!-- 将定义好的几个日志输出 追加到 root 上 -->
|
||||
<!-- <appender-ref ref="console"/>-->
|
||||
<appender-ref ref="console" />
|
||||
<!-- <appender-ref ref="async_file_info" />-->
|
||||
<appender-ref ref="async_file_info" />
|
||||
</root>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user