diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/BaseApplication.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/BaseApplication.java index bc01c61b..84ce341f 100644 --- a/code/base-project/base-springboot/src/main/java/cn/g3soft/BaseApplication.java +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/BaseApplication.java @@ -1,5 +1,6 @@ package cn.g3soft; +import cn.g3soft.utils.SnowflakeIdUtils; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -11,6 +12,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; }) public class BaseApplication { public static void main(String[] args) { + System.out.println(SnowflakeIdUtils.nextId()); SpringApplication.run(BaseApplication.class,args); } } diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/common/Result.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/common/Result.java deleted file mode 100644 index a3f6da97..00000000 --- a/code/base-project/base-springboot/src/main/java/cn/g3soft/common/Result.java +++ /dev/null @@ -1,79 +0,0 @@ -package cn.g3soft.common; - -import cn.g3soft.enums.ResponseEnum; -import lombok.Data; -import lombok.NonNull; - -@Data -public class Result { - private final Integer code; - private final String message; - @NonNull - private final T data; - private final Boolean showExceptionDetail; - - private Result(Integer code, String message, T data, Boolean showExceptionDetail) { - this.code = code; - this.message = message; - this.data = data; - this.showExceptionDetail = showExceptionDetail; - } - - // 默认showDetail为false的构造 - private Result(Integer code, String message, T data) { - this(code, message, data, false); - } - - // ✅ 链式设置 showExceptionDetail:返回一个新对象 - public Result withShowExceptionDetail(boolean showExceptionDetail) { - return new Result<>(this.code, this.message, this.data, showExceptionDetail); - } - - // 成功响应(无数据) - public static Result success() { - return new Result<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMessage(), null); - } - - public static Result success(String message) { - return new Result<>(ResponseEnum.SUCCESS.getCode(), message, null); - } - - public static Result success(@NonNull T data) { - return new Result<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMessage(), data); - } - - public static Result success(@NonNull T data, String message) { - return new Result<>(ResponseEnum.SUCCESS.getCode(), message, data); - } - - public static Result success(ResponseEnum responseEnum) { - return new Result<>(responseEnum.getCode(), responseEnum.getMessage(), null); - } - - public static Result success(ResponseEnum responseEnum, @NonNull T data) { - return new Result<>(responseEnum.getCode(), responseEnum.getMessage(), data); - } - - // 错误响应(默认不显示详细信息) - public static Result error(ResponseEnum responseEnum) { - return new Result<>(responseEnum.getCode(), responseEnum.getMessage(), null); - } - - public static Result error(Integer code, String message) { - return new Result<>(code, message, null); - } - - public static Result error(String message) { - return new Result<>(ResponseEnum.ERROR.getCode(), message, null); - } - - public static Result error(T data) { - return new Result<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getMessage(), data); - } - public static Result error(T data,String message) { - return new Result<>(ResponseEnum.ERROR.getCode(),message, data); - } - public static Result error() { - return new Result<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getMessage(), null); - } -} diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/common/model/ExceptionEntity.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/common/model/ExceptionEntity.java new file mode 100644 index 00000000..49cf10e4 --- /dev/null +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/common/model/ExceptionEntity.java @@ -0,0 +1,45 @@ +package cn.g3soft.common.model; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Builder; +import lombok.Data; + +import java.util.Date; +@Data +@Builder +public class ExceptionEntity { + /** 异常记录的唯一ID */ + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** 异常发生时间 */ + private Date time; + + /** 请求的URI地址 */ + private String uri; + + /** HTTP请求方法,如GET、POST等 */ + private String httpMethod; + + /** 抛出异常的类名 */ + private String className; + + /** 抛出异常的方法名 */ + private String methodName; + + /** 抛出异常的文件名 */ + private String fileName; + + /** 抛出异常的代码行号 */ + private Integer lineNumber; + + /** 异常的简短信息 */ + private String summary; + + /** 异常的详细堆栈信息 */ + private String detailMessage; + + /** 请求参数(可能为JSON字符串等) */ + private String params; +} diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/common/model/Result.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/common/model/Result.java new file mode 100644 index 00000000..d320def9 --- /dev/null +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/common/model/Result.java @@ -0,0 +1,126 @@ +package cn.g3soft.common.model; + +import cn.g3soft.enums.ResponseEnum; +import lombok.NonNull; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder(toBuilder = true) +public class Result { + private final Integer code; + private final String message; + private final T data; + private final Boolean showExceptionDetail; + + // 工厂方法建议保留,简化构造调用 + public static Result success() { + return Result.builder() + .code(ResponseEnum.SUCCESS.getCode()) + .message(ResponseEnum.SUCCESS.getMessage()) + .showExceptionDetail(false) + .build(); + } + + public static Result success(String message) { + return Result.builder() + .code(ResponseEnum.SUCCESS.getCode()) + .message(message) + .showExceptionDetail(false) + .build(); + } + + public static Result success(@NonNull T data) { + return Result.builder() + .code(ResponseEnum.SUCCESS.getCode()) + .message(ResponseEnum.SUCCESS.getMessage()) + .data(data) + .showExceptionDetail(false) + .build(); + } + + public static Result success(@NonNull T data, String message) { + return Result.builder() + .code(ResponseEnum.SUCCESS.getCode()) + .message(message) + .data(data) + .showExceptionDetail(false) + .build(); + } + + public static Result success(ResponseEnum responseEnum) { + return Result.builder() + .code(responseEnum.getCode()) + .message(responseEnum.getMessage()) + .showExceptionDetail(false) + .build(); + } + + public static Result success(ResponseEnum responseEnum, @NonNull T data) { + return Result.builder() + .code(responseEnum.getCode()) + .message(responseEnum.getMessage()) + .data(data) + .showExceptionDetail(false) + .build(); + } + + public static Result error() { + return Result.builder() + .code(ResponseEnum.ERROR.getCode()) + .message(ResponseEnum.ERROR.getMessage()) + .showExceptionDetail(false) + .build(); + } + + public static Result error(String message) { + return Result.builder() + .code(ResponseEnum.ERROR.getCode()) + .message(message) + .showExceptionDetail(false) + .build(); + } + + public static Result error(Integer code, String message) { + return Result.builder() + .code(code) + .message(message) + .showExceptionDetail(false) + .build(); + } + + public static Result error(ResponseEnum responseEnum) { + return Result.builder() + .code(responseEnum.getCode()) + .message(responseEnum.getMessage()) + .showExceptionDetail(false) + .build(); + } + + public static Result error(T data) { + return Result.builder() + .code(ResponseEnum.ERROR.getCode()) + .message(ResponseEnum.ERROR.getMessage()) + .data(data) + .showExceptionDetail(false) + .build(); + } + + public static Result error(T data, String message) { + return Result.builder() + .code(ResponseEnum.ERROR.getCode()) + .message(message) + .data(data) + .showExceptionDetail(false) + .build(); + } + + public Result withShowExceptionDetail(boolean show) { + return this.toBuilder().showExceptionDetail(show).build(); + } + + public boolean isSuccess() { + return ResponseEnum.SUCCESS.getCode().equals(this.code); + } +} diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/exception/GlobalExceptionHandler.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/exception/GlobalExceptionHandler.java index e2c4b7f0..39154019 100644 --- a/code/base-project/base-springboot/src/main/java/cn/g3soft/exception/GlobalExceptionHandler.java +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/exception/GlobalExceptionHandler.java @@ -1,12 +1,11 @@ package cn.g3soft.exception; -import cn.g3soft.common.Result; +import cn.g3soft.common.model.Result; import cn.g3soft.exception.auth.AuthException; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/ExceptionUtils.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/ExceptionUtils.java new file mode 100644 index 00000000..2089b0b6 --- /dev/null +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/ExceptionUtils.java @@ -0,0 +1,98 @@ +package cn.g3soft.utils; + +import cn.g3soft.common.model.ExceptionEntity; +import cn.g3soft.exception.BaseException; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class ExceptionUtils { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 根据异常和请求信息,生成统一的异常信息对象 ExceptionDetailInfo + * @param ex 异常对象 + * @param request HttpServletRequest,用来获取请求路径和方法等 + * @param projectBasePackage 项目基础包名,如 "com.error",用来定位业务代码异常栈帧 + * @return ExceptionDetailInfo 封装的异常信息 + */ + public static ExceptionEntity extractExceptionInfo(Exception ex, HttpServletRequest request, String projectBasePackage) { + StackTraceElement[] stackTrace = ex.getStackTrace(); + + StackTraceElement source = null; + if (stackTrace != null) { + for (StackTraceElement element : stackTrace) { + if (element.getClassName().startsWith(projectBasePackage)) { + source = element; + break; + } + } + if (source == null && stackTrace.length > 0) { + source = stackTrace[0]; + } + } + + String briefMessage; + + if (ex instanceof BaseException) { + BaseException be = (BaseException) ex; + briefMessage = be.getMessage(); + } else { + if (ex.getMessage() != null) { + String[] lines = ex.getMessage().split("\n"); + // 遍历每一行,找到第一行“非空、非符号”的有效信息 + String usefulLine = null; + for (String line : lines) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + usefulLine = trimmed; + break; + } + } + // 如果找到有效行,就用它;否则仅用异常类名 + if (usefulLine != null) { + briefMessage = ex.getClass().getSimpleName() + ": " + usefulLine; + } else { + briefMessage = ex.getClass().getSimpleName(); + } + } else { + briefMessage = ex.getClass().getSimpleName(); + } + } + + String paramJson = null; + if (request != null) { + Map paramMap = new HashMap<>(); + Enumeration paramNames = request.getParameterNames(); + while (paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + paramMap.put(paramName, request.getParameter(paramName)); + } + try { + paramJson = JsonUtils.toJson(paramMap); + } catch (Exception e) { + paramJson = "参数序列化失败: " + e.getMessage(); + } + } + + return ExceptionEntity.builder() + .id(SnowflakeIdUtils.nextId()) + .uri(request != null ? request.getRequestURI() : null) + .httpMethod(request != null ? request.getMethod() : null) + .className(source != null ? source.getClassName() : null) + .methodName(source != null ? source.getMethodName() : null) + .fileName(source != null ? source.getFileName() : null) + .lineNumber(source != null ? source.getLineNumber() : null) + .summary(briefMessage) + .detailMessage(org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace(ex)) + .time(new Date()) + .params(paramJson) + .build(); + } +} diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java new file mode 100644 index 00000000..1b319ed7 --- /dev/null +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java @@ -0,0 +1,40 @@ +package cn.g3soft.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JsonUtils { + private static final ObjectMapper objectMapper = new ObjectMapper(); + // 判断字符串是否为有效的 JSON 格式 + public static boolean isTypeJSON(String msgText) { + try { + // 尝试将字符串解析为 JsonNode,如果能解析,说明是有效的 JSON + JsonNode node = objectMapper.readTree(msgText); + return node != null; + } catch (Exception e) { + return false; // 如果抛出异常,说明不是有效的 JSON + } + } + + // 将 JSON 字符串转换为指定类型的 Java 对象 + public static T toBean(String json, Class clazz) { + try { + // 使用 ObjectMapper 将 JSON 字符串转换为指定类型的对象 + return objectMapper.readValue(json, clazz); + } catch (JsonProcessingException e) { + e.printStackTrace(); // 打印异常信息 + return null; // 如果转换失败,返回 null + } + } + + // 将对象转换为 JSON 字符串 + public static String toJson(Object obj) { + try { + return objectMapper.writeValueAsString(obj); // 将对象转换为 JSON 字符串 + } catch (JsonProcessingException e) { + System.out.println(e.getMessage()); + return null; // 如果出现异常,返回 null + } + } +} diff --git a/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/SnowflakeIdUtils.java b/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/SnowflakeIdUtils.java new file mode 100644 index 00000000..e4bc1e5f --- /dev/null +++ b/code/base-project/base-springboot/src/main/java/cn/g3soft/utils/SnowflakeIdUtils.java @@ -0,0 +1,165 @@ +package cn.g3soft.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.Assert; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class SnowflakeIdUtils { + + public static long MAX_START_INTERVAL_TIME = TimeUnit.SECONDS.toNanos(5L); + private static final long twepoch = 1288834974657L; + + private static final long workerIdBits = 5L; + private static final long datacenterIdBits = 5L; + private static final long maxWorkerId = 31L; + private static final long maxDatacenterId = 31L; + private static final long sequenceBits = 12L; + + private static final long workerIdShift = 12L; + private static final long datacenterIdShift = 17L; + private static final long timestampLeftShift = 22L; + private static final long sequenceMask = 4095L; + + // 这两个必须初始化 + private static long workerId; + private static long datacenterId; + + private static long sequence = 0L; + private static long lastTimestamp = -1L; + + private static InetAddress inetAddress; + + // 初始化一次即可,线程安全调用 + public static synchronized void init(InetAddress address) { + if (inetAddress == null) { + inetAddress = address; + datacenterId = getDatacenterId(maxDatacenterId); + workerId = getMaxWorkerId(datacenterId, maxWorkerId); + long start = System.nanoTime(); + long end = System.nanoTime(); + if (end - start > MAX_START_INTERVAL_TIME) { + log.warn("Initialization Sequence Very Slow! Get datacenterId:{} workerId:{}", datacenterId, workerId); + } else { + initLog(); + } + } + } + + public static synchronized void init(long fixedWorkerId, long fixedDatacenterId) { + Assert.isTrue(fixedWorkerId <= maxWorkerId && fixedWorkerId >= 0, + String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + Assert.isTrue(fixedDatacenterId <= maxDatacenterId && fixedDatacenterId >= 0, + String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + workerId = fixedWorkerId; + datacenterId = fixedDatacenterId; + initLog(); + } + + private static void initLog() { + if (log.isDebugEnabled()) { + log.debug("Initialization Sequence datacenterId:{} workerId:{}", datacenterId, workerId); + } + } + + protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) { + StringBuilder mpid = new StringBuilder(); + mpid.append(datacenterId); + String name = ManagementFactory.getRuntimeMXBean().getName(); + if (StringUtils.isNotBlank(name)) { + mpid.append(name.split("@")[0]); + } + return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1L); + } + + protected static long getDatacenterId(long maxDatacenterId) { + long id = 0L; + try { + if (inetAddress == null) { + if (log.isDebugEnabled()) { + log.debug("Use localhost address"); + } + inetAddress = InetAddress.getLocalHost(); + } + if (log.isDebugEnabled()) { + log.debug("Get {} network interface", inetAddress); + } + NetworkInterface network = NetworkInterface.getByInetAddress(inetAddress); + if (log.isDebugEnabled()) { + log.debug("Get network interface info: {}", network); + } + if (network == null) { + log.warn("Unable to get network interface for {}", inetAddress); + id = 1L; + } else { + byte[] mac = network.getHardwareAddress(); + if (mac != null && mac.length >= 2) { + id = ((0xffL & (long) mac[mac.length - 2]) | (0xff00L & ((long) mac[mac.length - 1] << 8))) >> 6; + id %= maxDatacenterId + 1L; + } + } + } catch (Exception e) { + log.warn("getDatacenterId error: {}", e.getMessage(), e); + } + return id; + } + + public static synchronized long nextId() { + long timestamp = timeGen(); + + if (timestamp < lastTimestamp) { + long offset = lastTimestamp - timestamp; + if (offset > 5L) { + throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset)); + } + try { + SnowflakeIdUtils.class.wait(offset << 1); + timestamp = timeGen(); + if (timestamp < lastTimestamp) { + throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset)); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + if (sequence == 0) { + timestamp = tilNextMillis(lastTimestamp); + } + } else { + sequence = ThreadLocalRandom.current().nextLong(1, 3); + } + + lastTimestamp = timestamp; + + return ((timestamp - twepoch) << timestampLeftShift) + | (datacenterId << datacenterIdShift) + | (workerId << workerIdShift) + | sequence; + } + + protected static long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + protected static long timeGen() { + return System.currentTimeMillis(); + } + + public static long parseIdTimestamp(long id) { + return (id >> 22) + 1288834974657L; + } +} + diff --git a/code/base-project/base-springboot/src/main/resources/datasource.yml b/code/base-project/base-springboot/src/main/resources/datasource.yml index 70ddc1db..cf88b4b7 100644 --- a/code/base-project/base-springboot/src/main/resources/datasource.yml +++ b/code/base-project/base-springboot/src/main/resources/datasource.yml @@ -1,11 +1,11 @@ datasource: db1: - url: jdbc:jtds:sqlserver://192.168.1.176:1433;databaseName=G3-Email + url: jdbc:jtds:sqlserver://127.0.0.1:1433;databaseName=test1 username: sa password: sa driverClassName: net.sourceforge.jtds.jdbc.Driver db2: - url: jdbc:jtds:sqlserver://192.168.1.176:1433;databaseName=Sllog + url: jdbc:jtds:sqlserver://127.0.0.1:1433;databaseName=test2 username: sa password: sa driverClassName: net.sourceforge.jtds.jdbc.Driver \ No newline at end of file