소스 검색

Merge branch 'master' of http://git.yangzhiqiang.tech/jiayq/ai-tagging

jiayongqiang 5 일 전
부모
커밋
67dd33f339
100개의 변경된 파일4888개의 추가작업 그리고 1496개의 파일을 삭제
  1. 21 0
      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. 64 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/config/DataDictionary.java
  6. 101 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagApiLogController.java
  7. 9 6
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagAppController.java
  8. 15 8
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagCategoryController.java
  9. 154 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagInfoController.java
  10. 107 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagInfoVersionController.java
  11. 164 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/controller/AitagTagLogController.java
  12. 24 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagApiLogQueryDto.java
  13. 0 16
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagAppCreateDTO.java
  14. 16 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagAppCreateDto.java
  15. 0 19
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagTagCategoryCreateDTO.java
  16. 19 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagTagCategoryCreateDto.java
  17. 0 23
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagTagCategoryUpdateDTO.java
  18. 23 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/AitagTagCategoryUpdateDto.java
  19. 114 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/TagInfoDto.java
  20. 114 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/dto/TagInfoVersionDto.java
  21. 77 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagApiLog.java
  22. 55 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagTagDailyAggEntity.java
  23. 0 99
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagTagInfo.java
  24. 115 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagTagInfoEntity.java
  25. 232 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagTagInfoVersionEntity.java
  26. 160 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/entity/AitagTagLogEntity.java
  27. 27 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagApiLogListVo.java
  28. 0 33
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagTagCategoryVO.java
  29. 33 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagTagCategoryVo.java
  30. 171 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagTagInfoQueryVo.java
  31. 210 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/AitagTagInfoVersionQueryVo.java
  32. 44 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/DataOverviewVo.java
  33. 58 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/ExportDataVo.java
  34. 24 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/IconResVo.java
  35. 32 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/SmartTaggingResultVo.java
  36. 10 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/TagDistStatsReqVo.java
  37. 74 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/TagNodeVo.java
  38. 23 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/TaggingTransactionReqVo.java
  39. 21 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/TaggingTrendReqVo.java
  40. 35 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/domain/vo/VersionRollbackVo.java
  41. 54 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagApiLogMapper.java
  42. 24 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagTagDailyAggDao.java
  43. 24 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagTagInfoDao.java
  44. 17 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagTagInfoVersionDao.java
  45. 34 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/mapper/AitagTagLogDao.java
  46. 43 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagApiLogService.java
  47. 2 2
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagAppService.java
  48. 6 6
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagTagCategoryService.java
  49. 44 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagTagInfoService.java
  50. 25 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagTagInfoVersionService.java
  51. 31 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagTagLogService.java
  52. 113 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/DistributedScheduledTask.java
  53. 159 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagApiLogServiceImpl.java
  54. 2 2
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagAppServiceImpl.java
  55. 23 11
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagCategoryServiceImpl.java
  56. 272 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagInfoServiceImpl.java
  57. 81 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagInfoVersionServiceImpl.java
  58. 204 0
      server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/impl/AitagTagLogServiceImpl.java
  59. 76 0
      server/yusp-tagging-core/src/main/resources/mapper/AitagApiLogMapper.xml
  60. 1 1
      server/yusp-tagging-core/src/main/resources/mapper/AitagTagCategoryMapper.xml
  61. 37 0
      server/yusp-tagging-core/src/main/resources/mapper/AitagTagDailyAggMapper.xml
  62. 75 0
      server/yusp-tagging-core/src/main/resources/mapper/AitagTagInfoMapper.xml
  63. 21 0
      server/yusp-tagging-core/src/main/resources/mapper/AitagTagInfoVersionMapper.xml
  64. 112 0
      server/yusp-tagging-core/src/main/resources/mapper/AitagTagLogMapper.xml
  65. 3 1
      server/yusp-tagging-core/src/main/resources/messages/yusp_input_msg.properties
  66. 7 0
      server/yusp-tagging-starter/pom.xml
  67. 8 0
      server/yusp-tagging-starter/src/main/java/cn/com/yusys/yusp/detail/App.java
  68. 3 0
      server/yusp-tagging-starter/src/main/resources/application.yml
  69. 6 5
      web/.editorconfig
  70. 8 0
      web/.env
  71. 15 0
      web/.env.development
  72. 2 37
      web/.env.production
  73. 0 22
      web/.env.staging
  74. 17 5
      web/.eslintignore
  75. 140 106
      web/.eslintrc.js
  76. 20 27
      web/.gitignore
  77. 10 3
      web/.npmrc
  78. 4 7
      web/.postcssrc.js
  79. 21 0
      web/.prettierignore
  80. 43 0
      web/.prettierrc.js
  81. 16 0
      web/.stylelintignore
  82. 0 5
      web/.travis.yml
  83. 132 0
      web/.xy-tsd.js
  84. 4 3
      web/.yarnrc
  85. 17 0
      web/Dockerfile
  86. 2 39
      web/LICENSE
  87. 0 12
      web/babel.config.js
  88. 94 0
      web/build-proxy.js
  89. 0 36
      web/build/index.js
  90. 60 0
      web/commitlint.config.js
  91. 41 0
      web/default.conf
  92. 29 0
      web/dev.sh
  93. 5 0
      web/dns.sh
  94. 15 0
      web/docker-compose.yml
  95. 0 962
      web/docs.md
  96. 24 0
      web/docs/api/common.md
  97. 36 0
      web/docs/api/component.md
  98. 32 0
      web/docs/api/constant.md
  99. 29 0
      web/docs/api/resp.md
  100. 43 0
      web/docs/api/table.md

+ 21 - 0
server/yusp-tagging-core/pom.xml

@@ -164,7 +164,28 @@
             <version>3.4.5</version> <!-- 最后一个支持 Java 8 的版本 -->
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel-core</artifactId>
+            <version>3.3.4</version>
+            <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动态代理的问题
+
+}

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

@@ -0,0 +1,64 @@
+package cn.com.yusys.yusp.config;
+
+
+/**
+ * 数据字典
+ */
+public class DataDictionary {
+
+    /**
+     * 打标执行中
+     */
+    public static final Integer MARKING_EXECUTING = 0;
+
+    /**
+     * 打标完成
+     */
+    public static final Integer MARKING_COMPLETED = 1;
+
+    /**
+     * 客户经理已经确认
+     */
+    public static final Integer MANAGER_CONFIRMED = 2;
+
+    /**
+     * 结果已推送
+     */
+    public static final Integer RESULT_PUSHED = 3;
+
+    /**
+     * 反馈结果  状态值 agree:同意;reject:拒绝
+     */
+    public static final String FEEDBACK_RESULT_AGREE = "agree";
+
+    public static final String FEEDBACK_RESULT_REJECT = "reject";
+
+
+    /**
+     *标签启用	tag_enabled
+     * 标签停用	tag_disabled
+     * 标签删除	tag_deleted
+     * 标签未删除	tag_undeleted
+     */
+    public static final Integer TAG_ENABLED = 0;
+
+    public static final Integer TAG_DISABLED = 1;
+    public static final Integer TAG_DELETED = 1;
+
+    public static final Integer TAG_UNDELETED = 0;
+
+
+
+    /**
+     *    统计值
+     *        day:日
+     *        week:周
+     *        month:月
+     */
+    public static final String DAY = "day";
+
+    public static final String WEEK = "week";
+
+    public static final String MONTH = "month";
+
+}

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

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

@@ -1,13 +1,15 @@
 package cn.com.yusys.yusp.controller;
 
 import cn.com.yusys.yusp.model.Result;
-import cn.com.yusys.yusp.domain.dto.AitagAppCreateDTO;
+import cn.com.yusys.yusp.domain.dto.AitagAppCreateDto;
 import cn.com.yusys.yusp.domain.entity.AitagApp;
 import cn.com.yusys.yusp.service.AitagAppService;
 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,9 +37,9 @@ public class AitagAppController {
     }
 
 
-
+    @ApiOperationType("新增应用")
     @PostMapping("/add")
