add:第一版测试版本。

This commit is contained in:
2025-12-15 10:42:02 +08:00
parent 98e0bbcaa6
commit cd483c81d1
84 changed files with 3714 additions and 26 deletions

View File

@@ -0,0 +1,26 @@
package org.nl.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author dsh
* 2025/12/13
*/
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedHeaders(CorsConfiguration.ALL)
.allowedMethods(CorsConfiguration.ALL)
.allowCredentials(true)
.maxAge(3600);
}
}

View File

@@ -0,0 +1,44 @@
package org.nl.enums;
import lombok.Getter;
/**
* @author dsh
* 2025/12/11
*/
@Getter
public enum ScheduleTaskReportStatusEnum {
/**
* 未上报
*/
NOT_REPORTED("0", "未上报", "Not reported"),
/**
* 已上报
*/
REPORTED("1", "已上报", "reported"),
/**
* 处理完成
*/
FINISH_REPORTED("2", "处理完成", "外部API");
private String code;
private String name;
private String desc;
ScheduleTaskReportStatusEnum(String code, String name, String desc) {
this.code = code;
this.name = name;
this.desc = desc;
}
public static ScheduleTaskReportStatusEnum getByCode(String code) {
for (ScheduleTaskReportStatusEnum e : ScheduleTaskReportStatusEnum.values()) {
if (e.code.equals(code)) {
return e;
}
}
return null;
}
}

View File

@@ -0,0 +1,31 @@
package org.nl.enums;
import lombok.Getter;
/**
* @author dsh
* 2025/12/3
*/
@Getter
public enum YesOrNoEnum {
/**
* 否
*/
NO("0", "", ""),
/**
* 执行中
*/
YES("1", "", "");
private String code;
private String name;
private String desc;
YesOrNoEnum(String code, String name, String desc) {
this.code = code;
this.name = name;
this.desc = desc;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.nl.exception;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
@Data
class ApiError {
private Integer code = 400;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime Date;
private String message;
private ApiError() {
Date = LocalDateTime.now();
}
public static ApiError error(String message){
ApiError apiError = new ApiError();
apiError.setMessage(message);
return apiError;
}
public static ApiError error(Integer status, String message){
ApiError apiError = new ApiError();
apiError.setCode(status);
apiError.setMessage(message);
return apiError;
}
}

View File

@@ -0,0 +1,25 @@
package org.nl.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* @author dsh
* 2025/7/3
*/
@Getter
public class BadRequestException extends RuntimeException{
private Integer status = BAD_REQUEST.value();
public BadRequestException(String msg){
super(msg);
}
public BadRequestException(HttpStatus status, String msg){
super(msg);
this.status = status.value();
}
}

View File

@@ -0,0 +1,77 @@
package org.nl.exception;
import lombok.extern.slf4j.Slf4j;
import org.nl.util.ThrowableUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Objects;
/**
* @author liejiu
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有不可知的异常
*/
@ExceptionHandler(Throwable.class)
public ResponseEntity<ApiError> handleException(Throwable e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
return buildResponseEntity(ApiError.error(e.getMessage()));
}
/**
* token 无效的异常拦截
* @param e
* @return
*/
// @ExceptionHandler(value = NotLoginException.class)
// public ResponseEntity<ApiError> notLoginException(Exception e) {
//// log.error(ThrowableUtil.getStackTrace(e));
// return buildResponseEntity(ApiError.error(401,"token 失效"));
// }
/**
* 处理自定义异常
*/
@ExceptionHandler(value = BadRequestException.class)
public ResponseEntity<ApiError> badRequestException(BadRequestException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
log.info(e.getMessage());
return buildResponseEntity(ApiError.error(e.getStatus(),e.getMessage()));
}
/**
* 处理所有接口数据验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
String msg = "不能为空";
if(msg.equals(message)){
message = str[1] + ":" + message;
}
return buildResponseEntity(ApiError.error(message));
}
/**
* 统一返回
*/
private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getCode()));
}
}

View File

@@ -0,0 +1,15 @@
package org.nl.logging.annotation;
import java.lang.annotation.*;
/**
* @author dsh
* 2025/11/28
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String value() default "";
}

View File

@@ -0,0 +1,79 @@
package org.nl.logging.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.nl.logging.annotation.Log;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author dsh
* 2025/11/28
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
/**
* 配置切入点
*/
@Pointcut("@annotation(org.nl.logging.annotation.Log)")
public void logPointCut(){}
/**
* 环绕通知
* @param point
* @return
* @throws Throwable
*/
@Around("logPointCut()")
public Object logAround(ProceedingJoinPoint point) throws Throwable {
Object result = null;
long beginTime = System.currentTimeMillis();
// 执行方法
result = point.proceed();
// 执行时长(毫秒)
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();
// 请求的方法参数名称
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
String[] paramNames = parameterNameDiscoverer.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);
}
}

