Browse Source

接口日志监控和管理

2643616413 5 days ago
parent
commit
70e34642a4
16 changed files with 784 additions and 9 deletions
  1. 15 1
      server/yusp-tagging-core/pom.xml
  2. 21 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/annotation/ApiOperationType.java
  3. 143 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/aop/ApiLogAspect.java
  4. 12 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/config/AopConfig.java
  5. 101 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagApiLogController.java
  6. 7 4
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagAppController.java
  7. 8 1
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagCategoryController.java
  8. 24 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagApiLogQueryDTO.java
  9. 77 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagApiLog.java
  10. 27 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagApiLogListVO.java
  11. 54 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagApiLogMapper.java
  12. 45 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagApiLogService.java
  13. 159 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagApiLogServiceImpl.java
  14. 14 2
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagCategoryServiceImpl.java
  15. 76 0
      server/yusp-tagging-core/src/main/resources/mapper/AitagApiLogMapper.xml
  16. 1 1
      server/yusp-tagging-core/src/main/resources/mapper/AitagTagCategoryMapper.xml

+ 15 - 1
server/yusp-tagging-core/pom.xml

@@ -164,7 +164,21 @@
             <version>3.4.5</version> <!-- 最后一个支持 Java 8 的版本 -->
             <scope>compile</scope>
         </dependency>
-
+        <!-- AOP 模块  2.26新增 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <version>1.9.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+            <version>1.9.7</version>
+        </dependency>
     </dependencies>
 
     <dependencyManagement>

+ 21 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/annotation/ApiOperationType.java

@@ -0,0 +1,21 @@
+package cn.com.yusys.yusp.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * API操作类型注解
+ * 用于标识接口的操作类型
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiOperationType {
+    
+    /**
+     * 操作类型名称
+     * @return 操作类型
+     */
+    String value();
+}

+ 143 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/aop/ApiLogAspect.java

@@ -0,0 +1,143 @@
+package cn.com.yusys.yusp.aop;
+
+import cn.com.yusys.yusp.annotation.ApiOperationType;
+import cn.com.yusys.yusp.domain.entity.AitagApiLog;
+import cn.com.yusys.yusp.service.AitagApiLogService;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Date;
+
+@Aspect
+@Component
+public class ApiLogAspect {
+
+    @Autowired
+    private AitagApiLogService apiLogService;
+
+    @Around("@annotation(cn.com.yusys.yusp.annotation.ApiOperationType)")
+    public Object logApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
+        long startTime = System.currentTimeMillis();
+
+        AitagApiLog apiLog = new AitagApiLog();
+        apiLog.setId(apiLogService.generateLogId());
+        apiLog.setCreateDate(new Date());
+
+        // 获取操作类型
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        ApiOperationType annotation = method.getAnnotation(ApiOperationType.class);
+        if (annotation != null) {
+            apiLog.setOperationType(annotation.value());
+        }
+
+        // 记录输入参数(隐藏敏感数据)
+        Object[] args = joinPoint.getArgs();
+        if (args != null && args.length > 0) {
+            try {
+                String inputJson = JSON.toJSONString(args);
+                // 隐藏敏感数据
+                String hiddenInput = hideSensitiveData(inputJson);
+                apiLog.setInputData(hiddenInput);
+            } catch (Exception e) {
+                apiLog.setInputData("参数序列化失败");
+            }
+        }
+
+        // 执行目标方法
+        Object result;
+        try {
+            result = joinPoint.proceed();
+
+            // 记录成功执行
+            long endTime = System.currentTimeMillis();
+            apiLog.setConsumingTime(String.valueOf(endTime - startTime));
+
+            // 记录输出结果(隐藏敏感数据)
+            if (result != null) {
+                try {
+                    String outputJson = JSON.toJSONString(result);
+                    // 隐藏敏感数据
+                    String hiddenOutput = hideSensitiveData(outputJson);
+                    apiLog.setOutputData(hiddenOutput);
+                } catch (Exception e) {
+                    apiLog.setOutputData("返回结果序列化失败");
+                }
+            }
+
+            // 保存日志
+            apiLogService.recordApiLog(apiLog);
+
+            return result;
+        } catch (Exception e) {
+            // 记录异常情况
+            long endTime = System.currentTimeMillis();
+            apiLog.setConsumingTime(String.valueOf(endTime - startTime));
+
+            // 记录异常信息
+            String errorMsg = e.getClass().getSimpleName();
+            if (e.getMessage() != null) {
+                errorMsg += ": " + e.getMessage();
+            }
+            // 限制错误信息长度
+            if (errorMsg.length() > 500) {
+                errorMsg = errorMsg.substring(0, 500) + "...";
+            }
+            apiLog.setOutputData("调用失败 - " + errorMsg);
+
+            // 保存日志
+            apiLogService.recordApiLog(apiLog);
+
+            // 重新抛出异常
+            throw e;
+        }
+    }
+
+    /**
+     * 隐藏敏感数据
+     * @param jsonStr 原始JSON字符串
+     * @return 隐藏敏感数据后的JSON字符串
+     */
+    private String hideSensitiveData(String jsonStr) {
+        try {
+            // 尝试解析为JSONObject
+            Object parsedObj = JSON.parse(jsonStr);
+            if (parsedObj instanceof JSONObject) {
+                JSONObject jsonObj = (JSONObject) parsedObj;
+                hideDataInObject(jsonObj);
+                return jsonObj.toJSONString();
+            } else {
+                // 如果不是JSONObject,可能是数组或其他类型
+                return jsonStr.replaceAll("\"data\"\\s*:\\s*\"[^\"]*\"", "\"data\":\"已隐藏\"")
+                        .replaceAll("\"data\"\\s*:\\s*\\{[^}]*\\}", "\"data\":\"已隐藏\"");
+            }
+        } catch (Exception e) {
+            // 解析失败时使用正则表达式简单替换
+            return jsonStr.replaceAll("\"data\"\\s*:\\s*\"[^\"]*\"", "\"data\":\"已隐藏\"")
+                    .replaceAll("\"data\"\\s*:\\s*\\{[^}]*\\}", "\"data\":\"已隐藏\"");
+        }
+    }
+
+    /**
+     * 递归隐藏JSONObject中的敏感数据
+     */
+    private void hideDataInObject(JSONObject obj) {
+        for (String key : obj.keySet()) {
+            if ("data".equalsIgnoreCase(key)) {
+                obj.put(key, "已隐藏");
+            } else {
+                Object value = obj.get(key);
+                if (value instanceof JSONObject) {
+                    hideDataInObject((JSONObject) value);
+                }
+            }
+        }
+    }
+}