-    public Result<AitagApp> addApp(@Validated @RequestBody AitagAppCreateDTO dto) {
+    public Result<AitagApp> addApp(@Validated @RequestBody AitagAppCreateDto dto) {
         try {
             AitagApp app = aiTagAppService.addApp(dto);
             return Result.success(app); // 成功时返回封装后的数据
@@ -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 {

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

@@ -1,15 +1,16 @@
 package cn.com.yusys.yusp.controller;
 
-import cn.com.yusys.yusp.domain.dto.AitagTagCategoryCreateDTO;
-import cn.com.yusys.yusp.domain.dto.AitagTagCategoryUpdateDTO;
+import cn.com.yusys.yusp.domain.dto.AitagTagCategoryCreateDto;
+import cn.com.yusys.yusp.domain.dto.AitagTagCategoryUpdateDto;
 import cn.com.yusys.yusp.domain.entity.AitagTagCategory;
-import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVO;
+import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVo;
 import cn.com.yusys.yusp.model.Result;
 import cn.com.yusys.yusp.service.AitagTagCategoryService;
 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,21 +21,22 @@ public class AitagTagCategoryController {
     @Autowired
     private AitagTagCategoryService aiTagCategoryService;
 
+    @ApiOperationType("标签体系列表")
     @GetMapping("/list")
-    public Result<List<AitagTagCategoryVO>> listCategories(
+    public Result<List<AitagTagCategoryVo>> listCategories(
             @RequestParam(defaultValue = "1") int page,
             @RequestParam(defaultValue = "5") int size) {
         try {
-            Page<AitagTagCategoryVO> pageResult = aiTagCategoryService.listCategories(page, size);
+            Page<AitagTagCategoryVo> pageResult = aiTagCategoryService.listCategories(page, size);
             return Result.pageSuccess(pageResult.getRecords(), pageResult.getTotal());
         } catch (Exception e) {
             return Result.error("500", "分页查询失败:" + e.getMessage());
         }
     }
 
-
+    @ApiOperationType("新增标签体系")
     @PostMapping("/create")
-    public Result<AitagTagCategory> createCategory(@Validated @RequestBody AitagTagCategoryCreateDTO dto) {
+    public Result<AitagTagCategory> createCategory(@Validated @RequestBody AitagTagCategoryCreateDto dto) {
         try {
             AitagTagCategory category = aiTagCategoryService.createCategory(dto);
             return Result.success(category);
@@ -43,8 +45,9 @@ public class AitagTagCategoryController {
         }
     }
 
+    @ApiOperationType("编辑标签体系")
     @PostMapping("/update")
-    public Result<AitagTagCategory> updateCategory(@Validated @RequestBody AitagTagCategoryUpdateDTO dto) {
+    public Result<AitagTagCategory> updateCategory(@Validated @RequestBody AitagTagCategoryUpdateDto dto) {
         try {
             AitagTagCategory category = aiTagCategoryService.updateCategory(dto);
             return Result.success(category);
@@ -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 {

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

@@ -0,0 +1,154 @@
+package cn.com.yusys.yusp.controller;
+
+import cn.com.yusys.yusp.commons.exception.BizException;
+import cn.com.yusys.yusp.commons.module.adapter.web.rest.ResultDto;
+import cn.com.yusys.yusp.domain.dto.TagInfoDto;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity;
+import cn.com.yusys.yusp.domain.vo.AitagTagInfoQueryVo;
+import cn.com.yusys.yusp.domain.vo.TagNodeVo;
+import cn.com.yusys.yusp.domain.vo.VersionRollbackVo;
+import cn.com.yusys.yusp.service.AitagTagInfoService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+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 org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+@Api(tags = "标签管理")
+@RestController
+@RequestMapping("/api/aitagtaginfo")
+public class AitagTagInfoController {
+    /**
+     * AitagTagInfoService
+     */
+    @Autowired
+    private AitagTagInfoService aitagTagInfoService;
+
+
+    /**
+     * 列表查询
+     *
+     * @param aitagTagInfoQueryVo
+     * @return ResultDto
+     */
+    @ApiOperation("列表标签分页信息")
+    @GetMapping("/page")
+    public ResultDto<List<AitagTagInfoEntity>> page(AitagTagInfoQueryVo aitagTagInfoQueryVo) {
+        IPage<AitagTagInfoEntity> page = aitagTagInfoService.queryPage(aitagTagInfoQueryVo);
+        return ResultDto.success(page.getRecords()).total(page.getTotal());
+    }
+
+
+    /**
+     * 列表查询
+     *
+     * @param aitagTagInfoQueryVo
+     * @return ResultDto
+     */
+    @ApiOperation("标签列表查询")
+    @GetMapping("/list")
+    public ResultDto<List<AitagTagInfoEntity>> list(AitagTagInfoQueryVo aitagTagInfoQueryVo) {
+        List<AitagTagInfoEntity> page = aitagTagInfoService.queryList(aitagTagInfoQueryVo);
+        return ResultDto.success(page);
+    }
+
+    /**
+     * 查询标签树
+     *
+     * @param aitagTagInfoQueryVo
+     * @return ResultDto
+     */
+    @ApiOperation("查询标签树")
+    @PostMapping("/queryTagTree")
+    public ResultDto<List<TagNodeVo>> queryTagTree(@RequestBody AitagTagInfoQueryVo aitagTagInfoQueryVo) {
+        if(StringUtils.isBlank(aitagTagInfoQueryVo.getCategoryId())){
+           throw BizException.of("E001");
+        }
+        List<TagNodeVo> tagNodeVos = aitagTagInfoService.queryTagTree(aitagTagInfoQueryVo);
+        return ResultDto.success(tagNodeVos);
+    }
+
+    /**
+     * 详细
+     *
+     * @param id
+     * @return ResultDto
+     */
+    @ApiOperation("详细")
+    @GetMapping("/info/{id}")
+    public ResultDto<TagInfoDto> info(@PathVariable("id") String id) {
+        TagInfoDto aitagTagInfo = aitagTagInfoService.getTagInfo(id);
+        return ResultDto.success(aitagTagInfo);
+    }
+
+    /**
+     * 保存
+     *
+     * @param aitagTagInfoEntity
+     * @return ResultDto
+     */
+    @ApiOperation("保存")
+    @PostMapping("/save")
+    public ResultDto save(@RequestBody AitagTagInfoEntity aitagTagInfoEntity) {
+        aitagTagInfoService.saveTag(aitagTagInfoEntity);
+        return ResultDto.success();
+    }
+
+    /**
+     * 修改
+     *
+     * @param aitagTagInfoEntity
+     * @return ResultDto
+     */
+    @ApiOperation("修改")
+    @PostMapping("/update")
+    public ResultDto update(@RequestBody AitagTagInfoEntity aitagTagInfoEntity) {
+        aitagTagInfoService.updateTag(aitagTagInfoEntity);
+        return ResultDto.success();
+    }
+
+    /**
+     * 修改
+     *
+     * @param versionRollbackVo
+     * @return ResultDto
+     */
+    @ApiOperation("版本回溯")
+    @PostMapping("/versionRollback")
+    public ResultDto versionRollback(@RequestBody VersionRollbackVo versionRollbackVo) {
+        aitagTagInfoService.versionRollback(versionRollbackVo);
+        return ResultDto.success();
+    }
+
+
+    /**
+     * 删除
+     *
+     * @param ids
+     * @return ResultDto
+     */
+    @ApiOperation("删除")
+    @PostMapping("/delete")
+    public ResultDto delete(@RequestBody Map<String,List> ids) {
+        List<String> idsList = ids.get("ids");
+        aitagTagInfoService.removeTags(idsList);
+        return ResultDto.success();
+    }
+
+}

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

@@ -0,0 +1,107 @@
+package cn.com.yusys.yusp.controller;
+
+import cn.com.yusys.yusp.commons.module.adapter.web.rest.ResultDto;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoVersionEntity;
+import cn.com.yusys.yusp.domain.vo.AitagTagInfoVersionQueryVo;
+import cn.com.yusys.yusp.service.AitagTagInfoVersionService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+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 org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 标签信息版本表
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+@Api(tags = "标签信息版本表")
+@RestController
+@RequestMapping("/api/aitagtaginfoversion")
+public class AitagTagInfoVersionController {
+    /**
+     * AitagTagInfoVersionService
+     */
+    @Autowired
+    private AitagTagInfoVersionService aitagTagInfoVersionService;
+
+
+    /**
+     * 列表查询
+     *
+     * @param aitagTagInfoVersionQueryVo
+     * @return ResultDto
+     */
+    @ApiOperation("列表查询")
+    @GetMapping("/list")
+    public ResultDto<List<AitagTagInfoVersionEntity>> list(AitagTagInfoVersionQueryVo aitagTagInfoVersionQueryVo) {
+        IPage<AitagTagInfoVersionEntity> page = aitagTagInfoVersionService.queryPage(aitagTagInfoVersionQueryVo);
+
+        return ResultDto.success(page.getRecords()).total(page.getTotal());
+    }
+
+    /**
+     * 详细
+     *
+     * @param id
+     * @return ResultDto
+     */
+    @ApiOperation("详细")
+    @GetMapping("/info/{id}")
+    public ResultDto<AitagTagInfoVersionEntity> info(@PathVariable("id") String id) {
+        AitagTagInfoVersionEntity aitagTagInfoVersion = aitagTagInfoVersionService.getById(id);
+
+        return ResultDto.success(aitagTagInfoVersion);
+    }
+
+    /**
+     * 保存
+     *
+     * @param aitagTagInfoVersionEntity
+     * @return ResultDto
+     */
+    @ApiOperation("保存")
+    @PostMapping("/save")
+    public ResultDto save(@RequestBody AitagTagInfoVersionEntity aitagTagInfoVersionEntity) {
+        aitagTagInfoVersionService.save(aitagTagInfoVersionEntity);
+        return ResultDto.success();
+    }
+
+    /**
+     * 修改
+     *
+     * @param aitagTagInfoVersionEntity
+     * @return ResultDto
+     */
+    @ApiOperation("修改")
+    @PostMapping("/update")
+    public ResultDto update(@RequestBody AitagTagInfoVersionEntity aitagTagInfoVersionEntity) {
+        aitagTagInfoVersionService.updateById(aitagTagInfoVersionEntity);
+
+        return ResultDto.success();
+    }
+
+    /**
+     * 删除
+     *
+     * @param ids
+     * @return ResultDto
+     */
+    @ApiOperation("删除")
+    @PostMapping("/delete")
+    public ResultDto delete(@RequestBody String[] ids) {
+        aitagTagInfoVersionService.removeByIds(Arrays.asList(ids));
+
+        return ResultDto.success();
+    }
+
+}

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

@@ -0,0 +1,164 @@
+package cn.com.yusys.yusp.controller;
+
+import cn.com.yusys.yusp.commons.module.adapter.web.rest.ResultDto;
+import cn.com.yusys.yusp.domain.entity.AitagTagLogEntity;
+import cn.com.yusys.yusp.domain.vo.*;
+import cn.com.yusys.yusp.service.AitagTagLogService;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cn.com.yusys.yusp.config.DataDictionary.FEEDBACK_RESULT_REJECT;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-25 14:56:45
+ */
+@Api(tags = "数据概览")
+@RestController
+@RequestMapping("/api/aitagtaglog")
+public class AitagTagLogController {
+    /**
+     * AitagTagLogService
+     */
+    @Autowired
+    private AitagTagLogService aitagTagLogService;
+
+
+    /**
+     * 我的配置
+     *
+     * @param taggingResult 智能打标结果
+     * @return ResultDto
+     */
+    @ApiOperation("我的配置")
+    @PostMapping("/dataOverview")
+    public ResultDto<DataOverviewVo> dataOverview(@RequestBody SmartTaggingResultVo taggingResult) {
+        DataOverviewVo dataOverviewDTO = aitagTagLogService.dataOverview(taggingResult);
+        return ResultDto.success(dataOverviewDTO);
+    }
+
+
+
+    /**
+     * 打标趋势
+     *
+     * @param taggingTrendReq 打标趋势
+     * @return ResultDto
+     */
+    @ApiOperation("打标趋势")
+    @PostMapping("/taggingTrend")
+    public ResultDto<List<IconResVo>> taggingTrend(@RequestBody TaggingTrendReqVo taggingTrendReq) {
+        List<IconResVo> taggingTrendRes = aitagTagLogService.taggingTrend(taggingTrendReq);
+        return ResultDto.success(taggingTrendRes);
+    }
+
+    /**
+     * 标签分布统计
+     *
+     * @param resultDTO 智能打标结果
+     * @return ResultDto
+     */
+    @ApiOperation("标签分布统计")
+    @PostMapping("/tagDistStats")
+    public ResultDto<List<IconResVo>> tagDistStats(@RequestBody TagDistStatsReqVo resultDTO) {
+        List<IconResVo> taggingTrendRes = aitagTagLogService.tagDistStats(resultDTO);
+        return ResultDto.success(taggingTrendRes);
+    }
+
+
+    /**
+     * 打标明细
+     *
+     * @param transactionReqVo
+     * @return ResultDto
+     */
+    @ApiOperation("打标明细")
+    @PostMapping("/taggingTransaction")
+    public ResultDto<IPage<AitagTagLogEntity>> taggingTransaction(@RequestBody TaggingTransactionReqVo transactionReqVo) {
+        IPage<AitagTagLogEntity> page = aitagTagLogService.taggingDetails(transactionReqVo);
+        return ResultDto.success(page.getRecords()).total(page.getTotal());
+    }
+
+    /**
+     * 打标结果详情
+     *
+     * @param id
+     * @return ResultDto
+     */
+    @ApiOperation("打标结果详情")
+    @GetMapping("/show/{id}")
+    public ResultDto<AitagTagLogEntity> show(@PathVariable("id") String id) {
+        AitagTagLogEntity taggingDetailsResDTO = aitagTagLogService.show(id);
+        return ResultDto.success(taggingDetailsResDTO);
+    }
+
+    /**
+     * 导出数据
+     *
+     * @param transactionReqVo
+     * @return ResultDto
+     */
+    @ApiOperation("导出数据")
+    @PostMapping("/exportData")
+    public void exportData(@RequestBody TaggingTransactionReqVo transactionReqVo,HttpServletResponse response) throws IOException {
+        List<AitagTagLogEntity> aitagTagLogEntities = aitagTagLogService.queryList(transactionReqVo);
+        List<ExportDataVo> exportDataVos = new ArrayList<>();
+        for (AitagTagLogEntity aitagTagLog:aitagTagLogEntities) {
+            ExportDataVo exportDataVo = JSONObject.parseObject(JSONObject.toJSONString(aitagTagLog), ExportDataVo.class);
+            if(FEEDBACK_RESULT_REJECT.equals(aitagTagLog.getFeedback())){
+                String feedbackResult = aitagTagLog.getFeedbackResult();
+                exportDataVo.setResult(getTags(feedbackResult));
+            }else{
+                String result = aitagTagLog.getResult();
+                exportDataVo.setResult(getTags(result));
+            }
+            exportDataVos.add(exportDataVo);
+        }
+        // 2. 设置响应头(防止中文乱码)
+        response.reset();
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        String fileName = URLEncoder.encode("导出标签", "UTF-8").replaceAll("\\+", "%20");
+        response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + fileName + ".xlsx");
+        EasyExcel.write(response.getOutputStream(), ExportDataVo.class)
+                .sheet("标签数据")
+                .doWrite(exportDataVos);
+    }
+
+
+    private String getTags(String result) {
+        if(StringUtils.isBlank(result)){
+            return "";
+        }
+        List<Map> results = JSONArray.parseArray(result, Map.class);
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0 ;i<results.size();i++){
+            Map resultMap = results.get(i);
+            String tagName = resultMap.getOrDefault("tag_name","").toString();
+            stringBuilder.append(tagName);
+            if(i < (results.size()-1) ){
+                stringBuilder.append(",");
+            }
+        }
+        return stringBuilder.toString();
+    }
+
+
+}

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

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

@@ -1,16 +0,0 @@
-package cn.com.yusys.yusp.domain.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-
-@Data
-@ApiModel("新增应用请求DTO")
-public class AitagAppCreateDTO {
-
-    @NotBlank(message = "应用名称不能为空")
-    @ApiModelProperty(value = "应用名称", required = true)
-    private String appName;
-}

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

@@ -0,0 +1,16 @@
+package cn.com.yusys.yusp.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+@ApiModel("新增应用请求DTO")
+public class AitagAppCreateDto {
+
+    @NotBlank(message = "应用名称不能为空")
+    @ApiModelProperty(value = "应用名称", required = true)
+    private String appName;
+}

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

@@ -1,19 +0,0 @@
-package cn.com.yusys.yusp.domain.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-
-@Data
-@ApiModel("新建标签体系请求DTO")
-public class AitagTagCategoryCreateDTO {
-
-    @NotBlank(message = "标签体系名称不能为空")
-    @ApiModelProperty(value = "标签体系名称", required = true)
-    private String categoryNm;
-
-    @ApiModelProperty(value = "标签体系描述")
-    private String categoryDesc;
-}

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

@@ -0,0 +1,19 @@
+package cn.com.yusys.yusp.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+@ApiModel("新建标签体系请求DTO")
+public class AitagTagCategoryCreateDto {
+
+    @NotBlank(message = "标签体系名称不能为空")
+    @ApiModelProperty(value = "标签体系名称", required = true)
+    private String categoryNm;
+
+    @ApiModelProperty(value = "标签体系描述")
+    private String categoryDesc;
+}

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

@@ -1,23 +0,0 @@
-package cn.com.yusys.yusp.domain.dto;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-
-@Data
-@ApiModel("编辑标签体系请求DTO")
-public class AitagTagCategoryUpdateDTO {
-
-    @NotBlank(message = "ID不能为空")
-    @ApiModelProperty(value = "标签体系ID", required = true)
-    private String id;
-
-    @NotBlank(message = "标签体系名称不能为空")
-    @ApiModelProperty(value = "标签体系名称", required = true)
-    private String categoryNm;
-
-    @ApiModelProperty(value = "标签体系描述")
-    private String categoryDesc;
-}

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

@@ -0,0 +1,23 @@
+package cn.com.yusys.yusp.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+@ApiModel("编辑标签体系请求DTO")
+public class AitagTagCategoryUpdateDto {
+
+    @NotBlank(message = "ID不能为空")
+    @ApiModelProperty(value = "标签体系ID", required = true)
+    private String id;
+
+    @NotBlank(message = "标签体系名称不能为空")
+    @ApiModelProperty(value = "标签体系名称", required = true)
+    private String categoryNm;
+
+    @ApiModelProperty(value = "标签体系描述")
+    private String categoryDesc;
+}

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

@@ -0,0 +1,114 @@
+package cn.com.yusys.yusp.domain.dto;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+
+/**
+ * 
+ *标签详情信息
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+@Data
+public class TagInfoDto {
+
+    /**
+     * id
+     **/
+    @ApiModelProperty(value="id")
+    private String id;
+
+    /**
+     * 所属大类
+     **/
+    @ApiModelProperty(value = "所属大类")
+    private String categoryId;
+
+    /**
+     * 标签名称
+     **/
+    @ApiModelProperty(value = "标签名称")
+    private String tagNm;
+
+    /**
+     * 标签代码
+     **/
+    @ApiModelProperty(value = "标签代码")
+    private String tagCode;
+
+    /**
+     * 标签备注
+     **/
+    @ApiModelProperty(value = "标签备注")
+    private String tagRemark;
+
+    /**
+     * 父级ID
+     **/
+    @ApiModelProperty(value = "父级ID")
+    private String parentId;
+
+    /**
+     * 标签规则
+     **/
+    @ApiModelProperty(value = "标签规则")
+    private String reg;
+
+    /**
+     * 标签等级
+     **/
+    @ApiModelProperty(value = "标签等级")
+    private Integer level;
+
+    /**
+     * tag1/tag2/tag3/...
+     **/
+    @ApiModelProperty(value = "tag1/tag2/tag3/...")
+    private String tagPath;
+
+
+    /**
+     * 标签提示词
+     **/
+    @ApiModelProperty(value = "标签提示词")
+    private String tagPrompt;
+
+
+    /**
+     * 标签版本
+     **/
+    @ApiModelProperty(value = "标签版本")
+    private String tagVersion;
+
+    /**
+     * 修订时间
+     **/
+    @ApiModelProperty(value = "修订时间")
+    private String revisionTime;
+
+    /**
+     * 修订人
+     **/
+    @ApiModelProperty(value = "修订人")
+    private String reviser;
+
+
+    /**
+     * 标签历史版本信息
+     */
+    @ApiModelProperty(value = "标签历史版本信息")
+    private List<TagInfoVersionDto> tagInfoVersionDtos;
+
+
+    public String getTagVersionShow() {
+        return "版本V"+tagVersion;
+    }
+
+}

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

@@ -0,0 +1,114 @@
+package cn.com.yusys.yusp.domain.dto;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+
+/**
+ * 标签信息版本详情信息
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+@Data
+public class TagInfoVersionDto {
+
+    /**
+     * id
+     **/
+    @ApiModelProperty(value="id")
+    @TableId(type=IdType.UUID)
+    private String id;
+
+    /**
+     * 所属大类
+     **/
+    @ApiModelProperty(value = "所属大类")
+    private String categoryId;
+
+    /**
+     * 标签名称
+     **/
+    @ApiModelProperty(value = "标签名称")
+    private String tagNm;
+
+    /**
+     * 标签代码
+     **/
+    @ApiModelProperty(value = "标签代码")
+    private String tagCode;
+
+    /**
+     * 标签备注
+     **/
+    @ApiModelProperty(value = "标签备注")
+    private String tagRemark;
+
+    /**
+     * 父级ID
+     **/
+    @ApiModelProperty(value = "父级ID")
+    private String parentId;
+
+    /**
+     * 标签规则
+     **/
+    @ApiModelProperty(value = "标签规则")
+    private String reg;
+
+    /**
+     * 标签等级
+     **/
+    @ApiModelProperty(value = "标签等级")
+    private Integer level;
+
+    /**
+     * tag1/tag2/tag3/...
+     **/
+    @ApiModelProperty(value = "tag1/tag2/tag3/...")
+    private String tagPath;
+
+    /**
+     * 0未删除;1删除
+     **/
+    @ApiModelProperty(value = "0未删除;1删除")
+    private Integer isDelete;
+
+    /**
+     * 0 正常;1 停用
+     **/
+    @ApiModelProperty(value = "0 正常;1 停用")
+    private Integer state;
+
+    /**
+     * 标签提示词
+     **/
+    @ApiModelProperty(value = "标签提示词")
+    private String tagPrompt;
+
+    /**
+     * 标签版本
+     **/
+    @ApiModelProperty(value = "标签版本")
+    private String tagVersion;
+
+    /**
+     * 修订时间
+     **/
+    @ApiModelProperty(value = "修订时间")
+    private String revisionTime;
+
+    /**
+     * 修订人
+     **/
+    @ApiModelProperty(value = "修订人")
+    private String reviser;
+
+    public String getTagVersionShow() {
+        return "版本V"+tagVersion;
+    }
+}

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

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

@@ -0,0 +1,55 @@
+package cn.com.yusys.yusp.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.IdType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+
+/**
+ * 智能标签按天汇总信息
+ *
+ * @author 2507040827
+ * @date 2026-02-26 11:07:40
+ */
+@TableName("aitag_tag_daily_agg")
+@Data
+@ApiModel(value = "AitagTagDailyAggEntity", description = "智能标签按天汇总信息")
+public class AitagTagDailyAggEntity {
+
+    /**
+     * id
+     **/
+    @ApiModelProperty(value="id")
+    @TableId(type=IdType.UUID)
+    private String id;
+
+    /**
+     * 汇总日期
+     **/
+    @ApiModelProperty(value = "汇总日期")
+    private String aggDate;
+
+    /**
+     * 类别名称
+     **/
+    @ApiModelProperty(value = "标签类别")
+    private String categoryCode;
+
+    /**
+     * 标签名称
+     **/
+    @ApiModelProperty(value = "标签名称")
+    private String tagNm;
+
+    /**
+     * 统计梳理
+     **/
+    @ApiModelProperty(value = "统计梳理")
+    private Integer tagCount;
+
+
+
+}

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

@@ -1,99 +0,0 @@
-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 io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-import org.hibernate.validator.constraints.Length;
-
-/**
-* 
-* @TableName aitag_tag_info
-*/
-@Data
-public class AitagTagInfo 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 categoryId;
-    /**
-    * 标签名称
-    */
-    @Size(max= 100,message="编码长度不能超过100")
-    @ApiModelProperty("标签名称")
-    @Length(max= 100,message="编码长度不能超过100")
-    private String tagNm;
-    /**
-    * 标签代码
-    */
-    @Size(max= 100,message="编码长度不能超过100")
-    @ApiModelProperty("标签代码")
-    @Length(max= 100,message="编码长度不能超过100")
-    private String tagCode;
-    /**
-    * 标签备注
-    */
-    @Size(max= 100,message="编码长度不能超过100")
-    @ApiModelProperty("标签备注")
-    @Length(max= 100,message="编码长度不能超过100")
-    private String tagRemark;
-    /**
-    * 父级ID
-    */
-    @Size(max= 100,message="编码长度不能超过100")
-    @ApiModelProperty("父级ID")
-    @Length(max= 100,message="编码长度不能超过100")
-    private String parentCode;
-    /**
-    * 标签规则
-    */
-    @Size(max= 100,message="编码长度不能超过100")
-    @ApiModelProperty("标签规则")
-    @Length(max= 100,message="编码长度不能超过100")
-    private String reg;
-    /**
-    * 标签等级
-    */
-    @ApiModelProperty("标签等级")
-    private Integer level;
-    /**
-    * tag1/tag2/tag3/...
-    */
-    @Size(max= 100,message="编码长度不能超过100")
-    @ApiModelProperty("tag1/tag2/tag3/...")
-    @Length(max= 100,message="编码长度不能超过100")
-    private String tagPath;
-    /**
-    * 0未删除;1删除
-    */
-    @ApiModelProperty("0未删除;1删除")
-    private Integer isDelete;
-    /**
-    * 0 正常;1 停用
-    */
-    @ApiModelProperty("0 正常;1 停用")
-    private Integer state;
-    /**
-    * 标签提示词
-    */
-    @Size(max= 1000,message="编码长度不能超过1000")
-    @ApiModelProperty("标签提示词")
-    @Length(max= 1000,message="编码长度不能超过1,000")
-    private String tagPrompt;
-}

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

@@ -0,0 +1,115 @@
+package cn.com.yusys.yusp.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.IdType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+
+/**
+ * 
+ *标签信息表
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+@TableName("aitag_tag_info")
+@ApiModel(value = "AitagTagInfoEntity", description = "标签信息表")
+@Data
+public class AitagTagInfoEntity {
+
+    /**
+     * id
+     **/
+    @ApiModelProperty(value="id")
+    @TableId(type=IdType.UUID)
+    private String id;
+
+    /**
+     * 所属大类
+     **/
+    @ApiModelProperty(value = "所属大类")
+    private String categoryId;
+
+    /**
+     * 标签名称
+     **/
+    @ApiModelProperty(value = "标签名称")
+    private String tagNm;
+
+    /**
+     * 标签代码
+     **/
+    @ApiModelProperty(value = "标签代码")
+    private String tagCode;
+
+    /**
+     * 标签备注
+     **/
+    @ApiModelProperty(value = "标签备注")
+    private String tagRemark;
+
+    /**
+     * 父级ID
+     **/
+    @ApiModelProperty(value = "父级ID")
+    private String parentId;
+
+    /**
+     * 标签规则
+     **/
+    @ApiModelProperty(value = "标签规则")
+    private String reg;
+
+    /**
+     * 标签等级
+     **/
+    @ApiModelProperty(value = "标签等级")
+    private Integer level;
+
+    /**
+     * tag1/tag2/tag3/...
+     **/
+    @ApiModelProperty(value = "tag1/tag2/tag3/...")
+    private String tagPath;
+
+    /**
+     * 0未删除;1删除
+     **/
+    @ApiModelProperty(value = "0未删除;1删除")
+    private Integer isDelete;
+
+    /**
+     * 0 正常;1 停用
+     **/
+    @ApiModelProperty(value = "0 正常;1 停用")
+    private Integer state;
+
+    /**
+     * 标签提示词
+     **/
+    @ApiModelProperty(value = "标签提示词")
+    private String tagPrompt;
+
+
+    /**
+     * 标签版本
+     **/
+    @ApiModelProperty(value = "标签版本")
+    private String tagVersion;
+
+    /**
+     * 修订时间
+     **/
+    @ApiModelProperty(value = "修订时间")
+    private String revisionTime;
+
+    /**
+     * 修订人
+     **/
+    @ApiModelProperty(value = "修订人")
+    private String reviser;
+
+
+}

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

@@ -0,0 +1,232 @@
+package cn.com.yusys.yusp.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.IdType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+
+/**
+ * 标签信息版本表
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+@TableName("aitag_tag_info_version")
+@ApiModel(value = "AitagTagInfoVersionEntity", description = "标签信息版本表")
+public class AitagTagInfoVersionEntity {
+
+    /**
+     * id
+     **/
+    @ApiModelProperty(value="id")
+    @TableId(type=IdType.UUID)
+    private String id;
+
+    /**
+     * 所属大类
+     **/
+    @ApiModelProperty(value = "所属大类")
+    private String categoryId;
+
+    /**
+     * 标签名称
+     **/
+    @ApiModelProperty(value = "标签名称")
+    private String tagNm;
+
+    /**
+     * 标签代码
+     **/
+    @ApiModelProperty(value = "标签代码")
+    private String tagCode;
+
+    /**
+     * 标签备注
+     **/
+    @ApiModelProperty(value = "标签备注")
+    private String tagRemark;
+
+    /**
+     * 父级ID
+     **/
+    @ApiModelProperty(value = "父级ID")
+    private String parentId;
+
+    /**
+     * 标签规则
+     **/
+    @ApiModelProperty(value = "标签规则")
+    private String reg;
+
+    /**
+     * 标签等级
+     **/
+    @ApiModelProperty(value = "标签等级")
+    private Integer level;
+
+    /**
+     * tag1/tag2/tag3/...
+     **/
+    @ApiModelProperty(value = "tag1/tag2/tag3/...")
+    private String tagPath;
+
+    /**
+     * 0未删除;1删除
+     **/
+    @ApiModelProperty(value = "0未删除;1删除")
+    private Integer isDelete;
+
+    /**
+     * 0 正常;1 停用
+     **/
+    @ApiModelProperty(value = "0 正常;1 停用")
+    private Integer state;
+
+    /**
+     * 标签提示词
+     **/
+    @ApiModelProperty(value = "标签提示词")
+    private String tagPrompt;
+
+    /**
+     * 标签版本
+     **/
+    @ApiModelProperty(value = "标签版本")
+    private String tagVersion;
+
+    /**
+     * 修订时间
+     **/
+    @ApiModelProperty(value = "修订时间")
+    private String revisionTime;
+
+    /**
+     * 修订人
+     **/
+    @ApiModelProperty(value = "修订人")
+    private String reviser;
+
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getCategoryId() {
+        return this.categoryId;
+    }
+
+    public void setCategoryId(String categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public String getTagNm() {
+        return this.tagNm;
+    }
+
+    public void setTagNm(String tagNm) {
+        this.tagNm = tagNm;
+    }
+
+    public String getTagCode() {
+        return this.tagCode;
+    }
+
+    public void setTagCode(String tagCode) {
+        this.tagCode = tagCode;
+    }
+
+    public String getTagRemark() {
+        return this.tagRemark;
+    }
+
+    public void setTagRemark(String tagRemark) {
+        this.tagRemark = tagRemark;
+    }
+
+    public String getparentId() {
+        return this.parentId;
+    }
+
+    public void setparentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getReg() {
+        return this.reg;
+    }
+
+    public void setReg(String reg) {
+        this.reg = reg;
+    }
+
+    public Integer getLevel() {
+        return this.level;
+    }
+
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    public String getTagPath() {
+        return this.tagPath;
+    }
+
+    public void setTagPath(String tagPath) {
+        this.tagPath = tagPath;
+    }
+
+    public Integer getIsDelete() {
+        return this.isDelete;
+    }
+
+    public void setIsDelete(Integer isDelete) {
+        this.isDelete = isDelete;
+    }
+
+    public Integer getState() {
+        return this.state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public String getTagPrompt() {
+        return this.tagPrompt;
+    }
+
+    public void setTagPrompt(String tagPrompt) {
+        this.tagPrompt = tagPrompt;
+    }
+
+    public String getTagVersion() {
+        return this.tagVersion;
+    }
+
+    public void setTagVersion(String tagVersion) {
+        this.tagVersion = tagVersion;
+    }
+
+    public String getRevisionTime() {
+        return this.revisionTime;
+    }
+
+    public void setRevisionTime(String revisionTime) {
+        this.revisionTime = revisionTime;
+    }
+
+    public String getReviser() {
+        return this.reviser;
+    }
+
+    public void setReviser(String reviser) {
+        this.reviser = reviser;
+    }
+
+}

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

@@ -0,0 +1,160 @@
+package cn.com.yusys.yusp.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.IdType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-25 14:56:45
+ */
+
+@Data
+@TableName("aitag_tag_log")
+@ApiModel(value = "AitagTagLogEntity", description = "")
+public class AitagTagLogEntity {
+
+    /**
+     * id
+     **/
+    @ApiModelProperty(value="id")
+    @TableId(type=IdType.UUID)
+    private String id;
+
+    /**
+     * 应用ID
+     **/
+    @ApiModelProperty(value = "应用ID")
+    private String appId;
+
+    /**
+     * 发起时间
+     **/
+    @ApiModelProperty(value = "发起时间")
+    private LocalDateTime insertTime;
+
+    /**
+     * 业务属性,贷款编号
+     **/
+    @ApiModelProperty(value = "业务属性,贷款编号")
+    private String businessAttr;
+
+    /**
+     * 输入短语
+     **/
+    @ApiModelProperty(value = "输入短语")
+    private String phrase;
+
+    /**
+     * 附件名称
+     **/
+    @ApiModelProperty(value = "附件名称")
+    private String attachment;
+
+    /**
+     * 附件路径
+     **/
+    @ApiModelProperty(value = "附件路径")
+    private String attachmentUrl;
+
+    /**
+     * 打标结果,JSON
+[{
+label:xxx,
+label_code:xxx,
+desc:xxx
+passr: true/false
+}]
+     **/
+    @ApiModelProperty(value = "打标结果,JSON [{ label:xxx, label_code:xxx, desc:xxx passr: true/false }]")
+    private String result;
+
+    /**
+     * 反馈人ID
+     **/
+    @ApiModelProperty(value = "反馈人ID")
+    private String feedbackUserId;
+
+    /**
+     * 反馈人名字
+     **/
+    @ApiModelProperty(value = "反馈人名字")
+    private String feedbackUserNm;
+
+    /**
+     * 反馈时间
+     **/
+    @ApiModelProperty(value = "反馈时间")
+    private LocalDateTime feedbackTime;
+
+    /**
+     * 反馈,agree/reject
+     **/
+    @ApiModelProperty(value = "反馈,agree/reject")
+    private String feedback;
+
+    /**
+     * JSON
+[
+{
+ label:xxx,
+ label_code:xxx,
+ desc:xxx
+ passr: true/false
+}
+]
+     **/
+    @ApiModelProperty(value = "JSON [ { label:xxx, label_code:xxx, desc:xxx passr: true/false } ]")
+    private String feedbackResult;
+
+    /**
+     * 0:打标执行中;1:打标完成; 2:客户经理已经确认;3,结果已推送
+     **/
+    @ApiModelProperty(value = "0:打标执行中;1:打标完成; 2:客户经理已经确认;3,结果已推送")
+    private Integer state;
+
+    /**
+     * 打标耗时,秒
+     **/
+    @ApiModelProperty(value = "打标耗时,秒")
+    private String consumingTime;
+
+    /**
+     * 标签类别
+     **/
+    @ApiModelProperty(value = "标签类别")
+    private String categoryCode;
+
+    /**
+     * 合同编号
+     **/
+    @ApiModelProperty(value = "合同编号")
+    private String contractNo;
+
+
+
+    /**
+     * 正则过滤结果
+     **/
+    @ApiModelProperty(value = "正则过滤结果")
+    private String regResult;
+
+    /**
+     * 法人机构
+     **/
+    @ApiModelProperty(value = "法人机构")
+    private String feedbackUserOrg;
+
+    /**
+     * 网点
+     **/
+    @ApiModelProperty(value = "网点")
+    private String feedbackUserEndpoint;
+}

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

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

@@ -1,33 +0,0 @@
-package cn.com.yusys.yusp.domain.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-@Data
-@ApiModel("标签体系视图对象")
-public class AitagTagCategoryVO {
-    @ApiModelProperty("标签体系ID")
-    private String id;
-
-    @ApiModelProperty("类别代码")
-    private String categoryCode;
-
-    @ApiModelProperty("类别名称")
-    private String categoryNm;
-
-    @ApiModelProperty("类别描述")
-    private String categoryDesc;
-
-    @ApiModelProperty("生效级别")
-    private Integer visibilityLevel;
-
-    @ApiModelProperty("状态:0启用;1停用")
-    private Integer state;
-
-    @ApiModelProperty("删除标记:0未删除;1删除")
-    private Integer isDelete;
-
-    @ApiModelProperty("标签数量(临时字段)")
-    private Integer tagNum;
-}

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

@@ -0,0 +1,33 @@
+package cn.com.yusys.yusp.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel("标签体系视图对象")
+public class AitagTagCategoryVo {
+    @ApiModelProperty("标签体系ID")
+    private String id;
+
+    @ApiModelProperty("类别代码")
+    private String categoryCode;
+
+    @ApiModelProperty("类别名称")
+    private String categoryNm;
+
+    @ApiModelProperty("类别描述")
+    private String categoryDesc;
+
+    @ApiModelProperty("生效级别")
+    private Integer visibilityLevel;
+
+    @ApiModelProperty("状态:0启用;1停用")
+    private Integer state;
+
+    @ApiModelProperty("删除标记:0未删除;1删除")
+    private Integer isDelete;
+
+    @ApiModelProperty("标签数量(临时字段)")
+    private Integer tagNum;
+}

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

@@ -0,0 +1,171 @@
+package cn.com.yusys.yusp.domain.vo;
+
+import cn.com.yusys.yusp.commons.module.adapter.query.PageQuery;
+
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+public class AitagTagInfoQueryVo extends PageQuery {
+
+    /**
+     * id
+     **/
+    private String id;
+
+    /**
+     * 所属大类
+     **/
+    private String categoryId;
+
+    /**
+     * 标签名称
+     **/
+    private String tagNm;
+
+    /**
+     * 标签代码
+     **/
+    private String tagCode;
+
+    /**
+     * 标签备注
+     **/
+    private String tagRemark;
+
+    /**
+     * 父级ID
+     **/
+    private String parentId;
+
+    /**
+     * 标签规则
+     **/
+    private String reg;
+
+    /**
+     * 标签等级
+     **/
+    private Integer level;
+
+    /**
+     * tag1/tag2/tag3/...
+     **/
+    private String tagPath;
+
+    /**
+     * 0未删除;1删除
+     **/
+    private Integer isDelete;
+
+    /**
+     * 0 正常;1 停用
+     **/
+    private Integer state;
+
+    /**
+     * 标签提示词
+     **/
+    private String tagPrompt;
+
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getCategoryId() {
+        return this.categoryId;
+    }
+
+    public void setCategoryId(String categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public String getTagNm() {
+        return this.tagNm;
+    }
+
+    public void setTagNm(String tagNm) {
+        this.tagNm = tagNm;
+    }
+
+    public String getTagCode() {
+        return this.tagCode;
+    }
+
+    public void setTagCode(String tagCode) {
+        this.tagCode = tagCode;
+    }
+
+    public String getTagRemark() {
+        return this.tagRemark;
+    }
+
+    public void setTagRemark(String tagRemark) {
+        this.tagRemark = tagRemark;
+    }
+
+    public String getparentId() {
+        return this.parentId;
+    }
+
+    public void setparentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getReg() {
+        return this.reg;
+    }
+
+    public void setReg(String reg) {
+        this.reg = reg;
+    }
+
+    public Integer getLevel() {
+        return this.level;
+    }
+
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    public String getTagPath() {
+        return this.tagPath;
+    }
+
+    public void setTagPath(String tagPath) {
+        this.tagPath = tagPath;
+    }
+
+    public Integer getIsDelete() {
+        return this.isDelete;
+    }
+
+    public void setIsDelete(Integer isDelete) {
+        this.isDelete = isDelete;
+    }
+
+    public Integer getState() {
+        return this.state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public String getTagPrompt() {
+        return this.tagPrompt;
+    }
+
+    public void setTagPrompt(String tagPrompt) {
+        this.tagPrompt = tagPrompt;
+    }
+
+}

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

@@ -0,0 +1,210 @@
+package cn.com.yusys.yusp.domain.vo;
+
+import cn.com.yusys.yusp.commons.module.adapter.query.PageQuery;
+
+
+/**
+ * 标签信息版本表
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+public class AitagTagInfoVersionQueryVo extends PageQuery {
+
+    /**
+     * id
+     **/
+    private String id;
+
+    /**
+     * 所属大类
+     **/
+    private String categoryId;
+
+    /**
+     * 标签名称
+     **/
+    private String tagNm;
+
+    /**
+     * 标签代码
+     **/
+    private String tagCode;
+
+    /**
+     * 标签备注
+     **/
+    private String tagRemark;
+
+    /**
+     * 父级ID
+     **/
+    private String parentId;
+
+    /**
+     * 标签规则
+     **/
+    private String reg;
+
+    /**
+     * 标签等级
+     **/
+    private Integer level;
+
+    /**
+     * tag1/tag2/tag3/...
+     **/
+    private String tagPath;
+
+    /**
+     * 0未删除;1删除
+     **/
+    private Integer isDelete;
+
+    /**
+     * 0 正常;1 停用
+     **/
+    private Integer state;
+
+    /**
+     * 标签提示词
+     **/
+    private String tagPrompt;
+
+    /**
+     * 标签版本
+     **/
+    private String tagVersion;
+
+    /**
+     * 修订时间
+     **/
+    private String revisionTime;
+
+    /**
+     * 修订人
+     **/
+    private String reviser;
+
+
+    public String getId() {
+        return this.id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getCategoryId() {
+        return this.categoryId;
+    }
+
+    public void setCategoryId(String categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public String getTagNm() {
+        return this.tagNm;
+    }
+
+    public void setTagNm(String tagNm) {
+        this.tagNm = tagNm;
+    }
+
+    public String getTagCode() {
+        return this.tagCode;
+    }
+
+    public void setTagCode(String tagCode) {
+        this.tagCode = tagCode;
+    }
+
+    public String getTagRemark() {
+        return this.tagRemark;
+    }
+
+    public void setTagRemark(String tagRemark) {
+        this.tagRemark = tagRemark;
+    }
+
+    public String getparentId() {
+        return this.parentId;
+    }
+
+    public void setparentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getReg() {
+        return this.reg;
+    }
+
+    public void setReg(String reg) {
+        this.reg = reg;
+    }
+
+    public Integer getLevel() {
+        return this.level;
+    }
+
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    public String getTagPath() {
+        return this.tagPath;
+    }
+
+    public void setTagPath(String tagPath) {
+        this.tagPath = tagPath;
+    }
+
+    public Integer getIsDelete() {
+        return this.isDelete;
+    }
+
+    public void setIsDelete(Integer isDelete) {
+        this.isDelete = isDelete;
+    }
+
+    public Integer getState() {
+        return this.state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public String getTagPrompt() {
+        return this.tagPrompt;
+    }
+
+    public void setTagPrompt(String tagPrompt) {
+        this.tagPrompt = tagPrompt;
+    }
+
+    public String getTagVersion() {
+        return this.tagVersion;
+    }
+
+    public void setTagVersion(String tagVersion) {
+        this.tagVersion = tagVersion;
+    }
+
+    public String getRevisionTime() {
+        return this.revisionTime;
+    }
+
+    public void setRevisionTime(String revisionTime) {
+        this.revisionTime = revisionTime;
+    }
+
+    public String getReviser() {
+        return this.reviser;
+    }
+
+    public void setReviser(String reviser) {
+        this.reviser = reviser;
+    }
+
+}

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

@@ -0,0 +1,44 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import lombok.Data;
+
+/**
+ * 准确标签数返回对象
+ */
+
+@Data
+public class DataOverviewVo {
+
+    /**
+     * 总打标数
+     */
+    private Integer countNum;
+
+
+    /**
+     * 准确的标签数
+     */
+    private Integer accurateNum;
+
+    /**
+     * 准确率
+     */
+    private String accurateRate;
+
+    /**
+     * 人工调整数
+     */
+    private Integer manualAdjustCount;
+
+    /**
+     * 调整率
+     */
+    private String manualAdjustRate;
+
+
+    /**
+     * 打标未确认
+     */
+    private Integer taggedUnconfirmed;
+}

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

@@ -0,0 +1,58 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class ExportDataVo {
+
+
+    /**
+     * 合同编号
+     **/
+    @ExcelProperty(value = "合同编号" ,index = 0)
+    private String contractNo;
+
+
+    /**
+     * 客户经理
+     **/
+    @ExcelProperty(value = "客户经理" ,index = 1)
+    private String feedbackUserNm;
+
+
+    /**
+     * 客户经理
+     **/
+    @ExcelProperty(value = "法人机构" ,index = 2)
+    private String feedbackUserOrg;
+
+
+    /**
+     * 客户经理
+     **/
+    @ExcelProperty(value = "网点" ,index = 3)
+    private String feedbackUserEndpoint;
+
+
+
+    /**
+     * 打标结果,JSON
+     **/
+    @ExcelProperty(value = "智能标签" ,index = 4)
+    private String result;
+
+
+    /**
+     * 发起时间
+     **/
+    @ExcelProperty(value = "标注时间" ,index = 5)
+    private String insertTime;
+
+    public void setInsertTime(LocalDateTime insertTime) {
+        this.insertTime = insertTime.toString();
+    }
+}

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

@@ -0,0 +1,24 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import lombok.Data;
+
+/**
+ * 打标趋势 返回参数
+ */
+
+@Data
+public class IconResVo {
+
+    /**
+     * 字段stat值
+     */
+    private String stat;
+
+    /**
+     * 字段Value值
+     */
+    private String val;
+
+
+}

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

@@ -0,0 +1,32 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import cn.com.yusys.yusp.commons.module.adapter.query.PageQuery;
+import lombok.Data;
+
+/**
+ * 智能打标结果
+ * @author zhangkeyu
+ */
+
+@Data
+public class SmartTaggingResultVo extends PageQuery {
+
+
+    /**
+     * 打标开始时间
+     */
+    private String startTaggingTime;
+
+
+    /**
+     * 打标结束时间
+     */
+    private String endTaggingTime;
+
+
+    /**
+     * 标签类别
+     */
+    private String categoryCode;
+}

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

@@ -0,0 +1,10 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import lombok.Data;
+
+@Data
+public class TagDistStatsReqVo extends SmartTaggingResultVo {
+
+    private String sort = "desc";
+}

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

@@ -0,0 +1,74 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 智能标签数
+ */
+public class TagNodeVo {
+
+
+    private String id;
+
+    private String parentId;
+    private String tagNm;
+    private String tagCode;
+    private List<TagNodeVo> children = new ArrayList<>();
+
+
+    public TagNodeVo(AitagTagInfoEntity entity) {
+        this.id = entity.getId();
+        this.parentId = entity.getParentId();
+        this.tagNm = entity.getTagNm();
+        this.tagCode = entity.getTagCode();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getparentId() {
+        return parentId;
+    }
+
+    public void setparentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public String getTagNm() {
+        return tagNm;
+    }
+
+    public void setTagNm(String tagNm) {
+        this.tagNm = tagNm;
+    }
+
+    public String getTagCode() {
+        return tagCode;
+    }
+
+    public void setTagCode(String tagCode) {
+        this.tagCode = tagCode;
+    }
+
+
+    public List<TagNodeVo> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<TagNodeVo> children) {
+        this.children = children;
+    }
+
+    public void addChild(TagNodeVo child) {
+        this.children.add(child);
+    }
+}

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

@@ -0,0 +1,23 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import lombok.Data;
+
+/**
+ * 打标明细请求对象
+ */
+
+@Data
+public class TaggingTransactionReqVo extends SmartTaggingResultVo {
+
+    /**
+     * 贷款申请编号
+     */
+    private String loanApplicationNo;
+
+    /**
+     * 合同编号
+     */
+    private String contractNo;
+
+}

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

@@ -0,0 +1,21 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import lombok.Data;
+
+
+/**
+ * 打标趋势 请求参数
+ */
+@Data
+public class TaggingTrendReqVo extends SmartTaggingResultVo {
+
+
+    /**
+     *     统计值
+     *     day:日
+     *     week:周
+     *     month:月
+     */
+    private String statisticalPeriod;
+}

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

@@ -0,0 +1,35 @@
+package cn.com.yusys.yusp.domain.vo;
+
+
+import lombok.Data;
+
+/**
+ * 版本回溯
+ */
+
+@Data
+public class VersionRollbackVo {
+
+
+
+    /**
+     * 标签主键
+     */
+    private String id;
+
+    /**
+     * 标签代码
+     */
+    private String tagCode;
+
+
+    /**
+     * 要回溯标签版本
+     */
+    private String version;
+
+    /**
+     * 修订人
+     */
+    private String reviser;
+}

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

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

@@ -0,0 +1,24 @@
+package cn.com.yusys.yusp.mapper;
+
+import cn.com.yusys.yusp.commons.mybatisplus.mapper.BaseMapper;
+import cn.com.yusys.yusp.domain.vo.IconResVo;
+import cn.com.yusys.yusp.domain.entity.AitagTagDailyAggEntity;
+import cn.com.yusys.yusp.domain.vo.TagDistStatsReqVo;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+
+/**
+ * 智能标签按天汇总信息
+ *
+ * @author 2507040827
+ * @date 2026-02-26 11:07:40
+ */
+
+@Mapper
+public interface AitagTagDailyAggDao extends BaseMapper<AitagTagDailyAggEntity> {
+
+    List<IconResVo> selectTagDistStats(TagDistStatsReqVo taggingResult);
+
+}

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

@@ -0,0 +1,24 @@
+package cn.com.yusys.yusp.mapper;
+
+import cn.com.yusys.yusp.commons.mybatisplus.mapper.BaseMapper;
+import cn.com.yusys.yusp.domain.dto.TagInfoDto;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+
+@Mapper
+public interface AitagTagInfoDao extends BaseMapper<AitagTagInfoEntity> {
+
+
+   TagInfoDto selectTagInfo(@Param("id") String id);
+
+}

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

@@ -0,0 +1,17 @@
+package cn.com.yusys.yusp.mapper;
+
+import cn.com.yusys.yusp.commons.mybatisplus.mapper.BaseMapper;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoVersionEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 标签信息版本表
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+
+@Mapper
+public interface AitagTagInfoVersionDao extends BaseMapper<AitagTagInfoVersionEntity> {
+
+}

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

@@ -0,0 +1,34 @@
+package cn.com.yusys.yusp.mapper;
+
+import cn.com.yusys.yusp.commons.mybatisplus.mapper.BaseMapper;
+import cn.com.yusys.yusp.domain.vo.IconResVo;
+import cn.com.yusys.yusp.domain.vo.SmartTaggingResultVo;
+import cn.com.yusys.yusp.domain.entity.AitagTagLogEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-25 14:56:45
+ */
+
+@Mapper
+public interface AitagTagLogDao extends BaseMapper<AitagTagLogEntity> {
+
+    Integer selectTagCount(SmartTaggingResultVo taggingResult);
+
+
+    List<IconResVo> selectTagReportByDay(SmartTaggingResultVo taggingResult);
+
+
+    List<IconResVo> selectTagReportByMonth(SmartTaggingResultVo taggingResult);
+
+
+    List<IconResVo> selectTagReportByWeek(SmartTaggingResultVo taggingResult);
+
+    List<AitagTagLogEntity> selectByInsertTime(@Param("insertTime") String insertTime);
+}

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

@@ -0,0 +1,43 @@
+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;
+
+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);
+}

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

@@ -1,7 +1,7 @@
 package cn.com.yusys.yusp.service;
 
 import cn.com.yusys.yusp.domain.entity.AitagApp;
-import cn.com.yusys.yusp.domain.dto.AitagAppCreateDTO;
+import cn.com.yusys.yusp.domain.dto.AitagAppCreateDto;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 
 import java.util.List;
@@ -10,7 +10,7 @@ public interface AitagAppService {
     Page<AitagApp> listApps(int page, int size);
     Page<AitagApp> queryByAppNameLikeWithPagination(String appName, int page, int pageSize);
 
-    AitagApp addApp(AitagAppCreateDTO dto);
+    AitagApp addApp(AitagAppCreateDto dto);
     List<AitagApp> queryByName(String appName);
     List<AitagApp> queryByAppNameLike(String appName);
     String resetSecret(String id);

+ 6 - 6
server/yusp-tagging-core/src/main/java/cn/com/yusys/yusp/service/AitagTagCategoryService.java

@@ -1,17 +1,17 @@
 package cn.com.yusys.yusp.service;
 
-import cn.com.yusys.yusp.domain.dto.AitagTagCategoryCreateDTO;
-import cn.com.yusys.yusp.domain.dto.AitagTagCategoryUpdateDTO;
+import cn.com.yusys.yusp.domain.dto.AitagTagCategoryCreateDto;
+import cn.com.yusys.yusp.domain.dto.AitagTagCategoryUpdateDto;
 import cn.com.yusys.yusp.domain.entity.AitagTagCategory;
-import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVO;
+import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVo;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 
 import java.util.List;
 
 public interface AitagTagCategoryService {
-    Page<AitagTagCategoryVO> listCategories(int page, int size); // ← 改为 VO
-    AitagTagCategory createCategory(AitagTagCategoryCreateDTO dto);
-    AitagTagCategory updateCategory(AitagTagCategoryUpdateDTO dto);
+    Page<AitagTagCategoryVo> listCategories(int page, int size); // ← 改为 VO
+    AitagTagCategory createCategory(AitagTagCategoryCreateDto dto);
+    AitagTagCategory updateCategory(AitagTagCategoryUpdateDto dto);
     List<AitagTagCategory> searchByCategoryNm(String categoryNm);
     void enableCategory(String id);
     void disableCategory(String id);

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

@@ -0,0 +1,44 @@
+package cn.com.yusys.yusp.service;
+
+import cn.com.yusys.yusp.domain.dto.TagInfoDto;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity;
+import cn.com.yusys.yusp.domain.vo.AitagTagInfoQueryVo;
+import cn.com.yusys.yusp.domain.vo.TagNodeVo;
+import cn.com.yusys.yusp.domain.vo.VersionRollbackVo;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+
+public interface AitagTagInfoService extends IService<AitagTagInfoEntity> {
+
+    /**
+     * 分页查询
+     *
+     * @param aitagTagInfoQueryVo
+     * @return IPage
+     */
+    IPage<AitagTagInfoEntity> queryPage(AitagTagInfoQueryVo aitagTagInfoQueryVo);
+
+    void saveTag(AitagTagInfoEntity aitagTagInfo);
+
+    List<TagNodeVo> queryTagTree(AitagTagInfoQueryVo aitagTagInfoQueryVo);
+
+    TagInfoDto getTagInfo(String id);
+
+    void updateTag(AitagTagInfoEntity aitagTagInfoEntity);
+
+    void removeTags(List<String> list);
+
+    void versionRollback(VersionRollbackVo versionRollbackVo);
+
+    List<AitagTagInfoEntity> queryList(AitagTagInfoQueryVo aitagTagInfoQueryVo);
+}
+

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

@@ -0,0 +1,25 @@
+package cn.com.yusys.yusp.service;
+
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoVersionEntity;
+import cn.com.yusys.yusp.domain.vo.AitagTagInfoVersionQueryVo;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * 标签信息版本表
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+
+public interface AitagTagInfoVersionService extends IService<AitagTagInfoVersionEntity> {
+
+    /**
+     * 分页查询
+     *
+     * @param aitagTagInfoVersionQueryVo
+     * @return IPage
+     */
+    IPage<AitagTagInfoVersionEntity> queryPage(AitagTagInfoVersionQueryVo aitagTagInfoVersionQueryVo);
+}
+

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

@@ -0,0 +1,31 @@
+package cn.com.yusys.yusp.service;
+
+import cn.com.yusys.yusp.domain.entity.AitagTagLogEntity;
+import cn.com.yusys.yusp.domain.vo.*;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-25 14:56:45
+ */
+
+public interface AitagTagLogService extends IService<AitagTagLogEntity> {
+
+    DataOverviewVo dataOverview(SmartTaggingResultVo markingResult);
+
+    List<IconResVo> taggingTrend(TaggingTrendReqVo TaggingTrendReqVo);
+
+    IPage<AitagTagLogEntity> taggingDetails(TaggingTransactionReqVo aitagTagLogEntity);
+
+    AitagTagLogEntity show(String id);
+
+    List<IconResVo> tagDistStats(TagDistStatsReqVo resultDTO);
+
+    List<AitagTagLogEntity> queryList(TaggingTransactionReqVo transactionReqVo);
+}
+

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

@@ -0,0 +1,113 @@
+package cn.com.yusys.yusp.service;
+
+
+import cn.com.yusys.yusp.commons.util.StringUtils;
+import cn.com.yusys.yusp.domain.entity.AitagTagDailyAggEntity;
+import cn.com.yusys.yusp.domain.entity.AitagTagLogEntity;
+import cn.com.yusys.yusp.mapper.AitagTagDailyAggDao;
+import cn.com.yusys.yusp.mapper.AitagTagLogDao;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import static cn.com.yusys.yusp.config.DataDictionary.FEEDBACK_RESULT_AGREE;
+
+@Component
+@Slf4j
+public class DistributedScheduledTask {
+
+
+    private static final String TASK_LOCK_KEY = "scheduled_task:sync_data";
+
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+
+    @Autowired
+    private AitagTagLogDao aitagTagLogDao;
+
+    @Autowired
+    private AitagTagDailyAggDao aitagTagDailyAggDao;
+
+    /**
+     * 每天凌晨统计昨天打标数据
+     */
+    @Scheduled(cron = "#{@environment.getProperty('application.scheduled.task-cron')}")
+    public void syncDataTask() {
+        log.info("智能标签统计,准备获取redis锁");
+        Boolean taskCron = redisTemplate.opsForValue().setIfAbsent("taskCron", "1", 4, TimeUnit.HOURS);
+        if(Boolean.TRUE.equals(taskCron)){
+            try{
+                log.info("获取redis锁成功,智能标签开始统计");
+                LambdaQueryWrapper<AitagTagDailyAggEntity> queryWrapper = new LambdaQueryWrapper<>();
+                String yesterday = LocalDate.now().minusDays(1).toString();
+                queryWrapper.eq(AitagTagDailyAggEntity::getAggDate,yesterday);
+                int count = aitagTagDailyAggDao.selectCount(queryWrapper);
+                if(count == 0){
+                    executeBusinessLogic(yesterday);
+                }
+                log.info("智能标签统计任务结束");
+            }finally {
+                log.info("删除redis锁信息");
+                redisTemplate.delete("taskCron");
+            }
+        }else{
+            log.warn("智能标签统计任务已有节点执行--本次执行跳过");
+        }
+    }
+
+    private void executeBusinessLogic(String yesterday) {
+        List<AitagTagLogEntity> aitagTagLogEntities = aitagTagLogDao.selectByInsertTime(yesterday);
+        if(aitagTagLogEntities != null){
+            Map<String,Integer> count = new HashMap<>();
+            for (AitagTagLogEntity aitagTagLog:aitagTagLogEntities){
+                String feedback = aitagTagLog.getFeedback();
+                String categoryCode = aitagTagLog.getCategoryCode();
+                if(FEEDBACK_RESULT_AGREE.equals(feedback)){
+                    String result = aitagTagLog.getResult();
+                    duCount(result, count,categoryCode);
+                }else{
+                    String result = aitagTagLog.getFeedbackResult();
+                    duCount(result, count,categoryCode);
+                }
+            }
+            AitagTagDailyAggEntity aitagTagLog = new AitagTagDailyAggEntity();
+            for (String key : count.keySet()){
+                aitagTagLog.setId(StringUtils.getUUID());
+                aitagTagLog.setAggDate(yesterday);
+                aitagTagLog.setTagCount(count.get(key));
+                String[] split = key.split(":");
+                aitagTagLog.setTagNm(split[0]);
+                aitagTagLog.setCategoryCode(split[1]);
+                aitagTagDailyAggDao.insert(aitagTagLog);
+            }
+
+        }
+    }
+
+    private static void duCount(String result, Map<String, Integer> count,String categoryCode) {
+        JSONObject resultMap = JSONObject.parseObject(result);
+        String labe = resultMap.getString("labe")+":"+categoryCode;
+        if(count.containsKey(labe)){
+            Integer integer = count.get(labe);
+            count.put(labe,integer+1);
+        }else{
+            count.put(labe,1);
+        }
+    }
+
+
+}

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

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

@@ -1,6 +1,6 @@
 package cn.com.yusys.yusp.service.impl;
 
-import cn.com.yusys.yusp.domain.dto.AitagAppCreateDTO;
+import cn.com.yusys.yusp.domain.dto.AitagAppCreateDto;
 import cn.com.yusys.yusp.domain.entity.AitagApp;
 import cn.com.yusys.yusp.mapper.AitagAppMapper;
 import cn.com.yusys.yusp.service.AitagAppService;
@@ -31,7 +31,7 @@ public class AitagAppServiceImpl implements AitagAppService {
     }
 
     @Override
-    public AitagApp addApp(AitagAppCreateDTO dto) {
+    public AitagApp addApp(AitagAppCreateDto dto) {
         String appName = dto.getAppName();
 
         // 校验唯一性

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

@@ -1,9 +1,9 @@
 package cn.com.yusys.yusp.service.impl;
 
-import cn.com.yusys.yusp.domain.dto.AitagTagCategoryCreateDTO;
-import cn.com.yusys.yusp.domain.dto.AitagTagCategoryUpdateDTO;
+import cn.com.yusys.yusp.domain.dto.AitagTagCategoryCreateDto;
+import cn.com.yusys.yusp.domain.dto.AitagTagCategoryUpdateDto;
 import cn.com.yusys.yusp.domain.entity.AitagTagCategory;
-import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVO;
+import cn.com.yusys.yusp.domain.vo.AitagTagCategoryVo;
 import cn.com.yusys.yusp.mapper.AitagTagCategoryMapper;
 import cn.com.yusys.yusp.service.AitagTagCategoryService;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -21,14 +21,14 @@ public class AitagTagCategoryServiceImpl implements AitagTagCategoryService {
     private AitagTagCategoryMapper aiTagCategoryMapper;
 
     @Override
-    public Page<AitagTagCategoryVO> listCategories(int page, int size) {
+    public Page<AitagTagCategoryVo> listCategories(int page, int size) {
         int offset = (page - 1) * size;
         List<AitagTagCategory> records = aiTagCategoryMapper.selectPageCategories(offset, size);
         long total = aiTagCategoryMapper.selectCountAll();
 
         // 转换为 VO 并动态计算 tagNum
-        List<AitagTagCategoryVO> voList = records.stream().map(category -> {
-            AitagTagCategoryVO vo = new AitagTagCategoryVO();
+        List<AitagTagCategoryVo> voList = records.stream().map(category -> {
+            AitagTagCategoryVo vo = new AitagTagCategoryVo();
             vo.setId(category.getId());
             vo.setCategoryCode(category.getCategoryCode());
             vo.setCategoryNm(category.getCategoryNm());
@@ -44,14 +44,14 @@ public class AitagTagCategoryServiceImpl implements AitagTagCategoryService {
             return vo;
         }).collect(Collectors.toList());
 
-        Page<AitagTagCategoryVO> pageObj = new Page<>(page, size, total);
+        Page<AitagTagCategoryVo> pageObj = new Page<>(page, size, total);
         pageObj.setRecords(voList);
         return pageObj;
     }
 
 
     @Override
-    public AitagTagCategory createCategory(AitagTagCategoryCreateDTO dto) {
+    public AitagTagCategory createCategory(AitagTagCategoryCreateDto dto) {
         // 校验唯一性
         if (aiTagCategoryMapper.selectCountByNameAndNotDeleted(dto.getCategoryNm()) > 0) {
             throw new RuntimeException("标签体系名称已存在");
@@ -69,7 +69,7 @@ public class AitagTagCategoryServiceImpl implements AitagTagCategoryService {
     }
 
     @Override
-    public AitagTagCategory updateCategory(AitagTagCategoryUpdateDTO dto) {
+    public AitagTagCategory updateCategory(AitagTagCategoryUpdateDto dto) {
         AitagTagCategory existing = aiTagCategoryMapper.selectById(dto.getId());
         if (existing == null) {
             throw new RuntimeException("标签体系不存在");
@@ -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);
     }
 

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

@@ -0,0 +1,272 @@
+package cn.com.yusys.yusp.service.impl;
+
+import cn.com.yusys.yusp.commons.util.StringUtils;
+import cn.com.yusys.yusp.commons.util.date.DateUtils;
+import cn.com.yusys.yusp.domain.vo.VersionRollbackVo;
+import cn.com.yusys.yusp.mapper.AitagTagInfoVersionDao;
+import cn.com.yusys.yusp.domain.dto.TagInfoDto;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoVersionEntity;
+import cn.com.yusys.yusp.domain.vo.TagNodeVo;
+import cn.com.yusys.yusp.mapper.AitagTagInfoDao;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity;
+import cn.com.yusys.yusp.domain.vo.AitagTagInfoQueryVo;
+import cn.com.yusys.yusp.service.AitagTagInfoService;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+import static cn.com.yusys.yusp.config.DataDictionary.*;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-26 14:34:56
+ */
+
+@Service("aitagTagInfoService")
+public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagTagInfoEntity> implements AitagTagInfoService {
+
+
+    @Autowired
+    private AitagTagInfoVersionDao aitagTagInfoVersionDao;
+
+    /**
+     * 分页查询
+     *
+     * @param aitagTagInfoQueryVo
+     * @return IPage
+     */
+    @Override
+    public IPage<AitagTagInfoEntity> queryPage(AitagTagInfoQueryVo aitagTagInfoQueryVo) {
+        LambdaQueryWrapper<AitagTagInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
+        if (aitagTagInfoQueryVo.getId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getId, aitagTagInfoQueryVo.getId());
+        }
+        if (aitagTagInfoQueryVo.getCategoryId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getCategoryId, aitagTagInfoQueryVo.getCategoryId());
+        }
+        if (aitagTagInfoQueryVo.getTagNm() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagNm, aitagTagInfoQueryVo.getTagNm());
+        }
+        if (aitagTagInfoQueryVo.getTagCode() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagCode, aitagTagInfoQueryVo.getTagCode());
+        }
+        if (aitagTagInfoQueryVo.getTagRemark() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagRemark, aitagTagInfoQueryVo.getTagRemark());
+        }
+        if (aitagTagInfoQueryVo.getparentId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getParentId, aitagTagInfoQueryVo.getparentId());
+        }
+        if (aitagTagInfoQueryVo.getReg() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getReg, aitagTagInfoQueryVo.getReg());
+        }
+        if (aitagTagInfoQueryVo.getLevel() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getLevel, aitagTagInfoQueryVo.getLevel());
+        }
+        if (aitagTagInfoQueryVo.getTagPath() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagPath, aitagTagInfoQueryVo.getTagPath());
+        }
+        if (aitagTagInfoQueryVo.getIsDelete() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getIsDelete, aitagTagInfoQueryVo.getIsDelete());
+        }
+        if (aitagTagInfoQueryVo.getState() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getState, aitagTagInfoQueryVo.getState());
+        }
+        if (aitagTagInfoQueryVo.getTagPrompt() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagPrompt, aitagTagInfoQueryVo.getTagPrompt());
+        }
+
+        IPage<AitagTagInfoEntity> page = this.page(new Page<>(aitagTagInfoQueryVo.getPage(), aitagTagInfoQueryVo.getSize()), queryWrapper);
+        return page;
+    }
+
+
+    @Override
+    public void saveTag(AitagTagInfoEntity aitagTagInfo){
+        aitagTagInfo.setId(StringUtils.getUUID());
+        String parentId = aitagTagInfo.getParentId();
+        if(!StringUtils.isBlank(parentId)){
+            LambdaQueryWrapper<AitagTagInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(AitagTagInfoEntity::getParentId,parentId);
+            queryWrapper.eq(AitagTagInfoEntity::getIsDelete,TAG_UNDELETED);
+            List<AitagTagInfoEntity> aitagTagInfoEntities = this.baseMapper.selectList(queryWrapper);
+            if(!aitagTagInfoEntities.isEmpty()){
+                AitagTagInfoEntity aitagTagInfoEntity = aitagTagInfoEntities.get(0);
+                aitagTagInfo.setTagPath(aitagTagInfoEntity.getTagPath()+"/"+aitagTagInfo.getTagNm());
+                aitagTagInfo.setLevel(aitagTagInfoEntity.getLevel()+1);
+            }else{
+                aitagTagInfo.setTagPath(aitagTagInfo.getTagNm());
+                aitagTagInfo.setLevel(1);
+            }
+        }else{
+            aitagTagInfo.setTagPath(aitagTagInfo.getTagNm());
+            aitagTagInfo.setLevel(1);
+        }
+        aitagTagInfo.setRevisionTime(DateUtils.getCurrDateTimeStr());
+        aitagTagInfo.setTagVersion("0.1");
+        aitagTagInfo.setIsDelete(TAG_UNDELETED);
+        aitagTagInfo.setState(TAG_ENABLED);
+        this.save(aitagTagInfo);
+    }
+
+    /**
+     * 列表查询
+     *
+     * @param aitagTagInfoQueryVo
+     * @return List<TagNodeVo>
+     */
+    @Override
+    public List<TagNodeVo> queryTagTree(AitagTagInfoQueryVo aitagTagInfoQueryVo) {
+        LambdaQueryWrapper<AitagTagInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
+        if (aitagTagInfoQueryVo.getId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getId, aitagTagInfoQueryVo.getId());
+        }
+        queryWrapper.eq(AitagTagInfoEntity::getCategoryId, aitagTagInfoQueryVo.getCategoryId());
+        if (aitagTagInfoQueryVo.getTagNm() != null) {
+            queryWrapper.like(AitagTagInfoEntity::getTagNm, aitagTagInfoQueryVo.getTagNm());
+        }
+        queryWrapper.eq(AitagTagInfoEntity::getIsDelete, TAG_UNDELETED);
+        List<AitagTagInfoEntity> aitagTagInfoEntities = this.baseMapper.selectList(queryWrapper);
+        return this.buildTree(aitagTagInfoEntities);
+    }
+
+    @Override
+    public TagInfoDto getTagInfo(String id) {
+        return this.baseMapper.selectTagInfo(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateTag(AitagTagInfoEntity aitagTagInfoEntity) {
+        AitagTagInfoEntity tagInfo = this.baseMapper.selectById(aitagTagInfoEntity.getId());
+        AitagTagInfoVersionEntity aitagTagInfoVersionEntity = JSON.parseObject(JSON.toJSONString(tagInfo), AitagTagInfoVersionEntity.class);
+        aitagTagInfoVersionEntity.setId(StringUtils.getUUID());
+        this.aitagTagInfoVersionDao.insert(aitagTagInfoVersionEntity);
+        aitagTagInfoEntity.setRevisionTime(DateUtils.getCurrDateTimeStr());
+        aitagTagInfoEntity.setTagVersion(Double.parseDouble(tagInfo.getTagVersion())+0.1+"");
+        this.baseMapper.updateById(aitagTagInfoEntity);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void removeTags(List<String> list) {
+        AitagTagInfoEntity aitagTagInfoEntity = new AitagTagInfoEntity();
+        for (String id:list){
+            aitagTagInfoEntity.setId(id);
+            aitagTagInfoEntity.setIsDelete(TAG_DELETED);
+            this.baseMapper.updateById(aitagTagInfoEntity);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void versionRollback(VersionRollbackVo versionRollback) {
+        LambdaQueryWrapper<AitagTagInfoVersionEntity> queryTagVersionWrapper = new LambdaQueryWrapper<>();
+        queryTagVersionWrapper.eq(AitagTagInfoVersionEntity::getTagCode,versionRollback.getTagCode());
+        queryTagVersionWrapper.eq(AitagTagInfoVersionEntity::getTagVersion,versionRollback.getVersion());
+        List<AitagTagInfoVersionEntity> versionEntities = this.aitagTagInfoVersionDao.selectList(queryTagVersionWrapper);
+        if(versionEntities !=null && !versionEntities.isEmpty()){
+            AitagTagInfoVersionEntity tagInfoVersion = versionEntities.get(0);
+            AitagTagInfoEntity aitagTagInfoEntity = JSON.parseObject(JSONObject.toJSONString(tagInfoVersion), AitagTagInfoEntity.class);
+            aitagTagInfoEntity.setId(versionRollback.getId());
+            aitagTagInfoEntity.setRevisionTime(DateUtils.getCurrDateTimeStr());
+            aitagTagInfoEntity.setReviser(versionRollback.getReviser());
+            this.baseMapper.updateById(aitagTagInfoEntity);
+            LambdaUpdateWrapper<AitagTagInfoVersionEntity> updateTagVersion = new LambdaUpdateWrapper<>();
+            updateTagVersion.eq(AitagTagInfoVersionEntity::getTagCode,versionRollback.getTagCode());
+            updateTagVersion.ge(AitagTagInfoVersionEntity::getTagVersion,versionRollback.getVersion());
+            AitagTagInfoVersionEntity aitagTagInfoVersionEntity = new AitagTagInfoVersionEntity();
+            aitagTagInfoVersionEntity.setIsDelete(TAG_DELETED);
+            this.aitagTagInfoVersionDao.update(aitagTagInfoVersionEntity,updateTagVersion);
+        }
+    }
+
+    @Override
+    public List<AitagTagInfoEntity> queryList(AitagTagInfoQueryVo aitagTagInfoQueryVo) {
+        LambdaQueryWrapper<AitagTagInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
+        if (aitagTagInfoQueryVo.getId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getId, aitagTagInfoQueryVo.getId());
+        }
+        if (aitagTagInfoQueryVo.getCategoryId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getCategoryId, aitagTagInfoQueryVo.getCategoryId());
+        }
+        if (aitagTagInfoQueryVo.getTagNm() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagNm, aitagTagInfoQueryVo.getTagNm());
+        }
+        if (aitagTagInfoQueryVo.getTagCode() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagCode, aitagTagInfoQueryVo.getTagCode());
+        }
+        if (aitagTagInfoQueryVo.getTagRemark() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagRemark, aitagTagInfoQueryVo.getTagRemark());
+        }
+        if (aitagTagInfoQueryVo.getparentId() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getParentId, aitagTagInfoQueryVo.getparentId());
+        }
+        if (aitagTagInfoQueryVo.getReg() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getReg, aitagTagInfoQueryVo.getReg());
+        }
+        if (aitagTagInfoQueryVo.getLevel() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getLevel, aitagTagInfoQueryVo.getLevel());
+        }
+        if (aitagTagInfoQueryVo.getTagPath() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagPath, aitagTagInfoQueryVo.getTagPath());
+        }
+        if (aitagTagInfoQueryVo.getIsDelete() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getIsDelete, aitagTagInfoQueryVo.getIsDelete());
+        }
+        if (aitagTagInfoQueryVo.getState() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getState, aitagTagInfoQueryVo.getState());
+        }
+        if (aitagTagInfoQueryVo.getTagPrompt() != null) {
+            queryWrapper.eq(AitagTagInfoEntity::getTagPrompt, aitagTagInfoQueryVo.getTagPrompt());
+        }
+        queryWrapper.select(AitagTagInfoEntity::getId,AitagTagInfoEntity::getTagNm,AitagTagInfoEntity::getParentId);
+        return this.baseMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 将AitagTagInfoEntity列表转换为树形结构
+     * @param entities AitagTagInfoEntity列表
+     * @return 树形结构的根节点列表
+     */
+    private List<TagNodeVo> buildTree(List<AitagTagInfoEntity> entities) {
+        List<TagNodeVo> rootNodes = new ArrayList<>();
+        Map<String, TagNodeVo> nodeMap = new HashMap<>();
+
+        // 首先创建所有节点并放入map
+        for (AitagTagInfoEntity entity : entities) {
+            TagNodeVo node = new TagNodeVo(entity);
+            nodeMap.put(entity.getId(), node);
+        }
+
+        // 构建树形结构
+        for (AitagTagInfoEntity entity : entities) {
+            TagNodeVo currentNode = nodeMap.get(entity.getId());
+            String parentId = entity.getParentId();
+
+            // 如果parentId为null或空,则为根节点
+            if (parentId == null || parentId.isEmpty()) {
+                rootNodes.add(currentNode);
+            } else {
+                // 查找父节点并添加为子节点
+                TagNodeVo parentNode = nodeMap.get(parentId);
+                if (parentNode != null) {
+                    parentNode.addChild(currentNode);
+                }else{
+                    rootNodes.add(currentNode);
+                }
+            }
+        }
+        return rootNodes;
+    }
+}

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

@@ -0,0 +1,81 @@
+package cn.com.yusys.yusp.service.impl;
+
+import cn.com.yusys.yusp.mapper.AitagTagInfoVersionDao;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoVersionEntity;
+import cn.com.yusys.yusp.domain.vo.AitagTagInfoVersionQueryVo;
+import cn.com.yusys.yusp.service.AitagTagInfoVersionService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * 标签信息版本表
+ *
+ * @author 2507040827
+ * @date 2026-02-27 11:33:54
+ */
+
+@Service("aitagTagInfoVersionService")
+public class AitagTagInfoVersionServiceImpl extends ServiceImpl<AitagTagInfoVersionDao, AitagTagInfoVersionEntity> implements AitagTagInfoVersionService {
+
+    /**
+     * 分页查询
+     *
+     * @param aitagTagInfoVersionQueryVo
+     * @return IPage
+     */
+    @Override
+    public IPage<AitagTagInfoVersionEntity> queryPage(AitagTagInfoVersionQueryVo aitagTagInfoVersionQueryVo) {
+        LambdaQueryWrapper<AitagTagInfoVersionEntity> queryWrapper = new LambdaQueryWrapper<>();
+        if (aitagTagInfoVersionQueryVo.getId() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getId, aitagTagInfoVersionQueryVo.getId());
+        }
+        if (aitagTagInfoVersionQueryVo.getCategoryId() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getCategoryId, aitagTagInfoVersionQueryVo.getCategoryId());
+        }
+        if (aitagTagInfoVersionQueryVo.getTagNm() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getTagNm, aitagTagInfoVersionQueryVo.getTagNm());
+        }
+        if (aitagTagInfoVersionQueryVo.getTagCode() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getTagCode, aitagTagInfoVersionQueryVo.getTagCode());
+        }
+        if (aitagTagInfoVersionQueryVo.getTagRemark() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getTagRemark, aitagTagInfoVersionQueryVo.getTagRemark());
+        }
+        if (aitagTagInfoVersionQueryVo.getparentId() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getparentId, aitagTagInfoVersionQueryVo.getparentId());
+        }
+        if (aitagTagInfoVersionQueryVo.getReg() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getReg, aitagTagInfoVersionQueryVo.getReg());
+        }
+        if (aitagTagInfoVersionQueryVo.getLevel() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getLevel, aitagTagInfoVersionQueryVo.getLevel());
+        }
+        if (aitagTagInfoVersionQueryVo.getTagPath() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getTagPath, aitagTagInfoVersionQueryVo.getTagPath());
+        }
+        if (aitagTagInfoVersionQueryVo.getIsDelete() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getIsDelete, aitagTagInfoVersionQueryVo.getIsDelete());
+        }
+        if (aitagTagInfoVersionQueryVo.getState() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getState, aitagTagInfoVersionQueryVo.getState());
+        }
+        if (aitagTagInfoVersionQueryVo.getTagPrompt() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getTagPrompt, aitagTagInfoVersionQueryVo.getTagPrompt());
+        }
+        if (aitagTagInfoVersionQueryVo.getTagVersion() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getTagVersion, aitagTagInfoVersionQueryVo.getTagVersion());
+        }
+        if (aitagTagInfoVersionQueryVo.getRevisionTime() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getRevisionTime, aitagTagInfoVersionQueryVo.getRevisionTime());
+        }
+        if (aitagTagInfoVersionQueryVo.getReviser() != null) {
+            queryWrapper.eq(AitagTagInfoVersionEntity::getReviser, aitagTagInfoVersionQueryVo.getReviser());
+        }
+
+        IPage<AitagTagInfoVersionEntity> page = this.page(new Page<>(aitagTagInfoVersionQueryVo.getPage(), aitagTagInfoVersionQueryVo.getSize()), queryWrapper);
+        return page;
+    }
+}

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

