فهرست منبع

修复 问题如下:
现状:标签新增,未通过标签编号/标签名称进行唯一性校验,输入同名,报系统错误/新建成功(规则不明确)
目标:增加标签编号的唯一性校验
现状:查询时间区间小于等于15天,打标趋势图横坐标显示月份,粒度错误
目标:显示日期
现状:查询时间区间大于15天,打标趋势图横坐标显示日期,粒度错误
目标:显示月份
现状:批量导入,模版内填写的标签判断说明,系统内显示无判断说明
目标:与导入文件内的数据一致,导入的数据文件见备注
现状:批量导入,导入的数据中,标签树为五层结构
目标:层级最多到4级,批量导入和页面新建都需要控制
现状:删除海上养殖的子级标签后,删除海上养殖标签,提示存在子级标签
目标:删除成功
现状:导入csv文件,导入失败,提示导入xls/xlsx文件
目标:导入成功
现状:新增一级标签,新增的标签会插入到标签树中间
目标:新增的标签应插入至所在层级的末尾

2507040827 3 هفته پیش
والد
کامیت
3dac77a1c4

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

@@ -48,6 +48,12 @@ public class AitagTagInfoController {
     @Autowired
     private AitagTagInfoService aitagTagInfoService;
 
+
+    /**
+     * 最大文件上传5M
+     */
+    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024;
+
     /**
      * 列表查询
      *
@@ -187,20 +193,13 @@ public class AitagTagInfoController {
     @ApiOperationType("批量导入")
     @PostMapping("/batchImport")
     public ResultDto batchImport( @RequestParam("file") MultipartFile file,
-                                  @RequestParam("reviser") String reviser,
                                   @RequestParam("categoryId") String categoryId) throws IOException {
 
         if (file.isEmpty()) {
            throw BizException.of("E002");
         }
-
         // 简单的文件类型检查
-        String fileName = file.getOriginalFilename();
-        if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
-            throw BizException.of("E003");
-        }
-        List<String> tagIds = aitagTagInfoService.batchImport(file, reviser, categoryId);
-
+        List<String> tagIds = aitagTagInfoService.batchImport(file,categoryId);
         HashMap<String, Object> body = new HashMap<>();
         body.put("tag_ids",tagIds);
         aitagTagInfoService.callAiTag(body,"/api/aitag/admin/v1/synchronize_tag");

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

@@ -23,6 +23,9 @@ public class TagImportDto {
     @ExcelProperty("标签关键词规则")
     private String reg;
 
+    @ExcelProperty("标签判断说明")
+    private String tagPrompt;
+
     /**
      *  运行时填充字段
      */

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

@@ -96,6 +96,11 @@ public class TagLogDto {
      **/
     @ApiModelProperty(value = "反馈时间")
     private LocalDateTime feedbackTime;
+    public String getFeedbackTime() {
+        return feedbackTime.format(DateTimeFormatter.ofPattern(DateUtils.PATTERN_DATETIME));
+    }
+
+
 
     /**
      * 反馈,agree/reject

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

@@ -1,6 +1,7 @@
 package cn.com.yusys.yusp.domain.vo;
 
 
+import cn.com.yusys.yusp.commons.util.StringUtils;
 import lombok.Data;
 
 /**
@@ -18,7 +19,33 @@ public class IconResVo {
     /**
      * 字段Value值
      */
-    private String val;
-
+    private Integer val;
 
+    /**
+     * 统计类型 day 按天统计, mother 按月统计
+     */
+    private String statType;
+
+
+
+    public IconResVo(String stat, Integer val , String statType) {
+        this.stat = stat;
+        this.val = val;
+        this.statType = statType;
+    }
+
+    public IconResVo() {
+    }
+
+    public String getStatShow() {
+        if(StringUtils.equals("day",statType)){
+            String month = stat.substring(5, 7);
+            String day = stat.substring(8, 10);
+            return month + "月" + day + "日";
+        }else{
+            String year = stat.substring(0, 4);
+            String month = stat.substring(5,7);
+            return year + "年" + month + "月";
+        }
+    }
 }

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

@@ -29,7 +29,6 @@ public interface AitagTagLogDao extends BaseMapper<AitagTagLogEntity> {
     List<IconResVo> selectTagReportByMonth(SmartTaggingResultVo taggingResult);
 
 
-    List<IconResVo> selectTagReportByWeek(SmartTaggingResultVo taggingResult);
 
     List<AitagTagLogEntity> selectByInsertTime(@Param("insertTime") String insertTime);
 

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

@@ -46,7 +46,7 @@ public interface AitagTagInfoService extends IService<AitagTagInfoEntity> {
 
     List<AitagTagInfoEntity> queryList(AitagTagInfoQueryVo aitagTagInfoQueryVo);
 
-    List<String> batchImport(MultipartFile file, String reviser, String categoryId) throws IOException;
+    List<String> batchImport(MultipartFile file, String categoryId) throws IOException;
 
 
     AiTaggingResponseVo callAiTag(Map<String, Object> body, String path);

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

@@ -20,6 +20,7 @@ public class TagImportListener extends AnalysisEventListener<TagImportDto> {
 
     private final String[] expectedHeaders = {"标签名称","标签代码","父标签名称","标签说明","标签关键词规则","标签判断说明"};
 
+    private static final int MAX_ROWS = 1000; // 最多1000条
 
     @Override
     public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
@@ -38,6 +39,10 @@ public class TagImportListener extends AnalysisEventListener<TagImportDto> {
     @Override
     public void invoke(TagImportDto data, AnalysisContext context) {
         // 数据清洗
+
+        if (dataList.size() >= MAX_ROWS) {
+            throw BizException.of("E016");
+        }
         if (StringUtils.isBlank(data.getTagNm())) {
             throw BizException.of("E012");
         }

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

@@ -126,6 +126,7 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
 
         LambdaQueryWrapper<AitagTagInfoEntity> queryTagInfo = new LambdaQueryWrapper<>();
         queryTagInfo.eq(AitagTagInfoEntity::getTagCode,aitagTagInfo.getTagCode());
+        queryTagInfo.eq(AitagTagInfoEntity::getIsDelete,TAG_UNDELETED);
         if(this.baseMapper.selectCount(queryTagInfo) > 0){
             throw BizException.of("E010");
         }
@@ -139,6 +140,9 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
                 AitagTagInfoEntity aitagTagInfoEntity = aitagTagInfoEntities.get(0);
                 aitagTagInfo.setTagPath(aitagTagInfoEntity.getTagPath()+"/"+aitagTagInfo.getTagNm());
                 aitagTagInfo.setTagLevel(aitagTagInfoEntity.getTagLevel()+1);
+                if(aitagTagInfoEntity.getTagLevel() > 4){
+                    throw BizException.of("E017");
+                }
             }else{
                 aitagTagInfo.setTagPath(aitagTagInfo.getTagNm());
                 aitagTagInfo.setTagLevel(1);
@@ -173,6 +177,7 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
             queryWrapper.like(AitagTagInfoEntity::getTagNm, aitagTagInfoQueryVo.getTagNm());
         }
         queryWrapper.eq(AitagTagInfoEntity::getIsDelete, TAG_UNDELETED);
+        queryWrapper.orderByAsc(AitagTagInfoEntity::getRevisionTime);
         List<AitagTagInfoEntity> aitagTagInfoEntities = this.baseMapper.selectList(queryWrapper);
         return this.buildTree(aitagTagInfoEntities);
     }
@@ -205,6 +210,9 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
             if(parentTagInfo !=null){
                 aitagTagInfoEntity.setTagPath(parentTagInfo.getTagPath()+"/"+aitagTagInfoEntity.getTagNm());
                 aitagTagInfoEntity.setTagLevel(parentTagInfo.getTagLevel()+1);
+                if(aitagTagInfoEntity.getTagLevel() > 4){
+                    throw BizException.of("E017");
+                }
             }else{
                 aitagTagInfoEntity.setTagPath(aitagTagInfoEntity.getTagNm());
                 aitagTagInfoEntity.setTagLevel(1);
@@ -225,6 +233,7 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
         for (String id:list){
             LambdaQueryWrapper<AitagTagInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
             queryWrapper.eq(AitagTagInfoEntity::getParentId,id);
+            queryWrapper.eq(AitagTagInfoEntity::getIsDelete,NON_DELETE);
             if(this.baseMapper.selectCount(queryWrapper)>0){
                 throw BizException.of("E011");
             }
@@ -301,19 +310,20 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
         if (aitagTagInfoQueryVo.getTagPrompt() != null) {
             queryWrapper.eq(AitagTagInfoEntity::getTagPrompt, aitagTagInfoQueryVo.getTagPrompt());
         }
+        queryWrapper.eq(AitagTagInfoEntity::getIsDelete,NON_DELETE);
         queryWrapper.select(AitagTagInfoEntity::getId,AitagTagInfoEntity::getTagNm,AitagTagInfoEntity::getParentId);
         return this.baseMapper.selectList(queryWrapper);
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public List<String> batchImport(MultipartFile file, String reviser, String categoryId) throws IOException {
+    public List<String> batchImport(MultipartFile file, String categoryId) throws IOException {
         log.info("开始处理上传文件:{}", file.getOriginalFilename());
 
         TagImportListener listener = new TagImportListener();
         EasyExcel.read(file.getInputStream(), TagImportDto.class, listener)
                 .headRowNumber(1)
-                .sheet(1)
+                .sheet(0)
                 .doRead();
         List<TagImportDto> tagImportDtos = listener.getDataList();
         if (tagImportDtos.isEmpty()) {
@@ -330,22 +340,27 @@ public class AitagTagInfoServiceImpl extends ServiceImpl<AitagTagInfoDao, AitagT
 
         validateParentTags(nameToDtoMap);
         resolveHierarchy(nameToDtoMap);
-        saveToDatabase(tagImportDtos,reviser,categoryId);
+        saveToDatabase(tagImportDtos,categoryId);
         return  tagImportDtos.stream().map(TagImportDto::getId).collect(Collectors.toList());
     }
 
 
-    private void saveToDatabase(List<TagImportDto> tagImportDtos,String reviser,String categoryId) {
+    private void saveToDatabase(List<TagImportDto> tagImportDtos,String categoryId) {
         AitagTagCategory aitagTagCategory = aiTagCategoryMapper.selectById(categoryId);
-        String categoryNm = aitagTagCategory.getCategoryNm();
+        if(aitagTagCategory == null && aitagTagCategory.getIsDelete().intValue() != NON_DELETE){
+            throw BizException.of("E018");
+        }
         for (TagImportDto tagImportDto: tagImportDtos){
             AitagTagInfoEntity aitagTagInfo = JSONObject.parseObject(JSONObject.toJSONString(tagImportDto),
                     AitagTagInfoEntity.class);
             String tagPath = tagImportDto.getTagPath();
-            aitagTagInfo.setTagPath(categoryNm+"/"+tagPath);
+            aitagTagInfo.setTagPath(tagPath);
             aitagTagInfo.setTagLevel(aitagTagInfo.getTagPath().split("/").length);
+            if(aitagTagInfo.getTagLevel() > 4){
+                throw BizException.of("E017");
+            }
             aitagTagInfo.setRevisionTime(DateUtils.getCurrDateTimeStr());
-            aitagTagInfo.setReviser(reviser);
+            aitagTagInfo.setReviser(SessionCommonUtil.getUserInfo());
             aitagTagInfo.setTagVersion("1");
             aitagTagInfo.setIsDelete(TAG_UNDELETED);
             aitagTagInfo.setCategoryId(categoryId);

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

@@ -1,5 +1,6 @@
 package cn.com.yusys.yusp.service.impl;
 
+import cn.com.yusys.yusp.commons.util.date.DateFormatEnum;
 import cn.com.yusys.yusp.commons.util.date.DateUtils;
 import cn.com.yusys.yusp.config.DataDictionary;
 import cn.com.yusys.yusp.domain.dto.TagLogDto;
@@ -21,7 +22,15 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.List;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import static cn.com.yusys.yusp.config.DataDictionary.*;
 
@@ -40,6 +49,11 @@ public class AitagTagLogServiceImpl extends ServiceImpl<AitagTagLogDao, AitagTag
     private AitagTagInfoDao tagInfoDao;
 
 
+    private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    private static final DateTimeFormatter DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
+
     @Override
     public DataOverviewVo dataOverview(SmartTaggingResultVo taggingResult) {
         DataOverviewVo dataOverview = new DataOverviewVo();
@@ -86,6 +100,7 @@ public class AitagTagLogServiceImpl extends ServiceImpl<AitagTagLogDao, AitagTag
         return dataOverview;
     }
 
+
     @Override
     public List<IconResVo> taggingTrend(TaggingTrendReqVo taggingTrendReqVo) {
         String startTaggingTime = taggingTrendReqVo.getStartTaggingTime();
@@ -93,14 +108,93 @@ public class AitagTagLogServiceImpl extends ServiceImpl<AitagTagLogDao, AitagTag
 
         int daysByTwoDatesDef = DateUtils.getDaysByTwoDatesDef(startTaggingTime, endTaggingTime);
         List<IconResVo> taggingTrendResVo = null;
+        List<IconResVo> iconResVos = null;
         if(daysByTwoDatesDef>=15){
             taggingTrendResVo = this.baseMapper.selectTagReportByMonth(taggingTrendReqVo);
+            iconResVos = generateMonthlyStats(startTaggingTime, endTaggingTime);
         }else{
             taggingTrendResVo = this.baseMapper.selectTagReportByDay(taggingTrendReqVo);
+            iconResVos = generateDailyStats(startTaggingTime, endTaggingTime);
         }
-        return taggingTrendResVo;
+        return mergeLists(iconResVos,taggingTrendResVo);
+    }
+
+
+    public List<IconResVo> mergeLists(List<IconResVo> iconResVos, List<IconResVo> taggingTrendResVo) {
+        // 将taggingTrendResVo转换为Map,key为stat,value为IconResVo对象
+        Map<String, IconResVo> taggingMap = taggingTrendResVo.stream()
+                .collect(Collectors.toMap(
+                        IconResVo::getStat,
+                        item -> item,
+                        (existing, replacement) -> replacement
+                ));
+
+        // 遍历iconResVos,如果stat在taggingMap中存在,则更新val值
+        return iconResVos.stream()
+                .map(iconRes -> {
+                    IconResVo taggingRes = taggingMap.get(iconRes.getStat());
+                    if (taggingRes != null) {
+                        // 创建新对象或修改原对象(根据需要选择)
+                        IconResVo merged = new IconResVo();
+                        merged.setStat(iconRes.getStat());
+                        merged.setVal(taggingRes.getVal());
+                        merged.setStatType(iconRes.getStatType());
+                        return merged;
+                    }
+                    return iconRes;
+                })
+                .collect(Collectors.toList());
+    }
+
+
+
+    /**
+     * Stream版本 - 按天生成
+     */
+    private List<IconResVo> generateDailyStats(String startDateStr, String endDateStr) {
+        return getDateStream(startDateStr.substring(0, 10), endDateStr.substring(0, 10))
+                .map(date -> new IconResVo(date.format(DAY_FORMATTER), 0,"day"))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 生成日期流(JDK 11 兼容)
+     */
+    private Stream<LocalDate> getDateStream(String startDateStr, String endDateStr) {
+        LocalDate startDate = LocalDate.parse(startDateStr.trim(), DAY_FORMATTER);
+        LocalDate endDate = LocalDate.parse(endDateStr.trim(), DAY_FORMATTER);
+        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate) + 1;
+        return IntStream.iterate(0, i -> i + 1)
+                .limit(daysBetween)
+                .mapToObj(startDate::plusDays);
     }
 
+    /**
+     * Stream版本 - 按月生成
+     */
+    private List<IconResVo> generateMonthlyStats(String startDateStr, String endDateStr) {
+        LocalDateTime startTime = LocalDateTime.parse(startDateStr.trim(), DATETIME);
+        LocalDateTime  endTime = LocalDateTime.parse(endDateStr.trim(), DATETIME);
+
+        List<IconResVo> result = new ArrayList<>();
+
+        // 获取开始和结束的年月
+        YearMonth startMonth = YearMonth.from(startTime);
+        YearMonth endMonth = YearMonth.from(endTime);
+
+        // 遍历每个月
+        for (YearMonth currentMonth = startMonth;
+             !currentMonth.isAfter(endMonth);
+             currentMonth = currentMonth.plusMonths(1)) {
+            String stat = currentMonth.format(MONTH_FORMATTER);
+            result.add(new IconResVo(stat, 0,"mother"));
+            // 如果当前月已经是结束月,则跳出循环
+            if (currentMonth.equals(endMonth)) {
+                break;
+            }
+        }
+        return result;
+    }
     @Override
     public IPage<TagLogDto> taggingDetails(TaggingTransactionReqVo transactionReqVo) {
         LambdaQueryWrapper<AitagTagLogEntity> queryWrapper = new LambdaQueryWrapper<>();

+ 8 - 26
server/yusp-tagging-core/src/main/resources/mapper/AitagTagLogMapper.xml

@@ -43,8 +43,9 @@
 
     <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
+        DATE_FORMAT(insert_time, '%Y-%m-%d') AS stat,
+        COUNT(1) AS val,
+        'day' AS stat_type
         FROM aitag_tag_log
         WHERE
         tag_scope = 0
@@ -58,14 +59,15 @@
             <if test="categoryId != null and categoryId != ''">
                 AND result @> #{categoryExp}::jsonb
             </if>
-        GROUP BY DATE_FORMAT(insert_time, '%Y年%m月%d日')
+        GROUP BY DATE_FORMAT(insert_time, '%Y-%m-%d')
         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
+        DATE_FORMAT(insert_time, '%Y-%m') AS stat,
+        COUNT(1) AS val,
+        'month' AS stat_type
         FROM aitag_tag_log
         WHERE
         tag_scope = 0
@@ -79,30 +81,10 @@
             <if test="categoryId != null and categoryId != ''">
                 AND result @> #{categoryExp}::jsonb
             </if>
-        GROUP BY DATE_FORMAT(insert_time, '%Y年%m月')
+        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
-        tag_scope = 0
-        AND is_delete = 0
-            <if test="startTaggingTime != null and  startTaggingTime != ''">
-                AND insert_time >= #{startTaggingTime}
-            </if>
-            <if test="endTaggingTime != null and  endTaggingTime != ''">
-                AND #{endTaggingTime} >= insert_time
-            </if>
-            <if test="categoryId != null and categoryId != ''">
-                AND result @> #{categoryExp}::jsonb
-            </if>
-        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

+ 5 - 2
server/yusp-tagging-core/src/main/resources/messages/yusp_input_msg.properties

@@ -101,7 +101,7 @@ NO_DATA_PERIOD=\u8BE5\u7EDF\u8BA1\u5468\u671F\u6682\u672A\u5B9E\u73B0
 E001=\u6240\u5C5E\u5927\u7C7B\u4E0D\u80FD\u4E3A\u7A7A
 E002=\u4E0A\u4F20\u6587\u4EF6\u4E0D\u80FD\u4E3A\u7A7A
 E003=\u4EC5\u652F\u6301 .xlsx \u6216 .xls \u683C\u5F0F\u6587\u4EF6
-E004=Excel \u4E2D\u6CA1\u6709\u6709\u6548\u6570\u636E
+E004=\u6587\u4EF6\u4E2D\u6CA1\u6709\u6709\u6548\u6570\u636E
 E005=\u53D1\u73B0\u91CD\u590D\u7684\u6807\u7B7E\u540D\u79F0:[{0}]
 E006=\u6821\u9A8C\u5931\u8D25\uFF1A\u6807\u7B7E [{0}] \u7684\u7236\u6807\u7B7E [{1}] \u4E0D\u5B58\u5728\u4E8E\u6587\u4EF6\u4E2D\u3002
 E007=\u68C0\u6D4B\u5230\u6807\u7B7E\u5C42\u7EA7\u73AF\u8DEF:{0}
@@ -112,4 +112,7 @@ E011=\u6807\u7B7E\u5B58\u5728\u4E0B\u7EA7\u6807\u7B7E\uFF0C\u65E0\u6CD5\u5220\u9
 E012=\u6807\u7B7E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
 E013=\u6807\u7B7E\u4EE3\u7801\u4E0D\u80FD\u4E3A\u7A7A
 E014=\u6A21\u677F\u5217\u6570\u4E0D\u6B63\u786E
-E015=\u6A21\u677F\u683C\u5F0F\u4E0D\u6B63\u786E
+E015=\u6A21\u677F\u683C\u5F0F\u4E0D\u6B63\u786E
+E016=\u6570\u636E\u884C\u6570\u8D85\u8FC7\u9650\u5236
+E017=\u6587\u4EF6\u5C42\u7EA7\u4E0D\u80FD\u8D85\u8FC74\u7EA7
+E018=\u6240\u5C5E\u5927\u7C7B\u8F93\u5165\u6709\u8BEF