+ 12 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/config/AopConfig.java

@@ -0,0 +1,12 @@
+package cn.com.yusys.yusp.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+@Configuration
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+public class AopConfig {
+    // AOP配置类,启用AspectJ自动代理
+    // proxyTargetClass = true 强制使用CGLIB代理,避免某些情况下JDK动态代理的问题
+
+}

+ 101 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagApiLogController.java

@@ -0,0 +1,101 @@
+package cn.com.yusys.yusp.controller;
+
+import cn.com.yusys.yusp.domain.dto.AitagApiLogQueryDTO;
+import cn.com.yusys.yusp.domain.entity.AitagApiLog;
+import cn.com.yusys.yusp.domain.vo.AitagApiLogListVO;
+import cn.com.yusys.yusp.model.Result;
+import cn.com.yusys.yusp.service.AitagApiLogService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+
+@RestController
+@RequestMapping("/api/aitag-apilog")
+public class AitagApiLogController {
+
+    @Autowired
+    private AitagApiLogService apiLogService;
+
+
+    @GetMapping("/list")
+    public Result<List<AitagApiLogListVO>> listApiLogs(
+            @RequestParam(required = false) String startTime,
+            @RequestParam(required = false) String endTime,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            // 构建查询条件
+            AitagApiLogQueryDTO queryDTO = new AitagApiLogQueryDTO();
+            queryDTO.setPage(page);
+            queryDTO.setPageSize(pageSize);
+
+            // 处理时间参数 - 只处理日期粒度
+            if (startTime != null && !startTime.isEmpty()) {
+                queryDTO.setStartTime(parseDateOnly(startTime));
+            }
+
+            if (endTime != null && !endTime.isEmpty()) {
+                queryDTO.setEndTime(parseDateOnly(endTime));
+            }
+
+            Page<AitagApiLogListVO> pageResult = apiLogService.listApiLogs(queryDTO);
+            return Result.pageSuccess(pageResult.getRecords(), pageResult.getTotal());
+        } catch (Exception e) {
+            return Result.error("500", "查询日志列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 解析日期字符串(只保留日期部分,设置时间为当天00:00:00)
+     */
+    private Date parseDateOnly(String dateStr) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            Date date = sdf.parse(dateStr);
+            // 设置时间为当天00:00:00
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(date);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTime();
+        } catch (ParseException e) {
+            throw new RuntimeException("日期格式错误,请使用 yyyy-MM-dd 格式");
+        }
+    }
+
+    @GetMapping("/refresh")
+    public Result<List<AitagApiLogListVO>> refreshApiLogs(
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            Page<AitagApiLogListVO> pageResult = apiLogService.refreshApiLogs(page, pageSize);
+            // 直接返回Page对象
+            return Result.pageSuccess(pageResult.getRecords(), pageResult.getTotal());
+        } catch (Exception e) {
+            return Result.error("500", "刷新日志列表失败:" + e.getMessage());
+        }
+    }
+
+
+    @GetMapping("/query/{id}")
+    public Result<AitagApiLog> getApiLogDetail(@PathVariable String id) {
+        try {
+            AitagApiLog logDetail = apiLogService.getApiLogDetail(id);
+            if (logDetail == null) {
+                return Result.error("404", "日志记录不存在");
+            }
+            return Result.success(logDetail);
+        } catch (Exception e) {
+            return Result.error("500", "查询日志详情失败:" + e.getMessage());
+        }
+    }
+}