@@ -0,0 +1,204 @@
+package cn.com.yusys.yusp.service.impl;
+
+import cn.com.yusys.yusp.commons.exception.BizException;
+import cn.com.yusys.yusp.commons.util.date.DateUtils;
+import cn.com.yusys.yusp.config.DataDictionary;
+import cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity;
+import cn.com.yusys.yusp.domain.vo.*;
+import cn.com.yusys.yusp.mapper.AitagTagDailyAggDao;
+import cn.com.yusys.yusp.mapper.AitagTagInfoDao;
+import cn.com.yusys.yusp.mapper.AitagTagLogDao;
+import cn.com.yusys.yusp.domain.entity.AitagTagLogEntity;
+import cn.com.yusys.yusp.service.AitagTagLogService;
+import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+import static cn.com.yusys.yusp.config.DataDictionary.*;
+
+/**
+ * 
+ *
+ * @author 2507040827
+ * @date 2026-02-25 14:56:45
+ */
+
+@Service("aitagTagLogService")
+public class AitagTagLogServiceImpl extends ServiceImpl<AitagTagLogDao, AitagTagLogEntity> implements AitagTagLogService {
+
+    @Autowired
+    private AitagTagInfoDao tagInfoDao;
+
+    @Autowired
+    private AitagTagDailyAggDao aggDao;
+
+    @Override
+    public DataOverviewVo dataOverview(SmartTaggingResultVo taggingResult) {
+        DataOverviewVo dataOverview = new DataOverviewVo();
+        Integer totalCnt = this.baseMapper.selectTagCount(taggingResult);
+        dataOverview.setCountNum(totalCnt);
+
+        LambdaQueryWrapper<AitagTagLogEntity> queryCount = new LambdaQueryWrapper<>();
+        LambdaQueryWrapper<AitagTagLogEntity> queryAgreeTags = new LambdaQueryWrapper<>();
+        if (StringUtils.isNotBlank(taggingResult.getCategoryCode())) {
+            queryCount.eq(AitagTagLogEntity::getCategoryCode, taggingResult.getCategoryCode());
+            queryAgreeTags.eq(AitagTagLogEntity::getCategoryCode, taggingResult.getCategoryCode());
+        }
+        if (StringUtils.isNotBlank(taggingResult.getStartTaggingTime())){
+            queryCount.ge(AitagTagLogEntity::getInsertTime, taggingResult.getStartTaggingTime());
+            queryAgreeTags.ge(AitagTagLogEntity::getInsertTime, taggingResult.getStartTaggingTime());
+        }
+        if (StringUtils.isNotBlank(taggingResult.getEndTaggingTime())){
+            queryCount.le(AitagTagLogEntity::getInsertTime, taggingResult.getEndTaggingTime());
+            queryAgreeTags.le(AitagTagLogEntity::getInsertTime, taggingResult.getEndTaggingTime());
+        }
+        queryCount.in(AitagTagLogEntity::getState, DataDictionary.RESULT_PUSHED,DataDictionary.MANAGER_CONFIRMED);
+        queryAgreeTags.in(AitagTagLogEntity::getState, DataDictionary.RESULT_PUSHED,DataDictionary.MANAGER_CONFIRMED);
+
+        Integer totalConfirmedCnt = this.baseMapper.selectCount(queryCount);
+
+        queryAgreeTags.eq(AitagTagLogEntity::getFeedback,FEEDBACK_RESULT_AGREE);
+        Integer agreeTagNum = this.baseMapper.selectCount(queryAgreeTags);
+        BigDecimal accurateRate = calculateAccuracy(agreeTagNum, totalConfirmedCnt);
+
+        dataOverview.setAccurateNum(totalConfirmedCnt);
+        dataOverview.setAccurateRate(accurateRate+"%");
+        if(totalConfirmedCnt == 0){
+            dataOverview.setManualAdjustRate("0%");
+        }else{
+            dataOverview.setManualAdjustRate(BigDecimal.valueOf(100).subtract(accurateRate)
+                    .setScale(2, RoundingMode.HALF_UP)+"%");
+        }
+        dataOverview.setManualAdjustCount(totalConfirmedCnt-agreeTagNum);
+        dataOverview.setTaggedUnconfirmed(totalCnt-totalConfirmedCnt);
+        return dataOverview;
+    }
+
+    @Override
+    public List<IconResVo> taggingTrend(TaggingTrendReqVo taggingTrendReqVo) {
+        String startTaggingTime = taggingTrendReqVo.getStartTaggingTime();
+        String endTaggingTime = taggingTrendReqVo.getEndTaggingTime();
+
+        int daysByTwoDatesDef = DateUtils.getDaysByTwoDatesDef(startTaggingTime, endTaggingTime);
+        List<IconResVo> taggingTrendResVo = null;
+        if(daysByTwoDatesDef>=15){
+            taggingTrendResVo = this.baseMapper.selectTagReportByDay(taggingTrendReqVo);
+        }else{
+            taggingTrendResVo = this.baseMapper.selectTagReportByMonth(taggingTrendReqVo);
+        }
+        return taggingTrendResVo;
+    }
+
+    @Override
+    public IPage<AitagTagLogEntity> taggingDetails(TaggingTransactionReqVo transactionReqVo) {
+        LambdaQueryWrapper<AitagTagLogEntity> queryWrapper = new LambdaQueryWrapper<>();
+        if (StringUtils.isNotBlank(transactionReqVo.getCategoryCode())) {
+            queryWrapper.eq(AitagTagLogEntity::getCategoryCode, transactionReqVo.getCategoryCode());
+        }
+        if (StringUtils.isNotBlank(transactionReqVo.getStartTaggingTime())){
+            queryWrapper.ge(AitagTagLogEntity::getInsertTime, transactionReqVo.getStartTaggingTime());
+        }
+        if (StringUtils.isNotBlank(transactionReqVo.getEndTaggingTime())){
+            queryWrapper.le(AitagTagLogEntity::getInsertTime, transactionReqVo.getEndTaggingTime());
+        }
+        if (StringUtils.isNotBlank(transactionReqVo.getLoanApplicationNo())){
+            queryWrapper.le(AitagTagLogEntity::getBusinessAttr, transactionReqVo.getLoanApplicationNo());
+        }
+        return this.baseMapper.selectPage(new Page<>(transactionReqVo.getPage() == null?0:transactionReqVo.getPage(),
+                transactionReqVo.getSize() == null?10:transactionReqVo.getSize()),queryWrapper);
+    }
+
+    @Override
+    public AitagTagLogEntity show(String id) {
+        AitagTagLogEntity aitagTagLog = this.baseMapper.selectById(id);
+        if(aitagTagLog !=null){
+            String result = "";
+            String feedback = aitagTagLog.getFeedback();
+            if(FEEDBACK_RESULT_REJECT.equals(feedback)){
+                result = aitagTagLog.getFeedbackResult();
+                result = this.getTagPath(result);
+                aitagTagLog.setFeedbackResult(result);
+            }else{
+                result = aitagTagLog.getResult();
+                result = getTagPath(result);
+                aitagTagLog.setResult(result);
+            }
+        }
+        return aitagTagLog;
+    }
+
+    private String getTagPath(String result) {
+        List<Map> results = JSONArray.parseArray(result, Map.class);
+        for (Map resultMap: results){
+            String tagId = resultMap.getOrDefault("id","").toString();
+            AitagTagInfoEntity aitagTagInfoEntity = tagInfoDao.selectById(tagId);
+            if(aitagTagInfoEntity!=null){
+                resultMap.put("tag_path",aitagTagInfoEntity.getTagPath());
+            }
+        }
+        return JSONArray.toJSONString(results);
+    }
+
+    @Override
+    public List<IconResVo> tagDistStats(TagDistStatsReqVo resultVo) {
+        return aggDao.selectTagDistStats(resultVo);
+    }
+
+    @Override
+    public List<AitagTagLogEntity> queryList(TaggingTransactionReqVo transactionReqVo) {
+        LambdaQueryWrapper<AitagTagLogEntity> queryCount = new LambdaQueryWrapper<>();
+        if (StringUtils.isNotBlank(transactionReqVo.getCategoryCode())) {
+            queryCount.eq(AitagTagLogEntity::getCategoryCode, transactionReqVo.getCategoryCode());
+        }
+        if (StringUtils.isNotBlank(transactionReqVo.getStartTaggingTime())){
+            queryCount.ge(AitagTagLogEntity::getInsertTime, transactionReqVo.getStartTaggingTime());
+        }
+        if (StringUtils.isNotBlank(transactionReqVo.getEndTaggingTime())){
+            queryCount.le(AitagTagLogEntity::getInsertTime, transactionReqVo.getEndTaggingTime());
+        }
+        if (StringUtils.isNotBlank(transactionReqVo.getLoanApplicationNo())){
+            queryCount.le(AitagTagLogEntity::getBusinessAttr, transactionReqVo.getLoanApplicationNo());
+        }
+        return this.baseMapper.selectList(queryCount);
+    }
+
+
+
+    /**
+     * 计算准确率
+     * @param correctCount 正确数
+     * @param totalCount 总数
+     * @return 准确率(百分比形式,保留两位小数)
+     * @throws IllegalArgumentException 当总数为0时抛出异常
+     */
+    public static BigDecimal calculateAccuracy(int correctCount, int totalCount) {
+        if (totalCount == 0) {
+            return new BigDecimal(0);
+        }
+
+        // 将整数转换为BigDecimal
+        BigDecimal correct = BigDecimal.valueOf(correctCount);
+        BigDecimal total = BigDecimal.valueOf(totalCount);
+
+        // 计算准确率:(正确数 / 总数) * 100,保留两位小数,四舍五入
+        BigDecimal accuracy = correct.divide(total, 4, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100))
+                .setScale(2, RoundingMode.HALF_UP);
+
+        return accuracy;
+    }
+
+
+}