View File

@@ -0,0 +1,77 @@
package org.nl.response;
import lombok.Builder;
import lombok.Data;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.OK;
/**
* @author dsh
* 2025/11/26
*/
@Data
@Builder
public class WebResponse<T> {
/**
* 状态码
*/
private Integer code = 200;
/**
* 消息
*/
private String message;
/**
* 数据
*/
private T data;
/**
* 不带数据反馈
* @return WebResponse
*/
public static WebResponse requestOk() {
return WebResponse.builder()
.message("成功")
.code(OK.value())
.build();
}
/**
* 带消息反馈
* @return WebResponse
*/
public static WebResponse requestOk(String message) {
return WebResponse.builder()
.message(message)
.code(OK.value())
.build();
}
/**
* 带数据反馈
* @return WebResponse
*/
public static <T> WebResponse requestParamOk(T data) {
return WebResponse.builder()
.message("成功")
.data(data)
.code(OK.value())
.build();
}
/**
* 带数据和消息反馈
* @return WebResponse
*/
public static <T> WebResponse requestParamOk(T data, String message) {
return WebResponse.builder()
.message(message)
.data(data)
.code(OK.value())
.build();
}
}

View File

@@ -0,0 +1,11 @@
package org.nl.util;
public class IdUtil {
public static Long getLongId() {
return cn.hutool.core.util.IdUtil.getSnowflake(1, 1).nextId();
}
public static String getStringId() {
return String.valueOf(IdUtil.getLongId());
}
}

View File

@@ -0,0 +1,123 @@
package org.nl.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author dsh
* 2025/12/1
* 任务号生成器工具类 - 时间戳 + 序列号方案
*/
public class TaskCodeGeneratorUtil {
private TaskCodeGeneratorUtil() {
throw new IllegalStateException("Utility class");
}
// 序列号生成器
private static final AtomicLong SEQUENCE = new AtomicLong(0);
// 上次时间戳
private static volatile String LAST_TIMESTAMP = "";
// 重入锁保证线程安全
private static final ReentrantLock LOCK = new ReentrantLock();
// 日期时间格式化器(线程安全)
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
/**
* 生成任务号默认格式yyyyMMddHHmmssSSS + 3位序列号
* @return 任务号字符串
*/
public static String generateTaskId() {
return generateTaskId("yyyyMMddHHmmssSSS", 3);
}
/**
* 生成任务号(自定义时间格式)
* @param datePattern 时间格式yyyyMMddHHmmss
* @param sequenceLength 序列号长度
* @return 任务号字符串
*/
public static String generateTaskId(String datePattern, int sequenceLength) {
LOCK.lock();
try {
String currentTimestamp = getCurrentTimestamp(datePattern);
// 检查是否是新时间单位
if (!currentTimestamp.equals(LAST_TIMESTAMP)) {
LAST_TIMESTAMP = currentTimestamp;
SEQUENCE.set(0);
}
// 获取并增加序列号
long seq = SEQUENCE.getAndIncrement();
// 检查序列号是否超过范围
long maxSeq = (long) Math.pow(10, sequenceLength) - 1;
if (seq > maxSeq) {
// 序列号超过限制,等待下一时间单位
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 递归调用(注意:这种情况极少发生)
return generateTaskId(datePattern, sequenceLength);
}
// 格式化成指定长度的序列号
String sequenceFormat = "%0" + sequenceLength + "d";
return currentTimestamp + String.format(sequenceFormat, seq);
} finally {
LOCK.unlock();
}
}
/**
* 生成带前缀的任务号
* @param prefix 任务号前缀TASK、ORDER等
* @return 带前缀的任务号
*/
public static String generateTaskIdWithPrefix(String prefix) {
return prefix + "_" + generateTaskId();
}
/**
* 生成带前缀的自定义格式任务号
* @param prefix 前缀
* @param datePattern 时间格式
* @param sequenceLength 序列号长度
* @return 任务号
*/
public static String generateTaskIdWithPrefix(String prefix, String datePattern, int sequenceLength) {
return prefix + "_" + generateTaskId(datePattern, sequenceLength);
}
/**
* 获取当前时间戳字符串
*/
private static String getCurrentTimestamp(String pattern) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.now().format(formatter);
}
/**
* 批量生成任务号
* @param count 生成数量
* @return 任务号列表
*/
public static List<String> batchGenerateTaskId(int count) {
List<String> taskIds = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
taskIds.add(generateTaskId());
}
return taskIds;
}
}

View File

@@ -0,0 +1,22 @@
package org.nl.util;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @author liejiu
*/
public class ThrowableUtil {
/**
* 获取堆栈信息
*/
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
}
}
}

View File

@@ -0,0 +1,18 @@
package org.nl.util;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author dsh
* 2025/11/26
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); }
}