+ 7 - 4
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagAppController.java

@@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import cn.com.yusys.yusp.annotation.ApiOperationType;
+
 
 import java.util.List;
 
@@ -18,6 +20,7 @@ public class AitagAppController {
     @Autowired
     private AitagAppService aiTagAppService;
 
+    @ApiOperationType("应用列表一览")
     @GetMapping("/list")
     public Result<List<AitagApp>> listApps(
             @RequestParam(defaultValue = "1") int page,
@@ -34,7 +37,7 @@ public class AitagAppController {
     }
 
 
-
+    @ApiOperationType("新增应用")
     @PostMapping("/add")
     public Result<AitagApp> addApp(@Validated @RequestBody AitagAppCreateDTO dto) {
         try {
@@ -45,7 +48,7 @@ public class AitagAppController {
         }
     }
 
-
+    @ApiOperationType("查询应用")
     @GetMapping("/query")
     public Result<List<AitagApp>> queryByName(
             @RequestParam String appName,
@@ -61,7 +64,7 @@ public class AitagAppController {
 
 
 
-
+    @ApiOperationType("重置密钥")
     @PostMapping("/reset-secret")
     public Result<String> resetSecret(@RequestParam String id) {
         try {
@@ -72,7 +75,7 @@ public class AitagAppController {
         }
     }
 
-
+    @ApiOperationType("禁用应用")
     @PostMapping("/disable")
     public Result<Void> disableApp(@RequestParam String id) {
         try {

+ 8 - 1
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagCategoryController.java

@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import cn.com.yusys.yusp.annotation.ApiOperationType;
 
 import java.util.List;
 
@@ -20,6 +21,7 @@ public class AitagTagCategoryController {
     @Autowired
     private AitagTagCategoryService aiTagCategoryService;
 
+    @ApiOperationType("标签体系列表")
     @GetMapping("/list")
     public Result<List<AitagTagCategoryVO>> listCategories(
             @RequestParam(defaultValue = "1") int page,
@@ -32,7 +34,7 @@ public class AitagTagCategoryController {
         }
     }
 
-
+    @ApiOperationType("新增标签体系")
     @PostMapping("/create")
     public Result<AitagTagCategory> createCategory(@Validated @RequestBody AitagTagCategoryCreateDTO dto) {
         try {
@@ -43,6 +45,7 @@ public class AitagTagCategoryController {
         }
     }
 
+    @ApiOperationType("编辑标签体系")
     @PostMapping("/update")
     public Result<AitagTagCategory> updateCategory(@Validated @RequestBody AitagTagCategoryUpdateDTO dto) {
         try {
@@ -53,6 +56,7 @@ public class AitagTagCategoryController {
         }
     }
 
+    @ApiOperationType("查询标签体系")
     @GetMapping("/query")
     public Result<List<AitagTagCategory>> searchByCategoryNm(@RequestParam String categoryNm) {
         try {
@@ -63,6 +67,7 @@ public class AitagTagCategoryController {
         }
     }
 
+    @ApiOperationType("启用标签体系")
     @PostMapping("/enable")
     public Result<Void> enableCategory(@RequestParam String id) {
         try {
@@ -73,6 +78,7 @@ public class AitagTagCategoryController {
         }
     }
 
+    @ApiOperationType("停用标签体系")
     @PostMapping("/disable")
     public Result<Void> disableCategory(@RequestParam String id) {
         try {
@@ -83,6 +89,7 @@ public class AitagTagCategoryController {
         }
     }
 
+    @ApiOperationType("删除标签体系")
     @PostMapping("/delete")
     public Result<Void> deleteCategory(@RequestParam String id) {
         try {

+ 24 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagApiLogQueryDTO.java

@@ -0,0 +1,24 @@
+package cn.com.yusys.yusp.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@ApiModel("API日志查询条件DTO")
+public class AitagApiLogQueryDTO {
+
+    @ApiModelProperty(value = "起始时间")
+    private Date startTime;
+
+    @ApiModelProperty(value = "结束时间")
+    private Date endTime;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页大小", example = "10")
+    private Integer pageSize = 10;
+}

+ 77 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagApiLog.java

@@ -0,0 +1,77 @@
+package cn.com.yusys.yusp.domain.entity;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import javax.validation.constraints.NotNull;
+
+import java.io.Serializable;
+
+import java.util.Date;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+/**
+* 操作日志
+* @TableName aitag_api_log
+*/
+@Data
+public class AitagApiLog implements Serializable {
+
+    /**
+    * 
+    */
+    @NotBlank(message="[]不能为空")
+    @Size(max= 100,message="编码长度不能超过100")
+    @ApiModelProperty("")
+    @Length(max= 100,message="编码长度不能超过100")
+    private String id;
+    /**
+    * 操作类型
+    */
+    @Size(max= 100,message="编码长度不能超过100")
+    @ApiModelProperty("操作类型")
+    @Length(max= 100,message="编码长度不能超过100")
+    private String operationType;
+    /**
+    * 输入数据
+    */
+    @Size(max= 100,message="编码长度不能超过100")
+    @ApiModelProperty("输入数据")
+    @Length(max= 100,message="编码长度不能超过100")
+    private String inputData;
+    /**
+     * 输出数据
+     */
+    @Size(max= 100,message="编码长度不能超过255")
+    @ApiModelProperty("输出数据")
+    @Length(max= 100,message="编码长度不能超过255")
+    private String outputData;
+    /**
+    * 创建日期
+    */
+    @ApiModelProperty("创建日期")
+    private Date createDate;
+    /**
+    * 操作人ID
+    */
+    @Size(max= 100,message="编码长度不能超过100")
+    @ApiModelProperty("操作人ID")
+    @Length(max= 100,message="编码长度不能超过100")
+    private String userId;
+    /**
+    * 操作人昵称
+    */
+    @Size(max= 100,message="编码长度不能超过100")
+    @ApiModelProperty("操作人昵称")
+    @Length(max= 100,message="编码长度不能超过100")
+    private String userName;
+    /**
+    * 耗时
+    */
+    @Size(max= -1,message="编码长度不能超过-1")
+    @ApiModelProperty("耗时")
+    @Length(max= -1,message="编码长度不能超过-1")
+    private String consumingTime;
+
+}

+ 27 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagApiLogListVO.java

@@ -0,0 +1,27 @@
+package cn.com.yusys.yusp.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@ApiModel("API日志列表视图对象")
+public class AitagApiLogListVO {
+
+    @ApiModelProperty("日志ID")
+    private String id;
+
+    @ApiModelProperty("用户名称")
+    private String userName;
+
+    @ApiModelProperty("用户ID")
+    private String userId;
+
+    @ApiModelProperty("操作类型")
+    private String operationType;
+
+    @ApiModelProperty("操作时间")
+    private Date createDate;
+}

+ 54 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagApiLogMapper.java

@@ -0,0 +1,54 @@
+package cn.com.yusys.yusp.mapper;
+
+import cn.com.yusys.yusp.domain.entity.AitagApiLog;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface AitagApiLogMapper {
+
+    /**
+     * 插入API调用日志
+     * @param apiLog 日志实体
+     * @return 影响行数
+     */
+    int insertApiLog(AitagApiLog apiLog);
+
+    /**
+     * 根据日期获取最大的日志ID
+     * @param dateStr 日期字符串(yyyyMMdd格式)
+     * @return 最大的日志ID
+     */
+    String getMaxIdByDate(@Param("dateStr") String dateStr);
+
+    /**
+     * 统计符合条件的日志数量
+     * @param startTime 起始时间
+     * @param endTime 结束时间
+     * @return 数量
+     */
+    long countApiLogs(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
+
+    /**
+     * 分页查询日志列表
+     * @param startTime 起始时间
+     * @param endTime 结束时间
+     * @param offset 偏移量
+     * @param limit 限制数量
+     * @return 日志列表
+     */
+    List<AitagApiLog> selectApiLogs(@Param("startTime") Date startTime,
+                                    @Param("endTime") Date endTime,
+                                    @Param("offset") int offset,
+                                    @Param("limit") int limit);
+
+    /**
+     * 根据ID查询日志详情
+     * @param id 日志ID
+     * @return 日志详情
+     */
+    AitagApiLog selectApiLogById(@Param("id") String id);
+}

+ 45 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagApiLogService.java

@@ -0,0 +1,45 @@
+package cn.com.yusys.yusp.service;
+
+import cn.com.yusys.yusp.domain.dto.AitagApiLogQueryDTO;
+import cn.com.yusys.yusp.domain.entity.AitagApiLog;
+import cn.com.yusys.yusp.domain.vo.AitagApiLogListVO;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.Date;
+
+public interface AitagApiLogService {
+
+    /**
+     * 记录API调用日志
+     * @param apiLog 日志实体
+     */
+    void recordApiLog(AitagApiLog apiLog);
+
+    /**
+     * 生成日志ID
+     * @return 日志ID
+     */
+    String generateLogId();
+
+    /**
+     * 分页查询日志列表
+     * @param queryDTO 查询条件
+     * @return 分页结果
+     */
+    Page<AitagApiLogListVO> listApiLogs(AitagApiLogQueryDTO queryDTO);
+
+    /**
+     * 刷新日志列表(查询所有数据)
+     * @param page 页码
+     * @param pageSize 每页大小
+     * @return 分页结果
+     */
+    Page<AitagApiLogListVO> refreshApiLogs(Integer page, Integer pageSize);
+
+    /**
+     * 根据ID查询日志详情
+     * @param id 日志ID
+     * @return 日志详情
+     */
+    AitagApiLog getApiLogDetail(String id);
+}

+ 159 - 0
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagApiLogServiceImpl.java

@@ -0,0 +1,159 @@
+package cn.com.yusys.yusp.service.impl;
+
+import cn.com.yusys.yusp.domain.dto.AitagApiLogQueryDTO;
+import cn.com.yusys.yusp.domain.entity.AitagApiLog;
+import cn.com.yusys.yusp.domain.vo.AitagApiLogListVO;
+import cn.com.yusys.yusp.mapper.AitagApiLogMapper;
+import cn.com.yusys.yusp.service.AitagApiLogService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@Service
+public class AitagApiLogServiceImpl implements AitagApiLogService {
+
+    private static final Logger logger = LoggerFactory.getLogger(AitagApiLogServiceImpl.class);
+
+    @Autowired
+    private AitagApiLogMapper apiLogMapper;
+
+    // 日期格式化器
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
+
+    // 缓存当天的最大序列号 key:日期 value:最大序列号
+    private static final Map<String, Long> DATE_SEQUENCE_CACHE = new ConcurrentHashMap<>();
+
+    // 当前缓存的日期
+    private volatile String cachedDate = "";
+
+    @Override
+    public void recordApiLog(AitagApiLog apiLog) {
+        try {
+            apiLogMapper.insertApiLog(apiLog);
+            logger.info("API调用日志记录成功: {} - {}", apiLog.getId(), apiLog.getOperationType());
+        } catch (Exception e) {
+            logger.error("记录API调用日志失败: {}", e.getMessage(), e);
+            // 不抛出异常,避免影响主业务流程
+        }
+    }
+
+    @Override
+    public String generateLogId() {
+        String currentDate = DATE_FORMAT.format(new Date());
+
+        // 检查是否需要刷新缓存(跨天了)
+        if (!currentDate.equals(cachedDate)) {
+            refreshCache(currentDate);
+        }
+
+        // 从缓存获取并递增序列号
+        Long currentSequence = DATE_SEQUENCE_CACHE.get(currentDate);
+        if (currentSequence == null) {
+            // 缓存中没有数据,从数据库查询
+            loadFromDatabase(currentDate);
+            currentSequence = DATE_SEQUENCE_CACHE.get(currentDate);
+        }
+
+        currentSequence++;
+        DATE_SEQUENCE_CACHE.put(currentDate, currentSequence);
+
+        // 重置序列号(超过6位数限制)
+        if (currentSequence > 999999) {
+            currentSequence = 1L;
+            DATE_SEQUENCE_CACHE.put(currentDate, currentSequence);
+        }
+
+        return String.format("API%s%06d", currentDate, currentSequence);
+    }
+
+    @Override
+    public Page<AitagApiLogListVO> listApiLogs(AitagApiLogQueryDTO queryDTO) {
+        int page = queryDTO.getPage() != null ? queryDTO.getPage() : 1;
+        int pageSize = queryDTO.getPageSize() != null ? queryDTO.getPageSize() : 10;
+        int offset = (page - 1) * pageSize;
+
+        // 查询总记录数
+        long total = apiLogMapper.countApiLogs(queryDTO.getStartTime(), queryDTO.getEndTime());
+
+        // 查询列表数据
+        List<AitagApiLog> logs = apiLogMapper.selectApiLogs(
+                queryDTO.getStartTime(),
+                queryDTO.getEndTime(),
+                offset,
+                pageSize
+        );
+
+        // 转换为VO
+        List<AitagApiLogListVO> voList = logs.stream().map(log -> {
+            AitagApiLogListVO vo = new AitagApiLogListVO();
+            BeanUtils.copyProperties(log, vo);
+            return vo;
+        }).collect(Collectors.toList());
+
+        Page<AitagApiLogListVO> pageResult = new Page<>(page, pageSize, total);
+        pageResult.setRecords(voList);
+        return pageResult;
+    }
+
+    @Override
+    public Page<AitagApiLogListVO> refreshApiLogs(Integer page, Integer pageSize) {
+        AitagApiLogQueryDTO queryDTO = new AitagApiLogQueryDTO();
+        queryDTO.setPage(page != null ? page : 1);
+        queryDTO.setPageSize(pageSize != null ? pageSize : 10);
+        return listApiLogs(queryDTO);
+    }
+
+    @Override
+    public AitagApiLog getApiLogDetail(String id) {
+        return apiLogMapper.selectApiLogById(id);
+    }
+
+    /**
+     * 刷新缓存 - 每天0点执行
+     */
+    @Scheduled(cron = "0 0 0 * * ?")
+    public void dailyCacheRefresh() {
+        String newDate = DATE_FORMAT.format(new Date());
+        refreshCache(newDate);
+        logger.info("日志ID缓存已刷新,新日期: {}", newDate);
+    }
+
+    /**
+     * 刷新缓存
+     */
+    private synchronized void refreshCache(String newDate) {
+        cachedDate = newDate;
+        DATE_SEQUENCE_CACHE.clear();
+        loadFromDatabase(newDate);
+    }
+
+    /**
+     * 从数据库加载序列号
+     */
+    private void loadFromDatabase(String dateStr) {
+        String maxId = apiLogMapper.getMaxIdByDate(dateStr);
+        long sequence = 0;
+
+        if (maxId != null && maxId.length() >= 17) {
+            try {
+                String sequenceStr = maxId.substring(11, 17);
+                sequence = Long.parseLong(sequenceStr);
+            } catch (Exception e) {
+                logger.warn("解析数据库最大ID序列号失败: {}", e.getMessage());
+            }
+        }
+
+        DATE_SEQUENCE_CACHE.put(dateStr, sequence);
+    }
+}

+ 14 - 2
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagCategoryServiceImpl.java

@@ -100,9 +100,18 @@ public class AitagTagCategoryServiceImpl implements AitagTagCategoryService {
         if (category == null) {
             throw new RuntimeException("标签体系不存在");
         }
+
+        // 检查标签数量
+        int tagCount = aiTagCategoryMapper.countTagsByCategoryId(id);
+        if (tagCount == 0) {
+            throw new RuntimeException("体系内无可用标签!");
+        }
+
+        // 检查当前状态
         if (category.getState() == 0) {
-            throw new RuntimeException("标签体系已经是启用状态");
+            throw new RuntimeException("当前已为启用状态!");
         }
+
         aiTagCategoryMapper.updateState(id, 0);
     }
 
@@ -112,9 +121,12 @@ public class AitagTagCategoryServiceImpl implements AitagTagCategoryService {
         if (category == null) {
             throw new RuntimeException("标签体系不存在");
         }
+
+        // 检查当前状态
         if (category.getState() == 1) {
-            throw new RuntimeException("标签体系已经是停用状态");
+            throw new RuntimeException("当前已为停用状态!");
         }
+
         aiTagCategoryMapper.updateState(id, 1);
     }
 

+ 76 - 0
server/yusp-tagging-core/src/main/resources/mapper/AitagApiLogMapper.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.yusys.yusp.mapper.AitagApiLogMapper">
+
+    <!-- 插入API调用日志 -->
+    <insert id="insertApiLog" parameterType="cn.com.yusys.yusp.domain.entity.AitagApiLog">
+        INSERT INTO aitag_api_log (
+            id,
+            operation_type,
+            input_data,
+            output_data,
+            create_date,
+            user_id,
+            user_name,
+            consuming_time
+        ) VALUES (
+                     #{id},
+                     #{operationType},
+                     #{inputData},
+                     #{outputData},
+                     #{createDate},
+                     #{userId},
+                     #{userName},
+                     #{consumingTime}
+                 )
+    </insert>
+
+    <!-- 根据日期获取最大的日志ID -->
+    <select id="getMaxIdByDate" resultType="string">
+        SELECT id
+        FROM aitag_api_log
+        WHERE id LIKE CONCAT('API', #{dateStr}, '%')
+        ORDER BY id DESC
+            LIMIT 1
+    </select>
+
+    <!-- 统计符合条件的日志数量 -->
+    <select id="countApiLogs" resultType="long">
+        SELECT COUNT(*)
+        FROM aitag_api_log
+        <where>
+            <if test="startTime != null">
+                AND create_date >= #{startTime}
+            </if>
+            <if test="endTime != null">
+                <!-- 结束时间加一天,确保包含当天的所有记录 -->
+                AND create_date <![CDATA[<]]> DATE_ADD(#{endTime}, INTERVAL 1 DAY)
+            </if>
+        </where>
+    </select>
+
+    <!-- 分页查询日志列表 -->
+    <select id="selectApiLogs" resultType="cn.com.yusys.yusp.domain.entity.AitagApiLog">
+        SELECT id, user_name, user_id, operation_type, create_date
+        FROM aitag_api_log
+        <where>
+            <if test="startTime != null">
+                AND create_date >= #{startTime}
+            </if>
+            <if test="endTime != null">
+                <!-- 结束时间加一天,确保包含当天的所有记录 -->
+                AND create_date <![CDATA[<]]> DATE_ADD(#{endTime}, INTERVAL 1 DAY)
+            </if>
+        </where>
+        ORDER BY create_date DESC
+        LIMIT #{offset}, #{limit}
+    </select>
+
+    <!-- 根据ID查询日志详情 -->
+    <select id="selectApiLogById" resultType="cn.com.yusys.yusp.domain.entity.AitagApiLog">
+        SELECT *
+        FROM aitag_api_log
+        WHERE id = #{id}
+    </select>
+
+</mapper>

+ 1 - 1
server/yusp-tagging-core/src/main/resources/mapper/AitagTagCategoryMapper.xml

@@ -25,7 +25,7 @@
     <!-- 新增 -->
     <insert id="insertCategory">
         INSERT INTO aitag_tag_category (id, category_nm, category_desc, state, is_delete)
-        VALUES (#{id}, #{categoryNm}, #{categoryDesc}, 0, 0)
+        VALUES (#{id}, #{categoryNm}, #{categoryDesc}, 1, 0)
     </insert>
 
     <!-- 按 ID 查询 -->