+ 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 查询 -->

+ 37 - 0
server/yusp-tagging-core/src/main/resources/mapper/AitagTagDailyAggMapper.xml

@@ -0,0 +1,37 @@
+<?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.AitagTagDailyAggDao">
+    <resultMap id="aitagTagDailyAgg" type="cn.com.yusys.yusp.domain.entity.AitagTagDailyAggEntity">
+        <id column="id" jdbcType="VARCHAR" property="id"/>
+        <result column="agg_date" jdbcType="VARCHAR" property="aggDate"/>
+        <result column="category_nm" jdbcType="VARCHAR" property="categoryNm"/>
+        <result column="tag_nm" jdbcType="VARCHAR" property="tagNm"/>
+        <result column="tag_count" jdbcType="INTEGER" property="tagCount"/>
+    </resultMap>
+
+
+    <select id="selectTagDistStats" resultType="cn.com.yusys.yusp.domain.vo.IconResVo" parameterType="cn.com.yusys.yusp.domain.vo.TagDistStatsReqVo">
+        SELECT
+        tag_nm as stat,
+        tag_count AS val
+        FROM aitag_tag_daily_agg
+        <where>
+            <if test="startTaggingTime != null">
+                AND agg_date >= #{startTaggingTime}
+            </if>
+            <if test="endTaggingTime != null">
+                AND #{endTaggingTime} >= agg_date
+            </if>
+            <if test="categoryCode != null">
+                AND #{categoryCode} = category_code
+            </if>
+            <if test="sort == 'ase'">
+                order by tag_count
+            </if>
+            <if test="sort == 'desc' ">
+                order by tag_count desc
+            </if>
+        </where>
+        Limit 6
+    </select>
+</mapper>

+ 75 - 0
server/yusp-tagging-core/src/main/resources/mapper/AitagTagInfoMapper.xml

@@ -0,0 +1,75 @@
+<?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.AitagTagInfoDao">
+    <resultMap id="aitagTagInfo" type="cn.com.yusys.yusp.domain.entity.AitagTagInfoEntity">
+        <id column="id" jdbcType="VARCHAR" property="id"/>
+        <result column="category_id" jdbcType="VARCHAR" property="categoryId"/>
+        <result column="tag_nm" jdbcType="VARCHAR" property="tagNm"/>
+        <result column="tag_code" jdbcType="VARCHAR" property="tagCode"/>
+        <result column="tag_remark" jdbcType="VARCHAR" property="tagRemark"/>
+        <result column="parent_id" jdbcType="VARCHAR" property="parentId"/>
+        <result column="reg" jdbcType="VARCHAR" property="reg"/>
+        <result column="level" jdbcType="INTEGER" property="level"/>
+        <result column="tag_path" jdbcType="VARCHAR" property="tagPath"/>
+        <result column="tag_prompt" jdbcType="VARCHAR" property="tagPrompt"/>
+        <result column="tag_version" jdbcType="VARCHAR" property="tagVersion"/>
+        <result column="revision_time" jdbcType="VARCHAR" property="revisionTime"/>
+        <result column="reviser" jdbcType="VARCHAR" property="reviser"/>
+    </resultMap>
+
+
+    <resultMap id="tagInfo" type="cn.com.yusys.yusp.domain.dto.TagInfoDto" extends="aitagTagInfo">
+        <collection property="tagInfoVersionDtos" ofType="cn.com.yusys.yusp.domain.dto.TagInfoVersionDto">
+            <id property="id" column="version_id"/>
+            <result property="categoryId" column="version_category_id"/>
+            <result property="tagNm" column="version_tag_nm"/>
+            <result property="tagCode" column="version_tag_code"/>
+            <result property="tagRemark" column="version_tag_remark"/>
+            <result property="parentId" column="version_parent_id"/>
+            <result property="reg" column="version_reg"/>
+            <result property="level" column="version_level"/>
+            <result property="tagPath" column="version_tag_path"/>
+            <result property="tagPrompt" column="version_tag_prompt"/>
+            <result property="tagVersion" column="version_tag_version"/>
+            <result property="revisionTime" column="version_revision_time"/>
+            <result property="reviser" column="version_reviser"/>
+        </collection>
+    </resultMap>
+
+
+    <select id="selectTagInfo" resultMap="tagInfo">
+        SELECT
+            ti.id,
+            ti.category_id,
+            ti.tag_nm,
+            ti.tag_code,
+            ti.tag_remark,
+            ti.parent_id,
+            ti.reg,
+            ti.level,
+            ti.tag_path,
+            ti.tag_prompt,
+            ti.tag_version,
+            ti.revision_time,
+            ti.reviser,
+            tiv.id AS version_id,
+            tiv.category_id AS version_category_id,
+            tiv.tag_nm AS version_tag_nm,
+            tiv.tag_code AS version_tag_code,
+            tiv.tag_remark AS version_tag_remark,
+            tiv.parent_id AS version_parent_id,
+            tiv.reg AS version_reg,
+            tiv.level AS version_level,
+            tiv.tag_path AS version_tag_path,
+            tiv.tag_prompt AS version_tag_prompt,
+            tiv.tag_version AS version_tag_version,
+            tiv.revision_time AS version_revision_time,
+            tiv.reviser AS version_reviser
+        FROM aitag_tag_info ti
+                 LEFT JOIN (SELECT *
+                            FROM aitag_tag_info_version
+                            WHERE is_delete = '0')  tiv ON ti.tag_code = tiv.tag_code
+        where ti.id= #{id}
+    </select>
+
+</mapper>

+ 21 - 0
server/yusp-tagging-core/src/main/resources/mapper/AitagTagInfoVersionMapper.xml

@@ -0,0 +1,21 @@
+<?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.AitagTagInfoVersionDao">
+    <resultMap id="aitagTagInfoVersion" type="cn.com.yusys.yusp.domain.entity.AitagTagInfoVersionEntity">
+        <id column="id" jdbcType="VARCHAR" property="id"/>
+        <result column="category_id" jdbcType="VARCHAR" property="categoryId"/>
+        <result column="tag_nm" jdbcType="VARCHAR" property="tagNm"/>
+        <result column="tag_code" jdbcType="VARCHAR" property="tagCode"/>
+        <result column="tag_remark" jdbcType="LONGVARCHAR" property="tagRemark"/>
+        <result column="parent_id" jdbcType="VARCHAR" property="parentId"/>
+        <result column="reg" jdbcType="LONGVARCHAR" property="reg"/>
+        <result column="level" jdbcType="INTEGER" property="level"/>
+        <result column="tag_path" jdbcType="VARCHAR" property="tagPath"/>
+        <result column="is_delete" jdbcType="INTEGER" property="isDelete"/>
+        <result column="state" jdbcType="INTEGER" property="state"/>
+        <result column="tag_prompt" jdbcType="VARCHAR" property="tagPrompt"/>
+        <result column="tag_version" jdbcType="VARCHAR" property="tagVersion"/>
+        <result column="revision_time" jdbcType="VARCHAR" property="revisionTime"/>
+        <result column="reviser" jdbcType="VARCHAR" property="reviser"/>
+    </resultMap>
+</mapper>

+ 112 - 0
server/yusp-tagging-core/src/main/resources/mapper/AitagTagLogMapper.xml

@@ -0,0 +1,112 @@
+<?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.AitagTagLogDao">
+    <resultMap id="aitagTagLog" type="cn.com.yusys.yusp.domain.entity.AitagTagLogEntity">
+        <id column="id" jdbcType="VARCHAR" property="id"/>
+        <result column="app_id" jdbcType="VARCHAR" property="appId"/>
+        <result column="insert_time" jdbcType="TIMESTAMP" property="insertTime"/>
+        <result column="business_attr" jdbcType="VARCHAR" property="businessAttr"/>
+        <result column="phrase" jdbcType="VARCHAR" property="phrase"/>
+        <result column="attachment" jdbcType="VARCHAR" property="attachment"/>
+        <result column="attachment_url" jdbcType="VARCHAR" property="attachmentUrl"/>
+        <result column="result" jdbcType="VARCHAR" property="result"/>
+        <result column="feedback_user_id" jdbcType="VARCHAR" property="feedbackUserId"/>
+        <result column="feedback_user_nm" jdbcType="VARCHAR" property="feedbackUserNm"/>
+        <result column="feedback_time" jdbcType="TIMESTAMP" property="feedbackTime"/>
+        <result column="feedback" jdbcType="VARCHAR" property="feedback"/>
+        <result column="feedback_result" jdbcType="VARCHAR" property="feedbackResult"/>
+        <result column="state" jdbcType="INTEGER" property="state"/>
+        <result column="consuming_time" jdbcType="LONGVARCHAR" property="consumingTime"/>
+        <result column="category_code" jdbcType="VARCHAR" property="categoryCode"/>
+        <result column="reg_result" jdbcType="VARCHAR" property="regResult"/>
+        <result column="feedback_user_org" jdbcType="VARCHAR" property="feedbackUserOrg"/>
+        <result column="feedback_user_endpoint" jdbcType="VARCHAR" property="feedbackUserEndpoint"/>
+    </resultMap>
+
+
+    <select id="selectTagCount" parameterType="cn.com.yusys.yusp.domain.vo.SmartTaggingResultVo">
+        SELECT
+        COUNT(1) as totalCount
+        FROM aitag_tag_log
+        <where>
+            <if test="startTaggingTime != null and startTaggingTime != ''">
+                AND insert_time >= #{startTaggingTime}
+            </if>
+            <if test="endTaggingTime != null and endTaggingTime != ''">
+                AND #{endTaggingTime} >= insert_time
+            </if>
+            <if test="categoryCode != null and categoryCode != ''">
+                AND #{categoryCode} = category_code
+            </if>
+        </where>
+    </select>
+
+    <select id="selectTagReportByDay" resultType="cn.com.yusys.yusp.domain.vo.IconResVo" parameterType="cn.com.yusys.yusp.domain.vo.SmartTaggingResultVo">
+        SELECT
+        DATE_FORMAT(insert_time, '%Y年%m月%d日') AS stat,
+        COUNT(1) AS val
+        FROM aitag_tag_log
+        <where>
+            <if test="startTaggingTime != null and startTaggingTime != ''">
+                AND insert_time >= #{startTaggingTime}
+            </if>
+            <if test="endTaggingTime != null and endTaggingTime != ''">
+                AND #{endTaggingTime} >= insert_time
+            </if>
+            <if test="categoryCode != null and categoryCode != ''">
+                AND #{categoryCode} = category_code
+            </if>
+        </where>
+        GROUP BY DATE(insert_time)
+        ORDER BY stat;
+    </select>
+
+    <select id="selectTagReportByMonth" resultType="cn.com.yusys.yusp.domain.vo.IconResVo" parameterType="cn.com.yusys.yusp.domain.vo.SmartTaggingResultVo">
+        SELECT
+        DATE_FORMAT(insert_time, '%Y年%m月') AS stat,
+        COUNT(1) AS val
+        FROM aitag_tag_log
+        <where>
+            <if test="startTaggingTime != null and startTaggingTime != ''">
+                AND insert_time >= #{startTaggingTime}
+            </if>
+            <if test="endTaggingTime != null and endTaggingTime != ''">
+                AND #{endTaggingTime} >= insert_time
+            </if>
+            <if test="categoryCode != null and categoryCode != ''">
+                AND #{categoryCode} = category_code
+            </if>
+        </where>
+        GROUP BY DATE_FORMAT(insert_time, '%Y-%m')
+        ORDER BY stat;
+    </select>
+
+    <select id="selectTagReportByWeek" resultType="cn.com.yusys.yusp.domain.vo.IconResVo" parameterType="cn.com.yusys.yusp.domain.vo.SmartTaggingResultVo">
+        SELECT
+        YEARWEEK(insert_time, 1) AS stat,
+        COUNT(1) AS val
+        FROM aitag_tag_log
+        <where>
+            <if test="startTaggingTime != null and  startTaggingTime != ''">
+                AND insert_time >= #{startTaggingTime}
+            </if>
+            <if test="endTaggingTime != null and  endTaggingTime != ''">
+                AND #{endTaggingTime} >= insert_time
+            </if>
+            <if test="categoryCode != null and categoryCode != ''">
+                AND #{categoryCode} = category_code
+            </if>
+        </where>
+        GROUP BY YEARWEEK(insert_time, 1)
+        ORDER BY stat;
+    </select>
+
+    <select id="selectByInsertTime" resultType="cn.com.yusys.yusp.domain.entity.AitagTagLogEntity">
+        SELECT id, app_id, insert_time, business_attr, phrase, attachment, attachment_url, `result`, feedback_user_id, feedback_user_nm, feedback_time, feedback, feedback_result, state, consuming_time, category_code
+        FROM aitag_tag_log
+            where
+            DATE(insert_time) = #{insertTime}
+        AND state in ("2","3")
+    </select>
+
+</mapper>

+ 3 - 1
server/yusp-tagging-core/src/main/resources/messages/yusp_input_msg.properties

@@ -96,4 +96,6 @@ ONLY_APPLY_TO_AUDITING_TASKS=\u53EA\u80FD\u7533\u8BF7\u89E3\u9501\u5BA1\u6838\u4
 REQUEST_PARAMETER_CANNOT_BE_EMPTY=\u8BF7\u6C42\u53C2\u6570\u4E0D\u80FD\u4E3A\u7A7A
 EXTENSION_FIELD_CANNOT_BE_EMPTY=\u6269\u5C55\u5B57\u6BB5\u4E0D\u80FD\u4E3A\u7A7A
 SUPPLEMENTAL_TABLE_CONFIG_NOT_EXIST=\u8865\u5F55\u8868\u914D\u7F6E\u4E0D\u5B58\u5728,\u8BF7\u786E\u8BA4\u540E\u518D\u64CD\u4F5C
-DATA_SOURCE_INFO_NOT_EXIST=\u5F53\u524D\u8868\u6570\u636E\u6E90\u4FE1\u606F\u4E0D\u5B58\u5728
+DATA_SOURCE_INFO_NOT_EXIST=\u5F53\u524D\u8868\u6570\u636E\u6E90\u4FE1\u606F\u4E0D\u5B58\u5728
+NO_DATA_PERIOD=\u8BE5\u7EDF\u8BA1\u5468\u671F\u6682\u672A\u5B9E\u73B0
+E001=\u6240\u5C5E\u5927\u7C7B\u4E0D\u80FD\u4E3A\u7A7A

+ 7 - 0
server/yusp-tagging-starter/pom.xml

@@ -210,6 +210,13 @@
             <version>5.3.33</version>
         </dependency>
 
+    <!-- EasyExcel 依赖 -->
+    <dependency>
+        <groupId>com.alibaba</groupId>
+        <artifactId>easyexcel</artifactId>
+        <version>3.3.4</version>
+    </dependency>
+
     </dependencies>
 
     <build>

+ 8 - 0
server/yusp-tagging-starter/src/main/java/cn/com/yusys/yusp/detail/App.java

@@ -1,16 +1,24 @@
 package cn.com.yusys.yusp.detail;
 
 
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import java.net.URL;
 
 @EnableDiscoveryClient
 @SpringBootApplication(scanBasePackages = {"cn.com.yusys.yusp"})
 @MapperScan("cn.com.yusys.yusp.mapper")
+@EnableScheduling
+@EnableTransactionManagement
 public class App {
     public static void main(String[] args) {
         SpringApplication.run(App.class, args);
     }
+
 }

+ 3 - 0
server/yusp-tagging-starter/src/main/resources/application.yml

@@ -54,3 +54,6 @@ management:
       exposure:
         include: info,health,env,configprops,mappings,threaddump,metrics #管理节点
 
+mybatis-plus:
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 控制台输出

+ 6 - 5
web/.editorconfig

@@ -1,14 +1,15 @@
-# https://editorconfig.org
 root = true
 
 [*]
-charset = utf-8
 indent_style = space
-indent_size = 2
 end_of_line = lf
-insert_final_newline = true
+charset = utf-8
 trim_trailing_whitespace = true
+insert_final_newline = true
 
 [*.md]
-insert_final_newline = false
+max_line_length = off
 trim_trailing_whitespace = false
+
+[*.{ts,js,vue,css}]
+indent_size = 2

+ 8 - 0
web/.env

@@ -0,0 +1,8 @@
+# 项目本地运行端口号
+VITE_APP_DEV_PORT = 8000
+
+# 前端静态资源打包路径
+VITE_APP_BASE_URL = ./
+
+# 是否开启微服务
+VITE_APP_MICRO_SERVER = true

+ 15 - 0
web/.env.development

@@ -0,0 +1,15 @@
+# 开发环境服务地址/网关地址
+# VITE_APP_BASE_API = http://110.16.193.170:20011
+VITE_APP_BASE_API = http://10.192.72.13:9195
+# VITE_APP_BASE_API = http://10.192.72.13:22227
+
+# yusp-uaa yusp-oca服务相关接口走本地mock服务 支持配置多个地址,分隔
+# VITE_APP_BASE_AUTH = http://110.16.193.170:20011
+VITE_APP_BASE_AUTH = http://10.192.72.13:9195
+# VITE_APP_BASE_AUTH = http://10.192.72.13:22227
+
+# 开发环境登录相关代理前缀
+VITE_APP_PROXY_API = /yusp-flow,/cdp-oca,/bj-npam-starter,/account,/session,/oauth,/swagger-resources,/v2/api-docs,/zzh-api,/yusp-ai
+
+# 开发环境代理shuffle业务功能接口代理前缀
+VITE_APP_PROXY_SHUFFLE = /api

+ 2 - 37
web/.env.production

@@ -1,37 +1,2 @@
-# 环境标识,生产环境
-ENV = 'production'
-VUE_APP_ENV = 'production'
-
-# 生产模式
-NODE_ENV = production
-
-# APP应用若计划部署在应用服务器子目录,则需要设置publicPath,
-# 例如:你计划部署在GitHub Pages上,访问路径为:https://your.github.io/app/,则你此处应配置为:'/app/'
-# 默认配置为根:'/'
-VUE_APP_PUBLIC_PATH = ''
-
-# 应用服务前缀URL
-# 1) 指定应用模式,Protocol(http/https)+IP+PORT。如:'https://172.16.20.92:8102/xyapp'
-# 2) 本地应用模式,前后端部署在一起。如:'' 或者 '/xyapp'
-
-#山西环境(开发)--使用老服务
-#VUE_APP_BASE_API = 'http://172.168.165.159:30503'
-#山西环境(测试)--使用新服务
-#VUE_APP_BASE_API = 'http://172.168.168.125:30503'
-#金融云环境
-#VUE_APP_BASE_API = 'http://192.168.51.16:8000'
-#金华环境
-#VUE_APP_BASE_API = 'http://21.5.130.72:18503'
-#金华环境
-#VUE_APP_BASE_API = 'http://21.5.4.14:18503'
-#五矿演示
-# VUE_APP_BASE_API = 'http://192.168.42.7:12022'
-#五矿信托
-VUE_APP_BASE_API = ''
-
-
-# Mock模拟数据,true启用,false禁用
-# 为测试部署,保留mock
-VUE_APP_MOCK_ENABLE = false
-# Mock模拟方式,是否真实服务模拟: true 使用express模拟, false 使用XHR拦截模拟。生产环境只能配置为false。
-VUE_APP_MOCK_MODE = false
+# 开发环境服务地址/网关地址
+VITE_APP_BASE_API = ''

+ 0 - 22
web/.env.staging

@@ -1,22 +0,0 @@
-# 环境标识,自定义环境
-ENV = 'staging'
-VUE_APP_ENV = 'staging'
-
-# 生产模式
-NODE_ENV = production
-
-# APP应用若计划部署在应用服务器子目录,则需要设置publicPath,
-# 例如:你计划部署在GitHub Pages上,访问路径为:https://your.github.io/app/,则你此处应配置为:'/app/'
-# 默认配置为根:'/'
-VUE_APP_PUBLIC_PATH = ''
-
-# 应用服务前缀URL
-# 1) 指定应用模式,Protocol(http/https)+IP+PORT。如:'https://172.16.20.92:8102/xyapp'
-# 2) 本地应用模式,前后端部署在一起。如:'' 或者 '/xyapp'
-
-VUE_APP_BASE_API = ''
-
-# Mock模拟数据,true启用,false禁用
-VUE_APP_MOCK_ENABLE = false
-# Mock模拟方式,是否真实服务模拟: true 使用express模拟, false 使用XHR拦截模拟。生产环境只能配置为false。
-VUE_APP_MOCK_MODE = false

+ 17 - 5
web/.eslintignore

@@ -1,6 +1,18 @@
-build/*.js
-src/assets
-src/*
-public
 dist
-src/locale
+node_modules
+!.prettierrc.js
+components.d.ts
+auto-imports.d.ts
+.*
+*.min.js
+src/utils
+src/components
+src/router
+src/config
+src/store
+src/views/pagefactory
+src/views/framework
+public
+src/views/@xdjf/oca-web/config
+src/views/@xdjf/oca-web/views/xpOca
+src/views/@xdjf/card/components/xtableHeaderDrag

+ 140 - 106
web/.eslintrc.js

@@ -1,62 +1,105 @@
-// https://eslint.org/docs/user-guide/configuring
-module.exports = {
+const { defineConfig } = require('eslint-define-config');
+
+module.exports = defineConfig({
   root: true,
-  parserOptions: {
-      parser: 'babel-eslint'
-  },
   env: {
-    browser: false,
+    browser: true,
     node: true,
+    jest: true,
     es6: true,
-    mocha: true
   },
   globals: {
-    'document': true,
-    'localStorage': true,
-    'window': true,
-    'navigator': true,
-    'yufp': true,
-    'Image': true,
-    'backend': true
+    RESP_CODE: true,
+    APP_CONST: true,
+    backend: true,
+    document: true,
+    localStorage: true,
+    window: true,
+    navigator: true,
+    yufp: true,
+    Image: true,
+    MICRO: true,
+    xp: true,
+    ctx: true,
   },
-  // extends: [
-  //     // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
-  //     // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
-  //     'plugin:vue/essential',
-  //     // https://github.com/standard/standard/blob/master/docs/RULES-en.md
-  //     'standard'
-  // ],
-  extends: ['plugin:vue/recommended', 'eslint:recommended'],
-  // required to lint *.vue files
-  plugins: [
-    'vue',
-    'html'
+  plugins: ['vue'],
+  parser: 'vue-eslint-parser',
+  parserOptions: {
+    ecmaVersion: 'latest',
+    sourceType: 'module',
+    allowImportExportEverywhere: true,
+    ecmaFeatures: {
+      jsx: true,
+    },
+    // parser: "@babel/eslint-parser",
+    requireConfigFile: false,
+  },
+  extends: [
+    'airbnb-base',
+    'eslint:recommended',
+    'plugin:vue/essential',
+    'plugin:vue/recommended',
+    'plugin:prettier/recommended',
+    'eslint:recommended',
   ],
   // add your custom rules here
   rules: {
-    "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
-    "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
-    "vue/max-attributes-per-line": [2, {
-      "singleline": 10,
-      "multiline": {
-        "max": 1,
-        "allowFirstLine": false
-      }
-    }],
-    "vue/singleline-html-element-content-newline": "off",
-    "vue/multiline-html-element-content-newline": "off",
+    // 禁止使用多余的包
+    'import/no-extraneous-dependencies': 0,
+    // 确保在导入路径内一致使用文件扩展名
+    'import/extensions': 0,
+    // 确保导入指向可以解析的文件/模块
+    'import/no-unresolved': 0,
+    // 首选默认导出导入/首选默认导出
+    'import/prefer-default-export': 0,
+    // 要求使用 let 或 const 而不是 var
+    'no-var': 'error',
+    'no-loss-of-precision': 2,
+    'vue/no-lone-template': 'off',
+    // 禁止使用 new 以避免产生副作用
+    'no-new': 1,
+    // 禁止对 Function 对象使用 new 操作符
+    'no-new-func': 0,
+    // 禁止使用 Object 的构造函数
+    'no-new-object': 0,
+    // 禁止调用 require 时使用 new 操作符
+    'no-new-require': 2,
+    // 禁止 Symbol 的构造函数
+    'no-new-symbol': 1,
+    // 禁止对 String,Number 和 Boolean 使用 new 操作符
+    'no-new-wrappers': 0,
+    // 禁止变量声明与外层作用域的变量同名
+    'no-shadow': 0,
+    // 禁止覆盖受限制的标识符
+    'no-shadow-restricted-names': 2,
+    strict: 0,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+    'vue/max-attributes-per-line': [
+      2,
+      {
+        singleline: 10,
+        multiline: {
+          max: 1,
+          allowFirstLine: false,
+        },
+      },
+    ],
+    'vue/singleline-html-element-content-newline': 'off',
+    'vue/multiline-html-element-content-newline': 'off',
     // "vue/name-property-casing": ["error", "PascalCase"],
-    "vue/name-property-casing": "off",
-    "vue/no-v-html": "off",
-    "vue/valid-v-bind": "off",
-    "vue/html-closing-bracket-newline": "off",
+    'vue/name-property-casing': 'off',
+    'vue/no-v-html': 'off',
+    'vue/valid-v-bind': 'off',
+    'vue/html-closing-bracket-newline': 'off',
     // @fixable vue属性编写顺序
     // @off 没有必要限制
-    "vue/attributes-order": "off",
-    "vue/html-self-closing": "off",
-    "vue/require-default-prop": "off",
-    "vue/attribute-hyphenation": "off",
-    "vue/no-unused-components": "off",
+    'vue/attributes-order': 'off',
+    'vue/html-self-closing': 'off',
+    'vue/require-default-prop': 'off',
+    'vue/attribute-hyphenation': 'off',
+    'vue/no-unused-components': 'off',
+    // 不支持template嵌套规则
+    'vue/no-useless-template-attributes': 'off',
     // "vue/html-self-closing": ["error", {
     //   "html": {
     //     "void": "never",
@@ -81,19 +124,19 @@ module.exports = {
     'arrow-body-style': 0,
     // 要求箭头函数的参数使用圆括号
     'arrow-parens': 0,
-    // 要求箭头函数空格
-    'arrow-spacing': [2, {'before': true,'after': true}],
+    // 强制箭头函数的箭头前后使用一致的空格
+    'arrow-spacing': 'error',
     // 强制把变量的使用限制在其定义的作用域范围内
     'block-scoped-var': 0,
     // 禁止或强制在单行代码块中使用空格(禁用)
     'block-spacing': 0,
     //强制使用一致的缩进 第二个参数为 'tab' 时,会使用tab,
     // if while function 后面的{必须与if在同一行,java风格。
-    'brace-style': [2, '1tbs', { 'allowSingleLine': true }],
+    'brace-style': [2, '1tbs', { allowSingleLine: true }],
     // require return statements after
     'callback-return': 0,
     // 双峰驼命名格式
-    'camelcase': 2,
+    camelcase: 0,
     // 注释 大写字母开头,不推荐 注释的代码会报错
     'capitalized-comments': 0,
     // 如果一个类方法没有使用this,它有时可以变成一个静态函数。如果将该方法转换为静态函数,那么调用该特定方法的类的实例也必须转换为静态调用
@@ -102,14 +145,14 @@ module.exports = {
     // always-multiline:多行模式必须带逗号,单行模式不能带逗号
     // 'comma-dangle': [2, 'never'],
     // 控制逗号前后的空格
-    'comma-spacing': [2, {'before': false,'after': true}],
+    'comma-spacing': [2, { before: false, after: true }],
     // 控制逗号在行尾出现还是在行首出现 (默认行尾)
     // http: //eslint.org/docs/rules/comma-style
-    'comma-style': [2,'last'],
+    'comma-style': [2, 'last'],
     // 限制圈复杂度,也就是类似if else能连续接多少个
-    'complexity': 0,
+    complexity: 0,
     // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
-    'computed-property-spacing': [2,'never'],
+    'computed-property-spacing': [2, 'never'],
     // 要求 return 语句要么总是指定返回的值,要么不指定
     'consistent-return': 0,
     // 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
@@ -118,13 +161,13 @@ module.exports = {
     // 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
     'constructor-super': 0,
     // 强制所有控制语句使用一致的括号风格
-    'curly': [0,'all'],
+    curly: [0, 'all'],
     // switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
     'default-case': 2,
     // 强制object.key 中 . 的位置,参数:
     // property,'.'号应与属性在同一行
     // object, '.' 号应与对象名在同一行
-    'dot-location': [2,'property'],
+    'dot-location': [2, 'property'],
     // 强制使用.号取属性
     // 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
     // false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {'allowKeywords': false}]
@@ -133,9 +176,9 @@ module.exports = {
     // 文件末尾强制换行
     'eol-last': 0,
     // 使用 === 替代 == allow-null允许null和undefined==
-    'eqeqeq': [0, 'allow-null'],
+    eqeqeq: [0, 'allow-null'],
     //强制 “for” 循环中更新子句的计数器朝着正确的方向移动
-    'for-direction': "error",
+    'for-direction': 'error',
     // 要求或禁止函数标识符与其调用之间的间隔
     'func-call-spacing': [0, 'always'],
     // 要求函数名称与它们所分配的变量或属性的名称相匹配
@@ -150,7 +193,7 @@ module.exports = {
     // 在函数括号内强制执行一致的换行符
     'function-paren-newline': 0,
     // 强制 generator 函数中 * 号周围使用一致的空格
-    'generator-star-spacing': [2, {'before': true,'after': true}],
+    'generator-star-spacing': [2, { before: true, after: true }],
     // 强制在 getter 属性中出现一个 return 语句。每个 getter 都期望有返回值。
     'getter-return': 2,
     // 要求 require() 出现在顶层模块作用域中
@@ -158,7 +201,7 @@ module.exports = {
     // 要求 for-in 循环中有一个 if 语句
     'guard-for-in': 0,
     // 要求回调函数中有容错处理
-    'handle-callback-err': [2,'^(err|error)$'],
+    'handle-callback-err': [2, '^(err|error)$'],
     // 禁止使用指定的标识符
     'id-blacklist': 0,
     // 强制标识符的最新和最大长度
@@ -168,7 +211,7 @@ module.exports = {
     // 强制执行箭头函数体的位置,一个箭头函数体可以包含一个隐式返回,而不是一个块体。对隐式返回的表达式执行一致的位置可能很有用。
     'implicit-arrow-linebreak': 0,
     // 强制执行一致的缩进
-    'indent': [2, 2, {'SwitchCase': 1}], // 强制 switch 语句中的 case 子句的缩进水平(默认:0) 
+    indent: ['error', 2, { SwitchCase: 1 }], // 强制 switch 语句中的 case 子句的缩进水平(默认:0)
     // 要求或禁止 var 声明中的初始化(初值)
     'init-declarations': 0,
     // 强制在 JSX 属性中一致地使用双引号或单引号
@@ -188,9 +231,9 @@ module.exports = {
     // 要求或禁止class成员之间的空行
     'lines-between-class-members': 0,
     // 强制执行嵌套块的最大深度,以降低代码复杂度。"max"(默认为4)
-    'max-depth': [2,10],
+    'max-depth': [2, 10],
     // 强制一行的最大长度
-    'max-len': [1, 2000, { 'ignoreTemplateLiterals': true }],
+    'max-len': 0,
     // 强制最大行数
     'max-lines': 0,
     // 强制回调函数最大嵌套深度 5层
@@ -269,8 +312,8 @@ module.exports = {
     'no-dupe-keys': 2,
     // 禁止重复的 case 标签
     'no-duplicate-case': 2,
-    // 不允许复制模块的进口
-    'no-duplicate-imports': 2,
+    // 禁止重复模块导入
+    'no-duplicate-imports': 'error',
     // 禁止 if 语句中有 return 之后有 else
     'no-else-return': 0,
     // 禁止空语句块
@@ -317,7 +360,7 @@ module.exports = {
     // 禁止在代码行后使用内联注释
     'no-inline-comments': 0,
     // 禁止在嵌套的块中出现 function 或 var 声明
-    'no-inner-declarations': [0,'both'],
+    'no-inner-declarations': [0, 'both'],
     // 禁止 RegExp 构造函数中无效的正则表达式字符串
     'no-invalid-regexp': 2,
     // 禁止 this 关键字出现在类和类对象之外
@@ -352,25 +395,13 @@ module.exports = {
     // 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
     'no-multi-str': 0,
     // 不允许多个空行
-    'no-multiple-empty-lines': [2, {'max': 2}],
+    'no-multiple-empty-lines': [2, { max: 2 }],
     // 不允许否定的表达式
     'no-negated-condition': 0,
     // 禁止在 in 表达式中出现否定的左操作数
     'no-negated-in-lhs': 0,
     // 不允许使用嵌套的三元表达式 var foo = bar ? baz : qux === quxx ? bing : bam;
     'no-nested-ternary': 1,
-    // 禁止在非赋值或条件语句中使用 new 操作符
-    'no-new': 0,
-    // 禁止对 Function 对象使用 new 操作符
-    'no-new-func': 1,
-    // 禁止使用 Object 的构造函数
-    'no-new-object': 0,
-    // 禁止调用 require 时使用 new 操作符
-    'no-new-require': 2,
-    // 禁止 Symbol 的构造函数
-    'no-new-symbol': 1,
-    // 禁止对 String,Number 和 Boolean 使用 new 操作符
-    'no-new-wrappers': 0,
     // 禁止把全局对象 (Math 和 JSON) 作为函数调用 错误:var math = Math();
     'no-obj-calls': 2,
     // 禁用八进制字面量
@@ -417,10 +448,6 @@ module.exports = {
     'no-self-compare': 2,
     // 禁用逗号操作符
     'no-sequences': 2,
-    // 禁止 var 声明 与外层作用域的变量同名
-    'no-shadow': 0,
-    // 禁止覆盖受限制的标识符
-    'no-shadow-restricted-names': 2,
     // 禁用稀疏数组
     'no-sparse-arrays': 2,
     // 禁用同步方法
@@ -450,7 +477,7 @@ module.exports = {
     // 禁用一成不变的循环条件
     'no-unmodified-loop-condition': 2,
     // 禁止可以在有更简单的可替代的表达式时使用三元操作符
-    'no-unneeded-ternary': 0,
+    'no-unneeded-ternary': 'error',
     // 禁止在return、throw、continue 和 break语句之后出现不可达代码
     /*
     function foo() {
@@ -473,8 +500,8 @@ module.exports = {
     'no-use-before-define': 0,
     // 禁止不必要的 .call() 和 .apply()
     'no-useless-call': 2,
-    // 禁止不必要的计算性能键对象的文字
-    'no-useless-computed-key': 2,
+    // 禁止在对象中使用不必要的计算
+    'no-useless-computed-key': 'error',
     // 禁止不必要的字符串字面量或模板字面量的连接
     'no-useless-concat': 0,
     // ES2015 会提供默认的类构造函数。因此,没有必要提供一个空构造函数或一个简单地委托给它的父类的构造函数,
@@ -485,8 +512,6 @@ module.exports = {
     'no-useless-rename': 2,
     // 多余的return语句。
     'no-useless-return': 0,
-    // 要求使用 let 或 const 而不是 var
-    'no-var': 0,
     // 禁用 void 操作符
     'no-void': 2,
     // 禁止在注释中使用特定的警告术语
@@ -520,9 +545,9 @@ module.exports = {
     // 要求使用箭头函数作为回调
     'prefer-arrow-callback': 0,
     // 要求使用 const 声明那些声明后不再被修改的变量
-    'prefer-const': 2,
-    // 采用两组配置对象。第一个对象参数决定了规则适用的解构类型
-    'prefer-destructuring': 0,
+    'prefer-const': 'error',
+    // 只强制对象解构,不强制数组解构
+    'prefer-destructuring': ['error', { object: true, array: false }],
     // 禁止调用parseInt()或Number.parseInt()使用两个参数调用:一个字符串; 和2(二进制),8(八进制)或16(十六进制)的基数选项。
     'prefer-numeric-literals': 2,
     // 确保承诺只被Error对象拒绝。
@@ -530,31 +555,31 @@ module.exports = {
     // 要求在合适的地方使用 Reflect 方法
     'prefer-reflect': 0,
     // Suggest using the rest parameters instead of arguments
-    'prefer-rest-params': 2,
+    'prefer-rest-params': 0,
     // 要求使用扩展运算符而非 .apply()
     'prefer-spread': 2,
     // 最大属性在新的一行
-    "max-attributes-per-line": 0,
-    "vue/max-attributes-per-line": 0,
+    'max-attributes-per-line': 0,
+    'vue/max-attributes-per-line': 0,
     // 要求使用模板字面量而非字符串连接
     // 老代码或使用习惯
     'prefer-template': 0,
     // 要求对象字面量属性名称用引号括起来
     'quote-props': 0,
     // 强制使用一致的反勾号、双引号或单引号
-    'quotes': 0,
+    quotes: 0,
     // 强制在parseInt()使用基数参数
-    'radix': 0,
+    radix: 0,
     // 异步函数必须具有await表达式
-    'require-await': 2,
+    'require-await': 0,
     // 要求使用 JSDoc 注释
     'require-jsdoc': 0,
     // 要求generator 函数内有 yield
     'require-yield': 2,
     // enforce spacing between rest and spread operators and their expressions
-    'rest-spread-spacing': 0,
+    'rest-spread-spacing': 'error',
     // 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
-    'semi': 0,
+    semi: 0,
     // 强制分号之前和之后使用一致的空格
     'semi-spacing': 0,
     // 强制分号位于配置的位置。
@@ -566,11 +591,11 @@ module.exports = {
     // 要求同一个声明块中的变量按顺序排列
     'sort-vars': 0,
     // 强制在块之前使用一致的空格
-    'space-before-blocks': [2,'always'],
+    'space-before-blocks': [2, 'always'],
     // 强制在 function的左括号之前使用一致的空格
-    'space-before-function-paren': [0,'always'],
+    'space-before-function-paren': [0, 'always'],
     // 强制在圆括号内使用一致的空格
-    'space-in-parens': [2,'never'],
+    'space-in-parens': [2, 'never'],
     // 要求操作符周围有空格
     'space-infix-ops': 2,
     // 强制在一元操作符前后使用一致的空格
@@ -578,7 +603,7 @@ module.exports = {
     // 强制在注释中 // 或 /* 使用一致的空格
     'spaced-comment': 0,
     // 要求或禁止使用严格模式指令
-    'strict': 0,
+    strict: 0,
     // switch语句内的空格
     'switch-colon-spacing': 2,
     // var foo = Symbol("some description"); 一定要有描述
@@ -605,6 +630,15 @@ module.exports = {
     // 强制在 yield* 表达式中 * 周围使用空格
     'yield-star-spacing': 2,
     // 要求或禁止 “Yoda” 条件
-    'yoda': 0,
-  } 
-}
+    yoda: 0,
+  },
+  overrides: [
+    {
+      files: ['*.vue'],
+      rules: {
+        // 要求组件名称总是多个单词
+        'vue/multi-word-component-names': 0,
+      },
+    },
+  ],
+});

+ 20 - 27
web/.gitignore

@@ -1,40 +1,33 @@
-.DS_Store
-node_modules/
-node_modules_qt/
-dist
-dist.zip
+# Logs
+logs
+*.log
 npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
-**/*.log
-.history/
-.git
-
-.env.development
-
-
-
-tests/**/coverage/
-tests/e2e/reports
-selenium-debug.log
-
-# Theme sytlesheet
-dist.min.css
+pnpm-debug.log*
+lerna-debug.log*
+temp
+node_modules
+.DS_Store
+dist
+dist-ssr
+ynsp_dev
+*.local
 
-# Demo
-assets/**/demo.css
-assets/**/demo_index.html
+/cypress/videos/
+/cypress/screenshots/
 
 # Editor directories and files
 .idea
-.vscode
-.setting
-.prettierrc
 *.suo
 *.ntvs*
 *.njsproj
 *.sln
-*.local
+*.sw?
 
 package-lock.json
-yarn.lock
+
+components.d.ts
+auto-imports.d.ts
+dist/
+dist.zip

+ 10 - 3
web/.npmrc

@@ -1,3 +1,10 @@
-registry=http://210.12.198.156:9036/repository/yx-npm-group/
-_auth=eXVzcC1ndWVzdDp5dXNwLWd1ZXN0==
-always-auth=true
+# 私有仓库-外网开发地址
+#registry=http://210.12.198.156:9036/repository/yx-npm-group/
+registry=https://nexus.yangzhiqiang.tech/repository/yx-npm-group/
+# 私有仓库-云桌面开发地址
+#registry=http://192.168.36.21:9018/repository/yx-npm-group/
+# 私有仓库-认证令牌
+//nexus.yangzhiqiang.tech/repository/yx-npm-group/:_auth=eXV4cDp5dXhwJTIwMjE===
+# 始终进行身份验证的配置选项
+always-auth=true
+strict-ssl=false

+ 4 - 7
web/.postcssrc.js

@@ -1,8 +1,5 @@
-// https://github.com/michael-ciniawsky/postcss-load-config
-
 module.exports = {
-  "plugins": {
-    // to edit target browsers: use "browserslist" field in package.json
-    "autoprefixer": {}
-  }
-}
+  plugins: {
+    autoprefixer: {},
+  },
+};

+ 21 - 0
web/.prettierignore

@@ -0,0 +1,21 @@
+## OS
+.DS_Store
+.idea
+.editorconfig
+pnpm-lock.yaml
+.npmrc
+
+# Ignored suffix
+*.log
+*.md
+*.svg
+*.png
+*.ico
+*ignore
+
+## Local
+.husky
+
+## Built-files
+.cache
+dist

+ 43 - 0
web/.prettierrc.js

@@ -0,0 +1,43 @@
+module.exports = {
+  // 一行最多 120 字符
+  printWidth: 120,
+  // 使用 2 个空格缩进
+  tabWidth: 2,
+  // 不使用缩进符,而使用空格
+  useTabs: false,
+  // 行尾需要有分号
+  semi: true,
+  // 使用单引号
+  singleQuote: true,
+  // 对象的 key 仅在必要时用引号
+  quoteProps: 'as-needed',
+  // jsx 不使用单引号,而使用双引号
+  jsxSingleQuote: false,
+  // 末尾需要有逗号
+  trailingComma: 'all',
+  // 大括号内的首尾需要空格
+  bracketSpacing: true,
+  // jsx 标签的反尖括号需要换行
+  jsxBracketSameLine: false,
+  // 箭头函数,只有一个参数的时候,也需要括号
+  arrowParens: 'always',
+  // 每个文件格式化的范围是文件的全部内容
+  rangeStart: 0,
+  rangeEnd: Infinity,
+  // 不需要写文件开头的 @prettier
+  requirePragma: false,
+  // 不需要自动在文件开头插入 @prettier
+  insertPragma: false,
+  // 使用默认的折行标准
+  proseWrap: 'preserve',
+  // 根据显示样式决定 html 要不要折行
+  htmlWhitespaceSensitivity: 'css',
+  // vue 文件中的 script 和 style 内不用缩进
+  vueIndentScriptAndStyle: false,
+  // 换行符使用 lf
+  endOfLine: 'auto',
+  // 格式化嵌入的内容
+  embeddedLanguageFormatting: 'auto',
+  // html, vue, jsx 中每个属性占一行
+  singleAttributePerLine: false,
+};

+ 16 - 0
web/.stylelintignore

@@ -0,0 +1,16 @@
+# .stylelintignore
+# 旧的不需打包的样式库
+*.min.css
+
+# 其他类型文件
+*.js
+*.jpg
+*.woff
+
+# 测试和打包目录
+/test/
+/dist/*
+/public/*
+public/*
+/node_modules/
+*.min.js

+ 0 - 5
web/.travis.yml

@@ -1,5 +0,0 @@
-language: node_js
-node_js: stable
-script: npm run test
-notifications:
-  email: false

+ 132 - 0
web/.xy-tsd.js

@@ -0,0 +1,132 @@
+module.exports = {
+  // 1.全落标:对工程直接替换;2.半落标:增加接口请求获取配置数据,并增加拦截请求方法处理,
+  tsdType: 1,
+  // 基础文件夹路径,如src/
+  basePath: '/src',
+  // 排除处理文件,正则表达式或文件名数组, 默认排除多语言
+  exclude: ['/{api,config,assets,locale,router,store,utils}/**/*.js', '/views/batch/require/*.js', '/views/@xdjf/**/{api,config,assets,locale,router,store,utils}/**/*.js', '/views/framework/Login/index.vue'],
+  // 配置文件路径
+  config: '.xy-tsd.js',
+  // 要处理的文件类型, 可以多个,数组形式,配置参考https://github.com/isaacs/node-glob
+  include: '**/*.@(vue|js)',
+  // 自闭标签列表
+  selfClosingTag: ['img', 'hr', 'br', 'svg-icon', 'input'],
+  // 生成结果目标文件夹
+  output: '/temp',
+  // 映射关系数据来源api/excel,api则调用接口获取数据,excel则解析excel文件类获取映射数据
+  type: 'excel',
+  api: {
+    server: 'http://192.168.50.60:30355',
+    // 默认post请求, 支持对象类型及函数配置
+    rules: '/api/metaChangeStdField/listallchangefield',
+    // 字典映射列表
+    datacodes: '/api/metaChangeDict',
+    // 接口映射列表
+    apis: '/api/metaChangeStdField/listchangeapi',
+  },
+  excel: {
+    // 文件名
+    path: '标准字段差异数据.xlsx',
+    // 类型与excel标签页映射关系  目前仅仅实现了 field data-code 无效
+    types: {
+      field: '差异标准字段',
+      'data-code': '字典映射',
+    },
+    // 该映射关系需要与 config.map 对应上
+    // excel列与字段映射关系
+    map: {
+      field: {
+        // 模块英文名: 'moduleName',
+        // 模块中文名: 'moduleNameCn',
+        // 表名: 'tableName',
+        // 表中文名: 'tableNameCn',
+        '原字段英文名': 'oldName',
+        '新字段英文名': 'newName',
+        '原字段中文名': 'oldNameCn',
+        '新字段中文名': 'newNameCn',
+        // 字段类型: 'oldDbType',
+        // 标准字段类型: 'newDbType',
+        // 字典编号: 'oldCode',
+        // 标准字典编号: 'newCode',
+      },
+      // 'data-code': {
+      //   字典编号: 'oldCode',
+      //   字典中文名: 'oldName',
+      //   选项名称: 'oldItemName',
+      //   选项码值: 'oldItemValue',
+      //   标准字典编号: 'newCode',
+      //   标准字典中文名: 'newName',
+      //   标准选项名称: 'newItemName',
+      //   标准选项码值: 'newItemValue',
+      // },
+    },
+  },
+  // 日志记录配置
+  logs: {
+    path: 'logs/data-standard.log',
+  },
+  // 1.全量字段类型;2.接口类型
+  rulesType: 1,
+  // 规则判断主键
+  rulesPrimarykey: {
+    field: 'oldName',
+    'data-code': 'oldCode',
+  },
+  // 转换规则
+  rules: null,
+  // 是否需要转驼峰
+  isHump: true,
+  // 特殊关键词,指定特殊标记,在template中,内容与特殊关键词相同的,需要进行转换处理,但关键词本身不处理
+  specialKeywords: ['scope.row'],
+  // js代码中替换信息
+  map: {
+    dataCodeMap: ['oldCode', 'newCode'], // 数据字典映射关系
+    jsMap: ['oldName', 'newName'],
+    tempMap: ['oldName', 'newName'], // 模板通用
+    // 表单对应key替换信息,数组第一个为比较值,第二个为更新值
+    xformMap: {
+      label: ['oldNameCn', 'newNameCn'],
+      name: ['oldName', 'newName'],
+      'data-code': ['oldCode', 'newCode'],
+    },
+    // 表格对应key替换信息,数组第一个为比较值,第二个为更新值
+    xtableMap: {
+      label: ['oldNameCn', 'newNameCn'],
+      prop: ['oldName', 'newName'],
+    },
+  },
+  // 数据字典
+  datacodes: null,
+  // 接口映射数据,用于接口路径、接口请求类型修改
+  apis: null,
+  // 添加JavaScript默认解析器
+  jsVisitor: function () {
+    return {};
+  },
+  // js 特殊需要处理过滤器
+  jsFilters: function () {
+    return false;
+  },
+  // js 特殊需要处理过滤器
+  tempFilters: function () {
+    return false;
+  },
+  // 添加模板默认解析器
+  tempVisitor: [
+    {
+      tag: 'yu-xform',
+      childrenTag: 'yu-xform-item',
+      // 先不处理数据字典
+      KEYS: ['label', 'name', 'data-code'],
+      mapKey: 'xformMap',
+    },
+    {
+      tag: 'yu-xtable',
+      childrenTag: 'yu-xtable-column',
+      // KEYS: ['prop', 'data-code'],
+      // 先不处理数据字典
+      KEYS: ['label', 'prop'],
+      mapKey: 'xtableMap',
+    },
+  ],
+};

+ 4 - 3
web/.yarnrc

@@ -1,3 +1,4 @@
-registry "http://210.12.198.156:9036/repository/yx-npm-group/"
-_auth=eXVzcC1ndWVzdDp5dXNwLWd1ZXN0==
-always-auth=true
+#registry=http://210.12.198.156:9036/repository/yx-npm-group/
+registry=https://nexus.yangzhiqiang.tech/repository/yx-npm-group/
+disable-self-update-check true
+strict-ssl=false

+ 17 - 0
web/Dockerfile

@@ -0,0 +1,17 @@
+# FROM nginx:1.25-alpine
+ARG PROXY_URL=http://172.16.40.16:7280
+
+FROM public.ecr.aws/docker/library/nginx:1.25-alpine
+# 设置代理环境变量(在构建和运行时都生效)
+ENV http_proxy=${PROXY_URL} \
+    https_proxy=${PROXY_URL} \
+    HTTP_PROXY=${PROXY_URL} \
+    HTTPS_PROXY=${PROXY_URL}
+#将dist目录内容复制到nginx容器html内部
+COPY dist /usr/share/nginx/html/
+# 复制nginx配置文件
+COPY default.conf /etc/nginx/conf.d/
+#COPY dns.sh /docker-entrypoint.d/dns.sh
+#RUN chmod +x /docker-entrypoint.d/dns.sh
+
+EXPOSE 80

+ 2 - 39
web/LICENSE

@@ -1,6 +1,6 @@
-The MIT License (MIT)
+MIT License
 
-Copyright (c) 2017-present Yusys.
+Copyright (c) 2024 Yusys
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -19,40 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
-
-
-
-Copyright Notice and License Texts
-
-Software: vue
-Copyright notice: 
-Copyright (c) 2013-present, Yuxi (Evan) You
-License: The MIT License.
-Copyright (c) <year> <copyright holders>
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-Software: element
-Copyright notice:
-Copyright (c) 2016-present ElemeFE
-License: The MIT License.
-Please see above
-
-Software: vue-element-admin 4.0.0
-Copyright notice:
-Copyright (c) 2017-present PanJiaChen
-License: The MIT License.
-Please see above

+ 0 - 12
web/babel.config.js

@@ -1,12 +0,0 @@
-module.exports = {
-  presets: [
-    [
-      "@vue/app",
-      {
-        useBuiltIns: "usage",
-        corejs: { version: "3", proposals: true },
-      },
-    ],
-  ],
-  plugins: [["@babel/plugin-syntax-dynamic-import"]],
-};

+ 94 - 0
web/build-proxy.js

@@ -0,0 +1,94 @@
+const _toString = Object.prototype.toString;
+/**
+ * 开发模式,获取代理配置
+ * 注: 若配置项VUE_APP_BASE_API包含协议、域名[IP]、端口[可选],开发模式则默认配置代理
+ */
+export function getProxyConfig(env, insertYuxpProxy) {
+  let baseApi = env.VITE_APP_BASE_AUTH || ''; // 应用服务前缀URL
+  let proxyPrefix = env.VITE_APP_PROXY_API || ''; // 代理API前缀
+
+  const match = /^(https?:\/\/[0-9a-z.-]+)(:[0-9]+)?([/0-9a-z.]+)?/i; // 匹配URL(协议+域名+端口)
+  // 可数组配置多个
+  proxyPrefix = proxyPrefix.split(',');
+  if (_toString.call(baseApi) === '[object String]') {
+    baseApi = baseApi.split(',').filter((api) => {
+      return match.test(api);
+    });
+  }
+  let devServerProxy = {
+    '^/zzh-api': {
+      target: 'http://39.106.34.227',
+      changeOrigin: true,
+      logLevel: 'debug',
+      rewrite: (path) => {
+        return path.replace(/^\/zzh-api/, '');
+      },
+    },
+    '^/zzh': {
+      target: 'http://39.106.34.227',
+      changeOrigin: true,
+      logLevel: 'debug',
+      rewrite: (path) => {
+        return path.replace(/^\/zzh/, '');
+      },
+    },
+    '^/rest': {
+      target: 'http://39.106.34.227',
+      changeOrigin: true,
+    },
+    '^/api/infra_be/': {
+      target: ' http://dohb.exocr.com',
+      changeOrigin: true,
+    },
+    '^/agentfile': {
+      target: 'http://10.192.72.11:9000/static',
+      changeOrigin: true,
+      rewrite: (path) => {
+        return path.replace(/^\/agentfile/, '');
+      },
+    },
+    '^/ai-manage-web': {
+      target: 'http://210.12.198.139:9090',
+      changeOrigin: true,
+    },
+    '^/ai-oca': {
+      target: 'http://210.12.198.139:9090',
+      changeOrigin: true,
+    },
+    '^/ai-trans-analysis': {
+      target: 'http://210.12.198.139:9090',
+      changeOrigin: true,
+    },
+    '^/ai-trans-mgr': {
+      target: 'http://210.12.198.139:9090',
+      changeOrigin: true,
+    },
+  };
+  const l = baseApi.length - 1;
+  if (l > -1) {
+    // VUE_APP_BASE_API代理配置演示,/dev-proxy-api/xxx-api/* => https://172.16.20.92:8102/xxx-api/*
+    // 详见: https://cli.vuejs.org/config/#devserver-proxy
+    if (Array.isArray(proxyPrefix)) {
+      proxyPrefix.forEach((item, idx) => {
+        const matchResult = match.exec(baseApi[idx > l ? l : idx]);
+        devServerProxy[`^(?!.*package\/api\/)(/(?!src)[a-zA-Z-]+)*${item}.*`] = {
+          target: `${matchResult.input}`, // + (matchResult[3] || '')}`, // 协议+域名
+          changeOrigin: true,
+        };
+      });
+    }
+  }
+
+  insertYuxpProxy(devServerProxy);
+  const proxyShuffleApi = (env.VITE_APP_PROXY_SHUFFLE || '').split(',');
+  proxyShuffleApi.forEach((item) => {
+    // 正则表达式,添加给定代理的前缀服务模式匹配 业务功能香瓜接口代理到相应后端
+    devServerProxy[`^(?!.*package\/api\/)(/(?!src)[a-zA-Z-]+)*${item}.*`] = {
+      target: env.VITE_APP_BASE_API,
+    };
+  });
+
+  devServerProxy = JSON.stringify(devServerProxy) == '{}' ? false : devServerProxy;
+  console.log('配置代理', devServerProxy);
+  return devServerProxy;
+}

+ 0 - 36
web/build/index.js

@@ -1,36 +0,0 @@
-const { run } = require('runjs')
-const chalk = require('chalk')
-const config = require('../vue.config.js')
-
-const rawArgv = process.argv.slice(2)
-const args = rawArgv.join(' ')
-
-if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
-  const report = rawArgv.includes('--report')
-
-  run(`vue-cli-service build ${args}`)
-
-  const port = config.devServer.port
-  const publicPath = config.publicPath
-
-  var connect = require('connect')
-  var serveStatic = require('serve-static')
-  const app = connect()
-
-  app.use(
-    publicPath,
-    serveStatic('./dist1', {
-      index: ['index.html', '/']
-    })
-  )
-
-  app.listen(port, function () {
-    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
-    if (report) {
-      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}/report.html`))
-    }
-
-  })
-} else {
-  run(`vue-cli-service build ${args}`)
-}

+ 60 - 0
web/commitlint.config.js

@@ -0,0 +1,60 @@
+/**
+ * feat:新增功能
+ * fix:修复缺陷
+ * docs:文档更新
+ * style:不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)
+ * refactor:代码重构
+ * perf:性能提升
+ * test:测试相关
+ * build:构建相关
+ * ci:持续集成
+ * chore:不属于以上类型的其他类型,比如构建流程, 依赖管理
+ * revert:回退代码
+ */
+/** @type {import('cz-git').UserConfig} */
+module.exports = {
+  extends: ['@commitlint/config-conventional'],
+  rules: {
+    'body-leading-blank': [2, 'always'],
+    'footer-leading-blank': [1, 'always'],
+    'subject-empty': [2, 'never'],
+    'type-empty': [2, 'never'],
+    'subject-case': [0, 'never'],
+    'type-enum': [
+      2,
+      'always',
+      ['feat', 'fix', 'perf', 'style', 'docs', 'test', 'refactor', 'build', 'ci', 'chore', 'revert', 'release'],
+    ],
+  },
+  prompt: {
+    messages: {
+      type: '选择你要提交的类型 :',
+      scope: '选择一个提交范围(可选):',
+      customScope: '请输入自定义的提交范围 :',
+      subject: '填写简短精炼的变更描述 :\n',
+      body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
+      breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
+      footerPrefixsSelect: '选择关联issue前缀(可选):',
+      customFooterPrefixs: '输入自定义issue前缀 :',
+      footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
+      confirmCommit: '是否提交或修改commit ?',
+    },
+    types: [
+      { value: 'feat', name: 'feat:     新增功能 | A new feature' },
+      { value: 'fix', name: 'fix:      修复缺陷 | A bug fix' },
+      { value: 'docs', name: 'docs:     文档更新 | Documentation only changes' },
+      { value: 'style', name: 'style:    代码格式 | Changes that do not affect the meaning of the code' },
+      { value: 'refactor', name: 'refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature' },
+      { value: 'perf', name: 'perf:     性能提升 | A code change that improves performance' },
+      { value: 'test', name: 'test:     测试相关 | Adding missing tests or correcting existing tests' },
+      { value: 'build', name: 'build:    构建相关 | Changes that affect the build system or external dependencies' },
+      { value: 'ci', name: 'ci:       持续集成 | Changes to our CI configuration files and scripts' },
+      { value: 'revert', name: 'revert:   回退代码 | Revert to a commit' },
+      { value: 'chore', name: 'chore:    其他修改 | Other changes that do not modify src or test files' },
+    ],
+    useEmoji: false,
+    emojiAlign: 'center',
+    allowCustomIssuePrefixs: false,
+    allowEmptyIssuePrefixs: false,
+  },
+};

+ 41 - 0
web/default.conf

@@ -0,0 +1,41 @@
+server {
+    listen       80;
+    
+    # MCP 健康检查接口 - 使用prefix路径 (必须在通用路径之前)
+    # location /inspector/api {
+    #     proxy_pass http://mcp-inspector:6277;
+    #     proxy_set_header Host $host;
+    #     proxy_set_header X-Real-IP $remote_addr;
+    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    #     proxy_set_header X-Forwarded-Proto $scheme;
+    # }
+
+    # MCP Inspector 前端服务 (6274端口) - 使用prefix路径 (必须在最后,作为通用匹配)
+    # location /inspector {
+    #     proxy_pass http://mcp-inspector:6274;
+    #     proxy_set_header Host $host;
+    #     proxy_set_header X-Real-IP $remote_addr;
+    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    #     proxy_set_header X-Forwarded-Proto $scheme;
+    # }
+
+    # location /yusysflow/ {
+    #     proxy_pass http://frontend:80/;   # 注意末尾的 /
+    #     proxy_set_header Host $host;
+    #     proxy_set_header X-Real-IP $remote_addr;
+    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    #     proxy_set_header X-Forwarded-Proto $scheme;
+    # }
+
+
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html index.htm;
+        add_header Cache-Control "no-cache";
+    }
+
+    # error_page   500 502 503 504  /50x.html;
+    # location = /50x.html {
+    #     root   /usr/share/nginx/html;
+    # }
+}

+ 29 - 0
web/dev.sh

@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# --- 分支检查 ---
+CUR_BRANCH=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse --abbrev-ref HEAD)
+[ "$CUR_BRANCH" = "main" ] || { echo "❌ 仅允许在 main 分支打包!"; exit 1; }
+
+# --- 交互式拉代码 ---
+echo "→ 正在 git pull,请输入用户名/密码 ..."
+git pull origin main || { echo "❌ git pull 失败"; exit 1; }
+
+# --- 后续流程 ---
+npm run build || { echo "❌ npm run build 失败"; exit 1; }
+
+# 停止并删除现有容器
+echo "🛑 停止现有容器..."
+docker-compose down
+
+# 构建并启动服务
+echo "🔨 构建并启动服务..."
+docker-compose up -d --build
+
+# 检查服务状态
+echo ""
+echo "📊 服务状态:"
+docker-compose ps
+
+echo ""
+echo "✅ 服务启动完成!"
+

+ 5 - 0
web/dns.sh

@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export NAME_SERVER=`awk '/^nameserver/{print $2}' /etc/resolv.conf`
+
+sed -i  "s#{NAME_SERVER}#${NAME_SERVER}#g" /etc/nginx/conf.d/default.conf

+ 15 - 0
web/docker-compose.yml

@@ -0,0 +1,15 @@
+services:
+  yusys-ai-web:
+    build: .
+    container_name: yusys-ai-web
+    ports:
+      - '20003:80' # 外部端口20003映射到内部端口8080
+    restart: unless-stopped
+#     networks:
+#       - app-network
+
+# networks:
+#   app-network:
+#     name: app-network
+#     driver: bridge
+#     external: true

+ 0 - 962
web/docs.md

@@ -1,962 +0,0 @@
-
-## 浅谈 Vue 中构建前端 10w+ 代码量的单页面应用
-
-随着业务的不断累积,目前我们 `ToC 端主要项目`,除去 `node_modules`, `build 配置文件`,`dist 静态资源文件`的代码量为 `137521` 行,除去统计过渡的代码,也在 `10w+` 行,`后台管理系统下各个子应用代码`除去依赖等文件的总行数也达到 `100w` 多一点。
-
-本文会在主要描述构建过程中,遇到或者总结的点,可能并不适合你的业务场景,我会尽可能多的描述问题与其中的思考,最大可能的帮助到需要的开发者,也辛苦开发者发现问题或者不合理/不正确的地方及时向我反馈,会尽快修改,末尾会附上 `github` 地址,欢迎有更好的实现方式来 `pr`。
-
-
-### ① 单页面,多页面 
-首先要思考我们的项目最终的`构建主体`是`单页面`,还是`多页面`,还是`单页 + 多页`,通过他们的优缺点来分析:
-
-- **单页面(SPA)**
-    - 优点:体验好,路由之间跳转流程,可定制转场动画,使用了`懒加载`可有效减少首页白屏时间,相较于`多页面`减少了用户访问静态资源服务器的次数等。
-    - 缺点:初始会加载较大的静态资源,并且随着业务增长会越来越大,`懒加载`也有他的弊端,不做特殊处理不利于 SEO 等。
-- **多页面(MPA)**:
-    - 优点:对搜索引擎友好,开发难度较低。
-    - 缺点:资源请求较多,整页刷新体验较差,页面间传递数据只能依赖 `URL`,`cookie`,`storage` 等方式,较为局限。
-- **SPA + MPA**
-    - 这种方式常见于较`老 MPA 项目迁移至 SPA 的情况`,缺点结合两者,两种主体通信方式也只能以兼容`MPA 为准`
-    - 不过这种方式也有他的好处,假如你的 SPA 中,有类似文章分享这样(没有后端直出,后端返 `HTML 串`的情况下),想保证用户体验在 SPA 中开发一个页面,在 MPA 中也开发一个页面,去掉没用的依赖,或者直接用原生 JS 来开发,分享出去是 MPA 的文章页面,这样可以**加快分享出去的打开速度,同时也能减少静态资源服务器的压力**,因为如果分享出去的是 SPA 的文章页面,那 SPA 所需的静态资源`至少都需要去进行协商请求`,当然如果服务配置了强缓存就忽略以上所说。
-    
-我们首先根据业务所需,来最终确定`构建主体`,而我们选择了`体验至上的 SPA`,并选用 `Vue` 技术栈。
-
-### ② 目录结构
-其实我们看开源的绝大部分项目中,目录结构都会差不太多,我们可以综合一下来个通用的 `src` 目录:
-
-```
-src
-├── assets          // 资源目录 图片,样式,iconfont
-├── common          // 全局业务组件目录
-├── components      // 全局通用组件目录
-├── config          // 项目配置,拦截器,开关
-├── plugins         // 插件相关,生成路由、请求、store 等实例,并挂载 Vue 实例
-├── directives      // 拓展指令集合
-├── routes          // 路由配置
-├── service         // 服务层
-├── utils           // 工具类
-└── views           // 视图层
-```
-
-### ③ 业务组件,通用组件
-#### 业务组件
-`common` 中我们通常会存放如的 header,footer 等根据业务会自定义一些特有样式或逻辑,无法供其他业务使用的组件。
-
-#### 通用组件
-`components` 中我们会存放 UI 组件库中的那些常见通用组件了,在项目中直接通过设置`别名`来使用,如果其他项目需要使用,就发到 `npm` 上。
-
-#### 结构
-
-```
-// components 简易结构
-components
-├── dist
-├── build
-├── src      
-    ├── modal
-    ├── toast
-    └── ...
-├── index.js             
-└── package.json        
-```
-
-#### 项目中使用
-
-如果想最终编译成 `es5`,直接在 html 中使用或者部署 CDN 上,在 `build` 配置简单的打包逻辑,搭配着 `package.json` 构建 UI组件 的自动化打包发布,最终部署 `dist` 下的内容,并发布到 `npm` 上即可。
-
-而我们也可直接使用 `es6` 的代码:
-
-```js
-import 'Components/src/modal'
-```
-其他项目使用,假设我们发布的 `npm 包`叫 `bm-ui`,并且下载到了本地 `npm i bm-ui -S`:
-
-修改项目的最外层打包配置,在 rules 里 `babel-loader` 或 `happypack` 中添加 `include`,`node_modules/bm-ui`:
-
-```js
-// webpack.base.conf
-...
-    rules: [{
-        test: /\.vue$/,
-        loader: 'vue-loader',
-        options: vueLoaderConfig
-    },
-    {
-        test: /\.js$/,
-        loader: 'babel-loader',
-        // 这里添加
-        include: [resolve('src'), resolve('test'), resolve('node_modules/bm-ui')]
-    },{
-    ...
-    }]
-...
-```
-然后直接在项目中使用即可:
-```
-import { modal } from 'bm-ui'
-```
-
-#### 多个组件库
-同时有多个组件库的话,又或者有同学专门进行组件开发的话,把 `components
-内部细分`一下,多一个文件分层。
-
-```
-components
-├── bm-ui-1 
-├── bm-ui-2
-└── ...
-```
-你的打包配置文件可以放在 `components` 下,进行统一打包,当然如果要开源出去还是放在对应库下。
-
-### ④ 全局配置,插件与拦截器
-这个点其实会是项目中经常被忽略的,或者说很少聚合到一起,但同时我认为是**整个项目中重要之一**。
-
-#### 全局配置,拦截器目录结构
-```
-config
-├── base.js             // 全局配置/开关
-├── routes              // 路由表关系配置
-├── interceptors        // 拦截器
-    ├── index.js        // 入口文件
-    ├── axios.js        // 请求/响应拦截
-    ├── router.js       // 路由拦截
-    └── ...
-└── ...
-
-```
-#### 全局配置
-我们在 `config/index.js` 可能会有如下配置:
-
-```js
-// config/index.js
-
-// 当前宿主平台
-export const HOST_PLATFORM = 'WEB'
-// 这个就不多说了
-export const NODE_ENV = process.env.NODE_ENV || 'prod'
-
-// 是否强制所有请求访问本地 MOCK,看到这里同学不难猜到,每个请求也可以单独控制是否请求 MOCK
-export const AJAX_LOCALLY_ENABLE = false
-// 是否开启监控
-export const MONITOR_ENABLE = true
-// 路由默认配置,路由表并不从此注入
-export const ROUTER_DEFAULT_CONFIG = {
-    waitForData: true,
-    transitionOnLoad: true
-}
-
-// axios 默认配置
-export const AXIOS_DEFAULT_CONFIG = {
-    timeout: 20000,
-    maxContentLength: 2000,
-    headers: {}
-}
-
-// vuex 默认配置
-export const VUEX_DEFAULT_CONFIG = {
-    strict: process.env.NODE_ENV !== 'production'
-}
-
-// API 默认配置
-export const API_DEFAULT_CONFIG = {
-    mockBaseURL: '',
-    mock: true,
-    debug: false,
-    sep: '/'
-}
-
-// CONST 默认配置
-export const CONST_DEFAULT_CONFIG = {
-    sep: '/'
-}
-
-// 还有一些业务相关的配置
-// ...
-
-
-// 还有一些方便开发的配置
-export const CONSOLE_REQUEST_ENABLE = true      // 开启请求参数打印
-export const CONSOLE_RESPONSE_ENABLE = true     // 开启响应参数打印
-export const CONSOLE_MONITOR_ENABLE = true      // 监控记录打印
-```
-可以看出这里汇集了项目中**所有用到的配置**,下面我们在 `plugins` 中实例化插件,注入对应配置,目录如下:
-
-#### 插件目录结构
-```
-plugins
-├── inject.js           // 注入 Vue 原型插件
-├── router.js           // 路由实例插件
-├── axios.js            // 请求实例插件
-├── api.js              // 服务层 api 插件
-├── const.js            // 服务层 const 插件
-└── store.js            // vuex 实例插件
-```
-#### 实例化插件并注入配置
-这里先举出两个例子,看我们是如何注入配置,拦截器并实例化的
-
-实例化 `router`:
-```js
-import Vue from 'vue'
-import Router from 'vue-router'
-import ROUTES from 'Routes'
-import {ROUTER_DEFAULT_CONFIG} from 'Config/index'
-import {routerBeforeEachFunc} from 'Config/interceptors/router'
-
-Vue.use(Router)
-
-// 注入默认配置和路由表
-let routerInstance = new Router({
-    ...ROUTER_DEFAULT_CONFIG,
-    routes: ROUTES
-})
-// 注入拦截器
-routerInstance.beforeEach(routerBeforeEachFunc)
-
-export default routerInstance
-
-```
-
-实例化 `axios`:
-
-```js
-import axios from 'axios'
-import {AXIOS_DEFAULT_CONFIG} from 'Config/index'
-import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from 'Config/interceptors/axios'
-
-let axiosInstance = {}
-
-axiosInstance = axios.create(AXIOS_DEFAULT_CONFIG)
-
-// 注入请求拦截
-axiosInstance
-    .interceptors.request.use(requestSuccessFunc, requestFailFunc)
-// 注入失败拦截
-axiosInstance
-    .interceptors.response.use(responseSuccessFunc, responseFailFunc)
-
-export default axiosInstance
-```
-
-
-我们在 `main.js` **注入插件**:
-
-```js
-// main.js
-import Vue from 'vue'
-
-GLOBAL.vbus = new Vue()
-
-// import 'Components'// 全局组件注册
-import 'Directives' // 指令
-
-// 引入插件
-import router from 'Plugins/router'
-import inject from 'Plugins/inject'
-import store from 'Plugins/store'
-// 引入组件库及其组件库样式
-import VueOnsen from 'vue-onsenui'
-import 'onsenui/css/onsenui.css'
-import 'onsenui/css/onsen-css-components.css'
-// 引入根组件
-import App from './App'
-
-Vue.use(inject)
-Vue.use(VueOnsen)
-
-// render
-new Vue({
-    el: '#app',
-    router,
-    store,
-    template: '<App/>',
-    components: { App }
-})
-
-```
-
-`axios` 实例我们并没有直接引用,相信你也猜到他是通过 `inject` 插件引用的,我们看下 `inject`:
-```js
-import axios from './axios'
-import api from './api'
-import consts from './const'
-GLOBAL.ajax = axios
- 
-export default {
-    install: (Vue, options) => {
-        Vue.prototype.$api = api
-        Vue.prototype.$ajax = axios
-        Vue.prototype.$const = consts
-        // 需要挂载的都放在这里
-    }
-}
-```
-
-这里可以挂载你想在业务中( `vue` 实例中)便捷访问的 `api`,除了 `$ajax` 之外,`api` 和 `const` 两个插件是我们**服务层中主要的功能**,后续会介绍,这样我们插件流程大致运转起来,下面写对应拦截器的方法。
-
-#### 请求,路由拦截器
-在`ajax 拦截器`中(`config/interceptors/axios.js`):
-```js
-// config/interceptors/axios.js
-
-import {CONSOLE_REQUEST_ENABLE, CONSOLE_RESPONSE_ENABLE} from '../index.js'
-
-export function requestSuccessFunc (requestObj) {
-    CONSOLE_REQUEST_ENABLE && console.info('requestInterceptorFunc', `url: ${requestObj.url}`, requestObj)
-    // 自定义请求拦截逻辑,可以处理权限,请求发送监控等
-    // ...
-    
-    return requestObj
-}
-
-export function requestFailFunc (requestError) {
-    // 自定义发送请求失败逻辑,断网,请求发送监控等
-    // ...
-    
-    return Promise.reject(requestError);
-}
-
-export function responseSuccessFunc (responseObj) {
-    // 自定义响应成功逻辑,全局拦截接口,根据不同业务做不同处理,响应成功监控等
-    // ...
-    // 假设我们请求体为
-    // {
-    //     code: 1010,
-    //     msg: 'this is a msg',
-    //     data: null
-    // }
-    
-    let resData =  responseObj.data
-    let {code} = resData
-    
-    switch(code) {
-        case 0: // 如果业务成功,直接进成功回调  
-            return resData.data;
-        case 1111: 
-            // 如果业务失败,根据不同 code 做不同处理
-            // 比如最常见的授权过期跳登录
-            // 特定弹窗
-            // 跳转特定页面等
-            
-            location.href = xxx // 这里的路径也可以放到全局配置里
-            return;
-        default:
-            // 业务中还会有一些特殊 code 逻辑,我们可以在这里做统一处理,也可以下方它们到业务层
-            !responseObj.config.noShowDefaultError && GLOBAL.vbus.$emit('global.$dialog.show', resData.msg);
-            return Promise.reject(resData);
-    }
-}
-
-export function responseFailFunc (responseError) {
-    // 响应失败,可根据 responseError.message 和 responseError.response.status 来做监控处理
-    // ...
-    return Promise.reject(responseError);
-}
-```
-
-定义`路由拦截器`(`config/interceptors/router.js`):
-
-```js
-// config/interceptors/router.js
-
-export function routerBeforeFunc (to, from, next) {
-    // 这里可以做页面拦截,很多后台系统中也非常喜欢在这里面做权限处理
-    
-    // next(...)
-}
-```
-最后在`入口文件(config/interceptors/index.js)`中引入并暴露出来即可:
-
-```js
-import {requestSuccessFunc, requestFailFunc, responseSuccessFunc, responseFailFunc} from './ajax'
-import {routerBeforeEachFunc} from './router'
-
-let interceptors = {
-    requestSuccessFunc,
-    requestFailFunc,
-    responseSuccessFunc,
-    responseFailFunc,
-    routerBeforeEachFunc
-}
-
-export default interceptors
-```
-
-请求拦截这里代码都很简单,对于 `responseSuccessFunc` 中 switch `default` 逻辑做下简单说明:
-
-1. `responseObj.config.noShowDefaultError` 这里可能不太好理解
-我们在请求的时候,可以传入一个 axios 中并没有意义的 `noShowDefaultError` 参数为我们业务所用,当值为 false 或者不存在时,我们会触发全局事件 `global.dialog.show`,`global.dialog.show`我们会注册在 `app.vue` 中:
-
-```js
-// app.vue
-
-export default {
-    ...
-    created() {
-        this.bindEvents
-    },
-    methods: {
-        bindEvents() {
-            GLOBAL.vbus.$on('global.dialog.show', (msg) => {
-                if(msg) return
-                // 我们会在这里注册全局需要操控试图层的事件,方便在非业务代码中通过发布订阅调用
-                this.$dialog.popup({
-                    content: msg 
-                });
-            })
-        }
-        ...
-    }
-}
-```
-
-2. `GLOBAL` 是我们挂载 `window` 上的`全局对象`,我们把需要挂载的东西都放在 `window.GLOBAL` 里,减少命名空间冲突的可能。
-3. `vbus` 其实就是我们开始 `new Vue()` 挂载上去的
-```
-GLOBAL.vbus = new Vue()
-```
-4. 我们在这里 `Promise.reject` 出去,我们就可以在 `error` 回调里面只处理我们的业务逻辑,而其他如`断网`、`超时`、`服务器出错`等均通过拦截器进行统一处理。
-
-可以对比下**处理前后在业务中的使用代码**:
-
-**拦截器处理前**:
-```js
-this.$axios.get('test_url').then(({code, data}) => {
-    if( code === 1010 ) {
-        // 业务成功
-    } else if ()
-        // em... 各种业务不成功处理,如果遇到通用的处理,还需要抽离出来
-    
-    
-}, error => {
-   // 需要根据 error 做各种抽离好的处理逻辑,断网,超时等等...
-})
-```
-
-**拦截器处理后**:
-
-```js
-<!--业务失败走默认弹窗逻辑的情况-->
-this.$axios.get('test_url').then(({data}) => {
-    // 业务成功,直接操作 data 即可
-    
-})
-
-<!--业务失败自定义-->
-this.$axios.get('test_url', {
-    noShowDefaultError: true // 可选
-}).then(({data}) => {
-    // 业务成功,直接操作 data 即可
-    
-}, (code, msg) => {
-    // 当有特定 code 需要特殊处理,传入 noShowDefaultError:true,在这个回调处理就行
-})
-```
-
-
-#### **为什么要如此配置与拦截器?**
-
-> 在应对项目开发过程中需求的不可预见性时,让我们能处理的更快更好
-
-到这里很多同学会觉得,就这么简单的引入判断,可有可无,
-就如我们最近做的一个需求来说,我们 ToC 端项目之前一直是在微信公众号中打开的,而我们需要在小程序中通过 webview 打开大部分流程,而我们也`没有时间,没有空间`在小程序中重写近 100 + 的页面流程,**这是我们开发之初并没有想到的**。这时候必须把项目兼容到小程序端,在兼容过程中可能需要解决以下问题:
-
-1. 请求路径完全不同。
-2. 需要兼容两套不同的权限系统。
-3. 有些流程在小程序端需要做改动,跳转到特定页面。
-4. 有些公众号的 `api` ,在小程序中无用,需要调用小程序的逻辑,需要做兼容。
-5. 很多也页面上的元素,小程序端不做展示等。
-
-> 可以看出,稍微不慎,会影响公众号现有逻辑。
-
-* 添加请求拦截 `interceptors/minaAjax.js`, `interceptors/minaRouter.js`,原有的换更为 `interceptors/officalAjax.js`,`interceptors/officalRouter.js`,在入口文件`interceptors/index.js`,**根据当前`宿主平台`,也就是全局配置 `HOST_PLATFORM`,通过`代理模式`和`策略模式`,注入对应平台的拦截器**,**在`minaAjax.js`中重写请求路径和权限处理,在 `minaRouter.js` 中添加页面拦截配置,跳转到特定页面**,这样一并解决了上面的`问题 1,2,3`。
-* `问题 4` 其实也比较好处理了,拷贝需要兼容 `api` 的页面,重写里面的逻辑,通过`路由拦截器一并做跳转处理`。
-* `问题 5` 也很简单,拓展两个**自定义指令 v-mina-show 和 v-mina-hide** ,在展示不同步的地方可以直接使用指令。
-
-最终用最少的代码,最快的时间完美上线,丝毫没影响到现有 toC 端业务,而且**这样把所有兼容逻辑绝大部分聚合到了一起,方便二次拓展和修改。**
-
-虽然这只是根据自身业务结合来说明,可能没什么说服力,不过不难看出全局配置/拦截器 虽然代码不多,但却是整个项目的核心之一,我们可以在里面做更多 `awesome` 的事情。
-
-### ⑤ 路由配置与懒加载
-`directives` 里面没什么可说的,不过很多难题都可以通过他来解决,要时刻记住,我们可以再指令里面**操作虚拟 DOM。**
-
-#### 路由配置
-而我们根据自己的业务性质,最终根据业务流程来拆分配置:
-```
-routes
-├── index.js            // 入口文件
-├── common.js           // 公共路由,登录,提示页等
-├── account.js          // 账户流程
-├── register.js         // 挂号流程
-└── ...
-```
- 最终通过 index.js 暴露出去给 `plugins/router` 实例使用,这里的划分有两个注意的地方:
- 
-- 需要根据自己业务性质来决定,有的项目可能适合`业务线`划分,有的项目更适合以 `功能` 划分。
-- 在多人协作过程中,尽可能避免冲突,或者减少冲突。
-
-#### 拦截器
-
-文章开头说到单页面静态资源过大,`首次打开/每次版本升级`后都会较慢,可以用`懒加载`来拆分静态资源,减少白屏时间,但开头也说到`懒加载`也有待商榷的地方:
-
-- 如果异步加载较多的组件,会给静态资源服务器/ CDN 带来更大的访问压力的同时,如果当多个异步组件都被修改,造成版本号的变动,发布的时候会大大增加 CDN 被击穿的风险。
-- 懒加载首次加载未被缓存的异步组件白屏的问题,造成用户体验不好。
-- 异步加载通用组件,会在页面可能会在网络延时的情况下参差不齐的展示出来等。
-
-这就需要我们根据项目情况在`空间和时间`上做一些权衡。
-
-以下几点可以作为简单的参考:
-- 对于访问量可控的项目,如`公司后台管理系统`中,可以以操作 view 为单位进行异步加载,通用组件全部同步加载的方式。
-- 对于一些复杂度较高,实时度较高的应用类型,可采用按`功能模块拆分`进行异步组件加载。
-- 如果项目想保证比较高的完整性和体验,迭代频率可控,不太关心首次加载时间的话,可按需使用异步加载或者直接不使用。
-
-
-> 打包出来的 main.js 的大小,绝大部分都是在路由中引入的并注册的视图组件。
-
-### ⑥ Service 服务层
-服务层作为项目中的另一个核心之一,“自古以来”都是大家比较关心的地方。
-
-不知道你是否看到过如下组织代码方式:
-```
-views/
-    pay/
-        index.vue
-        service.js
-        components/
-            a.vue
-            b.vue
-```
-在 `service.js` 中写入编写数据来源
-```js
-export const CONFIAG = {
-    apple: '苹果',
-    banana: '香蕉'
-}
-// ...
-
-// ① 处理业务逻辑,还弹窗
-export function getBInfo ({name = '', id = ''}) {
-    return this.$ajax.get('/api/info', {
-        name, 
-        id
-    }).then({age} => {
-        this.$modal.show({
-            content: age
-        })
-    })
-}
-
-// ② 不处理业务,仅仅写请求方法
-export function getAInfo ({name = '', id = ''}) {
-    return this.$ajax.get('/api/info', {
-        name, 
-        id
-    })
-}
-
-...
-```
-
-简单分析:
-- ① 就不多说了,拆分的不够单纯,当做二次开发的时候,你还得去找这弹窗到底哪里出来的。
-- ② 看起来很美好,不掺杂业务逻辑,但不知道你与没遇到过这样情况,**经常会有其他业务需要用到一样的枚举,请求一样的接口,而开发其他业务的同学并不知道你在这里有一份数据源,最终造成的结果就是数据源的代码到处冗余**。
-
-我相信②在绝大多数项目中都能看到。
-
-那么我们的目的就很明显了,**解决冗余,方便使用**,我们把枚举和请求接口的方法,通过插件,挂载到一个大对象上,注入 Vue 原型,方面业务使用即可。
-
-首先抽离请求接口模型,可按照领域模型抽离 (`service/api/index.js`):
-```js
-{
-    user: [{
-        name: 'info',
-        method: 'GET',
-        desc: '测试接口1',
-        path: '/api/info',
-        mockPath: '/api/info',
-        params: {
-            a: 1,
-            b: 2
-        }
-    }, {
-        name: 'info2',
-        method: 'GET',
-        desc: '测试接口2',
-        path: '/api/info2',
-        mockPath: '/api/info2',
-        params: {
-            a: 1,
-            b: 2,
-            b: 3
-        }
-    }],
-    order: [{
-        name: 'change',
-        method: 'POST',
-        desc: '订单变更',
-        path: '/api/order/change',
-        mockPath: '/api/order/change',
-        params: {
-            type: 'SUCCESS'
-        }
-    }]
-    ...
-}
-```
-试想一下我们需要几个简单的功能:
-
-- 请求参数自动截取。
-- 请求参数不穿则发送默认配置参数。
-- 得需要命名空间。
-- 通过全局配置开启调试模式。
-- 通过全局配置来控制走本地 mock 还是线上接口等。
-
-定制好功能,开始编写简单的 plugins/api.js 插件:
-
-**目录层级(仅供参考)**
-```
-service
-├── api
-    ├── index.js             // 入口文件
-    ├── order.js             // 订单相关接口配置
-    └── ...
-├── const                   
-    ├── index.js             // 入口文件
-    ├── order.js             // 订单常量接口配置
-    └── ...
-├── store                    // vuex 状态管理
-├── expands                  // 拓展
-    ├── monitor.js           // 监控
-    ├── beacon.js            // 打点
-    ├── localstorage.js      // 本地存储
-    └── ...                  // 按需拓展
-└── ...
-
-```
-
-```js
-import axios from './axios'
-import _pick from 'lodash/pick'
-import _assign from 'lodash/assign'
-import _isEmpty from 'lodash/isEmpty'
-
-import { assert } from 'Utils/tools'
-import { API_DEFAULT_CONFIG } from 'Config'
-import API_CONFIG from 'Service/api'
-
-
-class MakeApi {
-    constructor(options) {
-        this.api = {}
-        this.apiBuilder(options)
-    }
-
-
-    apiBuilder({
-    	sep = '|',
-    	config = {},
-    	mock = false, 
-    	debug = false,
-    	mockBaseURL = ''
-    }) {
-    	Object.keys(config).map(namespace => {
-    		this._apiSingleBuilder({
-                namespace, 
-                mock, 
-                mockBaseURL, 
-                sep, 
-                debug, 
-                config: config[namespace]
-            })
-    	})
-    }
-    _apiSingleBuilder({
-    	namespace, 
-    	sep = '|',
-    	config = {},
-    	mock = false, 
-    	debug = false,
-    	mockBaseURL = ''
-    }) {
-        config.forEach( api => {
-            const {name, desc, params, method, path, mockPath } = api
-            let apiname = `${namespace}${sep}${name}`,// 命名空间
-                url = mock ? mockPath : path,//控制走 mock 还是线上
-                baseURL = mock && mockBaseURL
-            
-            // 通过全局配置开启调试模式。
-            debug && console.info(`调用服务层接口${apiname},接口描述为${desc}`)
-            debug && assert(name, `${apiUrl} :接口name属性不能为空`)
-            debug && assert(apiUrl.indexOf('/') === 0, `${apiUrl} :接口路径path,首字符应为/`)
-
-            Object.defineProperty(this.api, `${namespace}${sep}${name}`, {
-                value(outerParams, outerOptions) {
-                
-                    // 请求参数自动截取。
-                    // 请求参数不穿则发送默认配置参数。
-                    let _data = _isEmpty(outerParams) ? params : _pick(_assign({}, params, outerParams), Object.keys(params))
-                    return axios(_normoalize(_assign({
-                        url,
-                        desc,
-                        baseURL,
-                        method
-                    }, outerOptions), _data))
-                }
-            })      
-        })
-    }       
-}
-
-function _normoalize(options, data) {
-    if (options.method === 'POST') {
-        options.data = data
-    } else if (options.method === 'GET') {
-        options.params = data
-    }
-    return options
-} 
-// 注入模型和全局配置,并暴露出去
-export default new MakeApi({
-	config: API_CONFIG,
-	...API_DEFAULT_CONFIG
-})['api']
-```
-
-挂载到 `Vue 原型`上,上文有说到,通过 `plugins/inject.js`
-
-```js
-import api from './api'
- 
-export default {
-    install: (Vue, options) => {
-        Vue.prototype.$api = api
-        // 需要挂载的都放在这里
-    }
-}
-```
-
-这样我们可以在`业务中`愉快的使用业务层代码:
-
-```js
-// .vue 中
-export default {
-    methods: {
-        test() {
-            this.$api['order/info']({
-                a: 1,
-                b: 2
-            })
-        }
-    }
-}
-```
-
-即使在`业务之外`也可以使用:
-
-```js
-import api from 'Plugins/api'
-
-api['order/info']({
-    a: 1,
-    b: 2
-})
-
-```
-
-**当然对于`运行效率要求高`的项目中,`避免内存使用率过大`,我们把命名空间支持成驼峰,直接用解构的方式引入使用,最终利用 `webpack` 的 `tree-shaking` 减少打包体积即可。**
-
-```js
-import {orderInfo as getOrderInfo} from 'Plugins/api'
-
-getOrderInfo({
-    a: 1,
-    b: 2
-})
-```
-
-> 一般来说,多人协作时候大家都可以先看 `api` 是否有对应接口,当业务量上来的时候,也肯定会有人出现找不到,或者找起来比较费劲,这时候我们完全可以在 请求拦截器中,把当前请求的 `url` 和 `api` 中的请求做下判断,如果有重复接口请求路径,则提醒开发者已经配置相关请求,根据情况是否进行二次配置即可。
-
-最终我们可以拓展 Service 层的各个功能:
-
-**基础**
-- **api**:`异步与后端交互`
-- **const**:`常量枚举`
-- **store**:`Vuex` 状态管理
-
-**拓展**
-- **localStorage**:本地数据,稍微封装下,支持存取对象即可
-- **monitor**:`监控`功能,自定义搜集策略,调用 `api` 中的接口发送
-- **beacon**:`打点`功能,自定义搜集策略,调用 `api` 中的接口发送
-- ... 
-
-`const`,`localStorage`,`monitor` 和 `beacon` 根据业务自行拓展暴露给业务使用即可,思想也是一样的,下面着重说下 `store(Vuex)`。
-
-### 状态管理与视图拆分
-[Vuex 源码分析可以看我之前写的文章](https://github.com/PerseveranceZ/zero-blog/issues/2)。
-
-#### 我们是不是真的需要状态管理?
-> 答案是否定的,就算你的项目达到 10 万行代码,那也并不意味着你必须使用 Vuex,应该由**业务场景**决定。
-
-#### 业务场景
-1. 第一类项目:**业务/视图复杂度不高,不建议使用 Vuex,会带来开发与维护的成本**,使用简单的 `vbus` 做好**命名空间**,来解耦即可。
-```js
-let vbus = new Vue()
-vbus.$on('print.hello', () => {
-    console.log('hello')
-})
-
-vbus.$emit('print.hello')
-```
-2. 第二类项目:类似`多人协作项目管理`,`有道云笔记`,`网易云音乐`,`微信网页版/桌面版`等**应用**,功能集中,空间利用率高,实时交互的项目,无疑 `Vuex 是较好的选择`。这类应用中我们可以直接`抽离业务领域模型`:
-
-```
-store
-├── index.js          
-├── actions.js        // 根级别 action
-├── mutations.js      // 根级别 mutation
-└── modules
-    ├── user.js       // 用户模块
-    ├── products.js   // 产品模块
-    ├── order.js      // 订单模块
-    └── ...
-```
-
-当然对于这类项目,[vuex 或许不是最好的选择]()。
-3. 第三类项目:`后台系统`或者`页面之间业务耦合不高的项目`,这类项目是占比应该是很大的,我们思考下这类项目:
-
-全局共享状态不多,但是难免在某个模块中会有复杂度较高的功能(客服系统,实时聊天,多人协作功能等),这时候如果为了项目的可管理性,我们也在 store 中进行管理,随着项目的迭代我们不难遇到这样的情况:
-
-```
-store/
-    ...
-    modules/
-        b.js
-        ...
-views/
-    ...
-    a/
-        b.js
-        ...
-        
-```
-- 试想下有几十个 module,对应这边上百个业务模块,开发者在两个平级目录之间调试与开发的成本是巨大的。
-- 这些 module 可以在项目中任一一个地方被访问,但往往他们都是冗余的,除了引用的功能模块之外,基本不会再有其他模块引用他。
-- 项目的可维护程度会随着项目增大而增大。
-
-#### 如何解决第三类项目的 store 使用问题?
-
-先梳理我们的目标:
-- 项目中模块可以自定决定是否使用 Vuex。(渐进增强)
-- **从有状态管理的模块,跳转没有的模块,我们不想把之前的状态挂载到 `store` 上,想提高运行效率**。(冗余)
-- 让这类项目的状态管理变的更加可维护。(开发成本/沟通成本)
-
-#### 实现
-我们借助 Vuex 提供的 `registerModule` 和 `unregisterModule` 一并解决这些问题,我们在 `service/store` 中放入全局共享的状态:
-```
-service/
-    store/
-        index.js
-        actions.js
-        mutations.js
-        getters.js
-        state.js
-```
-> 一般这类项目全局状态不多,如果多了拆分 module 即可。
-
-编写插件生成 `store 实例`:
-```js
-import Vue from 'vue'
-import Vuex from 'vuex'
-import {VUEX_DEFAULT_CONFIG} from 'Config'
-import commonStore from 'Service/store'
-
-Vue.use(Vuex)
-
-export default new Vuex.Store({
-    ...commonStore,
-    ...VUEX_DEFAULT_CONFIG
-})
-```
-
-对一个需要状态管理页面或者模块进行分层:
-
-```
-views/
-    pageA/
-        index.vue
-        components/
-            a.vue
-            b.vue
-            ...
-        children/
-            childrenA.vue
-            childrenB.vue
-            ...
-        store/
-            index.js
-            actions.js
-            moduleA.js  
-            moduleB.js
-```
-module 中直接包含了 `getters`,`mutations`,`state`,我们在 `store/index.js` 中做文章:
-```js
-import Store from 'Plugins/store'
-import actions from './actions.js'
-import moduleA from './moduleA.js'
-import moduleB from './moduleB.js'
-
-export default {
-    install() {
-        Store.registerModule(['pageA'], {
-            actions,
-            modules: {
-                moduleA,
-                moduleB
-            },
-            namespace: true
-        })
-    },
-    uninstall() {
-        store.unregisterModule(['pageA'])
-    }
-}
-
-```
-
-最终在 `index.vue` 中引入使用, **在页面跳转之前注册这些状态和管理状态的规则,在路由离开之前,先卸载这些状态和管理状态的规则**:
-```js
-import store from './store'
-import {mapGetters} from 'vuex'
-export default {
-    computed: {
-        ...mapGetters('pageA', ['aaa', 'bbb', 'ccc'])
-    },
-    beforeRouterEnter(to, from, next) {
-        store.install()
-        next()
-    },
-    beforeRouterLeave(to, from, next) {
-        store.uninstall()
-        next()
-    }
-}
-```
-当然如果你的状态要共享到全局,就不执行 `uninstall`。
-
-> 这样就解决了开头的三个问题,不同开发者在开发页面的时候,可以根据页面特性,渐进增强的选择某种开发形式。
-
-### 其他优化
-
-#### 打包,构建
-这里网上已经有很多优化方法:`dll`,`happypack`,`多线程打包`等,但随着项目的代码量级,每次 dev 保存的时候编译的速度也是会愈来愈慢的,而一过慢的时候我们就不得不进行拆分,这是肯定的,而在拆分之前尽可能容纳更多的可维护的代码,有几个可以尝试和规避的点:
-1. 优化项目流程:这个点看起来好像没什么用,但改变却是最直观的,页面/业务上的化简为繁会直接提现到代码上,同时也会增大项目的可维护,可拓展行等。
-2. 减少项目文件层级纵向深度。
-3. 减少无用业务代码,避免使用无用或者过大依赖(类似 `moment.js` 这样的库)等。
-
-#### 代码规范
-请强制使用 `eslint`。
-
-#### TypeScript
-非常建议用 TS 编写项目,可能写 .vue 有些别扭,这样前端的大部分错误在编译时解决,同时也能提高浏览器运行时效率,可能减少 `re-optimize` 阶段时间等。
-
-### 最后
-时下有各种成熟的方案,项目底层构建往往会成为前端忽略的地方,我们不应该只从一个大局观来看待一个大型项目,或者整条业务线,应该对每一行代码精益求精,对开发体验不断优化,慢慢累积才能应对未知的变化。

+ 24 - 0
web/docs/api/common.md

@@ -0,0 +1,24 @@
+[TOC]
+# 统一规范
+
+## 自定义组件
+命名:全局组件YuXxXx  OCA相关组件YuOcaXxXx
+
+## 列表
+
+- 所有列表(包含弹窗选择列表数据页面)都要在最前面加序号列
+- 列表默认不换行,多出的内容会出现省略号展示,进行tooltip提示。开发时注意:最小宽度为表头全部可以展示的宽度;
+- 列表操作列按钮:根据状态及权限控制显隐,如审批中的数据不显示修改、删除按钮,启用的的数据不显示修改、删除按钮等;
+- 列表查询条件表单,默认展示4列,若页面为左右布局则展示3列;
+
+## 弹窗
+
+- 480px-适用于:
+  - 表单项数量(0,6]
+  - 1列布局,无较宽(长)的表单项
+- 800px-适用于:
+  - 表单项数量[7,14]
+  - 1-2列布局,且有较长的表单项(如地址、文本域、富文本组件、表格、自定义表单结构等)
+- 960px-1200px-适用于:
+  - 表单项数量[15,+∞)
+  - >=3列的布局情况

+ 36 - 0
web/docs/api/component.md

@@ -0,0 +1,36 @@
+[TOC]
+# OCA自定义组件
+
+## 级联
+## YuTableTag-列表自定义tag标签列
+src\components\widgets\YuTableTag\index.vue,已注册为全局组件
+
+#### 非低码:
+```
+<yu-xtable-column prop="loginSts" label="登录状态" width="100px">
+  <template slot-scope="scope">
+    <!-- 默认配置[是否启用]字典项 -->
+    <yu-table-tag :code="scope.row.loginSts"></yu-table-tag>
+    <!-- [是否STD_YES_NO]字典项:配置yesno属性 -->
+    <yu-table-tag :code="scope.row.loginSts" yesno></yu-table-tag>
+    <!-- 其他:配置data-code、code-map属性 -->
+    <yu-table-tag :code="scope.row.loginSts" data-code="USR_STS" :code-map="{1: 'success', 2: 'danger', 3: 'warning'}"></yu-table-tag>
+  </template>
+</yu-xtable-column>
+```
+#### 低码:
+需要为表格的相应列renderCustom事件绑定一个函数,当前demo绑定的函数为renderLoginSts
+```
+/**
+ * 自定义登录状态操作列
+ */
+function renderLoginSts() {
+  return {
+    xtype: 'custom', // 自定义列固定写法
+    xtag: 'yu-table-tag', // 自定义组件的name,当前为yu-table-tag
+    xprops: { // 自定义组件传参,当前对应yu-table-tag中的props,用法同非低码,默认为 是否启用字典项,如需自定义传参则在里面传参配置
+      // dataCode: ''
+    }
+  }
+}
+```

+ 32 - 0
web/docs/api/constant.md

@@ -0,0 +1,32 @@
+[TOC]
+# 常量
+
+## APP_CONST(原生和低码通用)
+### 日期格式:
+```
+APP_CONST.DATE_FORMAT // 默认日期格式
+APP_CONST.DATE_TIME_FORMAT // 默认时间格式
+```
+
+### 字典项常量值:
+(具体见src\config\constant\app.data.lookupMapping.js)
+```
+APP_CONST.LOOKUP.STS_QY // 数据启用状态 2-启用
+```
+
+### 提示信息:
+1. 包含前端静态信息配置(见src\config\constant\app.data.message.js)和【公共参数管理-系统提示信息管理】中配置的前端提示信息
+2. 数据结构:{message" '', type: ''},message-提示消息 type-消息提示类型
+```
+// 直接调用提示信息进行提示:
+// 非低码:
+this.$message(APP_CONST.MESSAGE.BCCG) // 保存成功
+// 低码:
+xp.message(APP_CONST.MESSAGE.BCCG) // 保存成功
+
+// 只提取获取提示信息(一般用于确认弹窗提示信息处理、或含有数量替换的信息):
+APP_CONST.MESSAGE.BCCG.message // 保存成功
+APP_CONST.MESSAGE.QYQR.message // 单笔启用确认提示
+APP_CONST.MESSAGE.PLTYQR.message.replace('{n}', pkIds.length) // 批量提示替换数值
+
+```

+ 29 - 0
web/docs/api/resp.md

@@ -0,0 +1,29 @@
+# 请求处理
+
+#### 请求返回码定义文件
+src/config/constant/app.data.respCode.js
+
+该文件中定义的码值会进入请求的正确响应中,即apiFn().then()中
+
+所有微服务的正确响应码需定义在该文件中进行统一管理
+
+有特殊异常判断时,在app.data.respCode.js 的 RESP_CODE 中定义业务异常码
+
+```js
+/**
+* 保全强校验
+*/
+NPM_STRONG_VERIF: '20001'
+
+```
+```js
+apiFn().then(res => {
+  const { code, data.message } = res;
+  if (code === RESP_CODE.NPM_STRONG_VERIF) {
+    ...当前特殊判断处理
+  } else {
+    ...正确响应处理
+  }
+})
+
+```

+ 43 - 0
web/docs/api/table.md

@@ -0,0 +1,43 @@
+[TOC]
+# 列表
+
+## 1. 格式化
+### formatter格式化
+目前所有提供的自定义格式化方法:src\utils\formatter.js
+#### 非低码:
+直接调用 :formatter="moneyFormatter"
+#### 低码:
+选择自定义格式化
+
+### 列表自定义列为tag标签,对应组件YuTableTag
+src\components\widgets\YuTableTag\index.vue,已注册为全局组件
+
+#### 非低码:
+```
+<yu-xtable-column prop="loginSts" label="登录状态" width="100px">
+  <template slot-scope="scope">
+    <!-- 默认配置[是否启用]字典项 -->
+    <yu-table-tag :code="scope.row.loginSts"></yu-table-tag>
+    <!-- [是否STD_YES_NO]字典项:配置yesno属性 -->
+    <yu-table-tag :code="scope.row.loginSts" yesno></yu-table-tag>
+    <!-- 其他:配置data-code、code-map属性 -->
+    <yu-table-tag :code="scope.row.loginSts" data-code="USR_STS" :code-map="{1: 'success', 2: 'danger', 3: 'warning'}"></yu-table-tag>
+  </template>
+</yu-xtable-column>
+```
+#### 低码:
+需要为表格的相应列renderCustom事件绑定一个函数,当前demo绑定的函数为renderLoginSts
+```
+/**
+ * 自定义登录状态操作列
+ */
+function renderLoginSts() {
+  return {
+    xtype: 'custom', // 自定义列固定写法
+    xtag: 'yu-table-tag', // 自定义组件的name,当前为yu-table-tag
+    xprops: { // 自定义组件传参,当前对应yu-table-tag中的props,用法同非低码,默认为 是否启用字典项,如需自定义传参则在里面传参配置
+      // dataCode: ''
+    }
+  }
+}
+```

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.