Procházet zdrojové kódy

feat(quartz): 同步开关门记录并更新订单状态

- 新增定时任务 synchronousDoorOpeningAndClosingRecords 同步门禁记录
- 更新 AppIsin 和 AppOrderProInfo 表中的相关记录
- 当所有子订单状态为 2 时,更新主订单状态为 2
- 优化了门禁记录的提取和处理逻辑
SheepHy před 3 týdny
rodič
revize
34dd2b0c0b

+ 11 - 14
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/controller/AppUserController.java

@@ -15,11 +15,9 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
-import java.io.IOException;
 import java.util.List;
 
 import static org.jeecg.modules.app.ezt.GetEidToken.getEidToken;
-import static org.jeecg.modules.hikiot.HikiotTool.queryOpenDoorRecord;
 
 @Slf4j
 @Tag(name = "App用户相关接口")
@@ -97,16 +95,15 @@ public class AppUserController {
         return Result.ok(getEidToken(idCard,name));
     }
 
-    /**
-     * @Author SheepHy
-     * @Description 分页查询开门记录
-     * @Date 10:53 2025/8/25
-     **/
-    @Operation(summary="分页查询开门记录")
-    @GetMapping(value = "/listOpenDoor")
-    public Result<String> listOpenDoor(@RequestParam String employeeNos,
-                                       @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
-                                       @RequestParam(name="pageSize", defaultValue="10") Integer pageSize) throws IOException, InterruptedException {
-        return Result.OK(queryOpenDoorRecord(pageNo, pageSize, employeeNos));
-    }
+//    /**
+//     * @Author SheepHy
+//     * @Description 分页查询开门记录
+//     * @Date 10:53 2025/8/25
+//     **/
+//    @Operation(summary="分页查询开门记录")
+//    @GetMapping(value = "/listOpenDoor")
+//    public Result<String> listOpenDoor(@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+//                                       @RequestParam(name="pageSize", defaultValue="10") Integer pageSize) throws IOException, InterruptedException {
+//        return Result.OK(queryOpenDoorRecord(pageNo, pageSize));
+//    }
 }

+ 16 - 16
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/OrderServiceImpl.java

@@ -956,23 +956,23 @@ public class OrderServiceImpl implements IOrderService {
             appOrderProInfo
                     .setOrderId(appOrder.getId())
                     .setOrderCode(appOrder.getOrderCode())
-                    //生成10位随机券号
-                    .setTicketNo(RandomUtil.randomNumbers(10))
-            ;
+                    .setTicketNo(RandomUtil.randomNumbers(10));
             if(appOrderProInfoMapper.insert(appOrderProInfo) > 0){
-                AppSite appSite = appSiteMapper.selectById(appCoursesMapper.selectById(appOrderProInfo.getProductId()).getAddressSiteId());
-                if(null != appSite && appOrderProInfo.getType() == 5 &&
-                        appSite.getType() == 0){
-                    for (AppDevice appDevice : appDeviceMapper.selectList(Wrappers.<AppDevice>lambdaQuery().eq(AppDevice::getSiteId, appSite.getId()))){
-                        if(null != appDevice){
-                            JsonObject addUserJson = JsonParser.parseString(addUser(new Date(),
-                                    appDevice.getDeviceSerial(),
-                                    appOrderProInfo.getUserName(),
-                                    appOrderProInfo.getId(),new DateTime(appOrderProInfo.getExpireTime()))).getAsJsonObject();
-                            JsonObject addFaceJson = JsonParser.parseString(addFace(appDevice.getDeviceSerial(), appOrderProInfo.getId(),
-                                    familyMembersMapper.selectById(appOrderProInfo.getFamilyUserId()).getRealNameImg())).getAsJsonObject();
-                            if (addUserJson.get("code").getAsInt() != 0 && addFaceJson.get("code").getAsInt() != 0){
-                                throw new JeecgBootException("设备录入用户信息失败!请联系管理员");
+                if(appOrderProInfo.getType() == 5){
+                    AppSite appSite = appSiteMapper.selectById(appCoursesMapper.selectById(appOrderProInfo.getProductId()).getAddressSiteId());
+                    if(null != appSite &&
+                            appSite.getType() == 0){
+                        for (AppDevice appDevice : appDeviceMapper.selectList(Wrappers.<AppDevice>lambdaQuery().eq(AppDevice::getSiteId, appSite.getId()))){
+                            if(null != appDevice){
+                                JsonObject addUserJson = JsonParser.parseString(addUser(new Date(),
+                                        appDevice.getDeviceSerial(),
+                                        appOrderProInfo.getUserName(),
+                                        appOrderProInfo.getId(),new DateTime(appOrderProInfo.getExpireTime()))).getAsJsonObject();
+                                JsonObject addFaceJson = JsonParser.parseString(addFace(appDevice.getDeviceSerial(), appOrderProInfo.getId(),
+                                        familyMembersMapper.selectById(appOrderProInfo.getFamilyUserId()).getRealNameImg())).getAsJsonObject();
+                                if (addUserJson.get("code").getAsInt() != 0 && addFaceJson.get("code").getAsInt() != 0){
+                                    throw new JeecgBootException("设备录入用户信息失败!请联系管理员");
+                                }
                             }
                         }
                     }

+ 15 - 6
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/hikiot/HikiotTool.java

@@ -1,5 +1,6 @@
 package org.jeecg.modules.hikiot;
 
+import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
@@ -17,6 +18,7 @@ import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
@@ -538,20 +540,27 @@ public class HikiotTool {
      * @Description 分页查询开门记录
      * @Date 10:05 2025/8/25
      **/
-    public static String queryOpenDoorRecord(int page, int size,String employeeNos) throws IOException, InterruptedException {
+    public static JsonObject queryOpenDoorRecord(int page, int size, Date startOfDay) {
         Map<String, Object> queryOpenDoor = new HashMap<>();
         queryOpenDoor.put("page", page);
         queryOpenDoor.put("size", size);
-        queryOpenDoor.put("employeeNos", new String[] {employeeNos});
+        // 获取今天最早和最晚时间并格式化
+        LocalDate today = LocalDate.now();
+        if(null != startOfDay){
+            queryOpenDoor.put("startDate", DateUtil.format(startOfDay, "yyyy-MM-dd HH:mm:ss"));
+        }
+//        queryOpenDoor.put("endDate", endOfDay.format(formatter));
+        queryOpenDoor.put("authResults", new int[] {1});
+//        queryOpenDoor.put("employeeNos", new String[] {employeeNos});
 //        queryOpenDoor.put("deviceSerial", "FX0889961");
 //        queryOpenDoor.put("deviceSerials", new String[] {"FX0889961"});
-        JsonObject jsonObject = gson.fromJson(sendPostRequest(QUERY_OPEN_DOOR_RECORD_URL, gson.toJson(queryOpenDoor), setHeaders()), JsonObject.class);
-        return gson.toJson(jsonObject.get("data"));
+        return gson.fromJson(sendPostRequest(QUERY_OPEN_DOOR_RECORD_URL, gson.toJson(queryOpenDoor), setHeaders()), JsonObject.class);
     }
 
     public static void main(String[] args) throws IOException, InterruptedException {
-        addUser(new Date(),"FX0889961","Sheep123","10011",null);
-        addFace("FX0889961","10011","https://national-motion.oss-cn-beijing.aliyuncs.com/opt/upFiles/tmp_81fc8aa195d37dac70fd57221d82845e_1756361097600.jpg");
+        queryOpenDoorRecord(1,10000,new Date());
+//        addUser(new Date(),"FX0889961","Sheep123","10011",null);
+//        addFace("FX0889961","10011","https://national-motion.oss-cn-beijing.aliyuncs.com/opt/upFiles/tmp_81fc8aa195d37dac70fd57221d82845e_1756361097600.jpg");
 //        addUser(new Date(),"FX0889961","Sheep123","10011");
 //        addFace("FX0889961","1001","https://national-motion.oss-cn-beijing.aliyuncs.com/opt/upFiles/tmp_81fc8aa195d37dac70fd57221d82845e_1756361097600.jpg");
 //        addUser();

+ 179 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/quartz/job/OrTeachingJobService.java

@@ -1,10 +1,13 @@
 package org.jeecg.modules.quartz.job;
 
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -14,6 +17,9 @@ import org.jeecg.modules.quartz.utils.HolidayUtil;
 import org.jeecg.modules.system.app.dto.TeachingDayDTO;
 import org.jeecg.modules.system.app.entity.*;
 import org.jeecg.modules.system.app.mapper.AppDeviceMapper;
+import org.jeecg.modules.system.app.mapper.AppIsinMapper;
+import org.jeecg.modules.system.app.mapper.AppOrderMapper;
+import org.jeecg.modules.system.app.mapper.AppOrderProInfoMapper;
 import org.jeecg.modules.system.app.service.IAppSitePlaceService;
 import org.jeecg.modules.system.app.service.IAppSitePriceRulesService;
 import org.jeecg.modules.system.app.service.IAppSiteService;
@@ -29,8 +35,11 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
+import static com.alibaba.dashscope.utils.JsonUtils.gson;
 import static org.jeecg.modules.hikiot.HikiotTool.deleteExpiredVisitors;
+import static org.jeecg.modules.hikiot.HikiotTool.queryOpenDoorRecord;
 
 /**
  * @author wzq
@@ -53,6 +62,13 @@ public class OrTeachingJobService {
     @Resource
     private AppDeviceMapper appDeviceMapper;
 
+    @Resource
+    private AppIsinMapper appIsinMapper;
+    @Resource
+    private AppOrderProInfoMapper appOrderProInfoMapper;
+    @Resource
+    private AppOrderMapper appOrderMapper;
+
     @Scheduled(cron = "0 0 0 1 12 *")
     @Transactional(rollbackFor = Exception.class)
     public void execute() throws ParseException {
@@ -168,6 +184,13 @@ public class OrTeachingJobService {
         }
     }
 
+    /**
+     * @Author SheepHy
+     * @Description 删除过期用户
+     * @Date 17:43 2025/9/1
+     * @Param
+     * @return
+     **/
     @Scheduled(cron = "0 0 1 * * ?")
     public void deleteExpiredVisitorsExecute(){
         try {
@@ -211,4 +234,160 @@ public class OrTeachingJobService {
             log.error("获取设备列表失败", e);
         }
     }
+
+    /**
+     * @Author SheepHy
+     * @Description 同步开关门记录
+     * @Date 17:44 2025/9/1
+     * @Param
+     * @return
+     **/
+    @Scheduled(fixedDelay = 30000)
+    public void synchronousDoorOpeningAndClosingRecords(){
+        try {
+            List<DoorRecordDTO> allRecords = fetchAllDoorRecords();
+            List<ExtractedData> extractedData = extractRequiredData(allRecords);
+
+            extractedData.forEach(data -> {
+                log.info("提取数据: {}", data);
+                AppIsin appIsin = appIsinMapper.selectOne(Wrappers.<AppIsin>lambdaQuery().eq(AppIsin::getOrderProInfoId, data.getEmployeeNo()));
+                if(null != appIsin){
+                    appIsin.setUseTime(DateUtils.str2Date(data.getGmtCreate(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
+                    appIsin.setUpdateTime(DateUtils.str2Date(data.getGmtCreate(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
+                    appIsin.setIsinStatus(1);
+                    appIsin.setUseImage(data.getPortraitUrl());
+                    appIsinMapper.updateById(appIsin);
+                    AppOrderProInfo orderProInfo = appOrderProInfoMapper.selectById(appIsin.getOrderProInfoId());
+                    orderProInfo.setOrderStatus(2);
+                    appOrderProInfoMapper.updateById(orderProInfo);
+                    // 查询当前订单的所有 AppOrderProInfo 记录
+                    List<AppOrderProInfo> orderProInfoList = appOrderProInfoMapper.selectList(
+                            Wrappers.<AppOrderProInfo>lambdaQuery()
+                                    .eq(AppOrderProInfo::getOrderCode, orderProInfo.getOrderCode())
+                    );
+                    // 判断所有子订单的 orderStatus 是否都为 2
+                    if (orderProInfoList != null && !orderProInfoList.isEmpty() &&
+                            orderProInfoList.stream().allMatch(opi -> opi.getOrderStatus() == 2)) {
+                        // 查询主订单
+                        AppOrder appOrder = appOrderMapper.selectOne(
+                                Wrappers.<AppOrder>lambdaQuery()
+                                        .eq(AppOrder::getOrderCode, orderProInfo.getOrderCode())
+                        );
+                        if (appOrder != null) {
+                            // 更新主订单状态为 2
+                            appOrder.setOrderStatus(2);
+                            appOrderMapper.updateById(appOrder);
+                        }
+                    }
+                }
+            });
+
+        } catch (Exception e) {
+            log.error("处理门禁记录失败", e);
+        }
+    }
+
+    /**
+     * 分页获取所有门禁记录
+     */
+    private List<DoorRecordDTO> fetchAllDoorRecords() {
+        List<DoorRecordDTO> allRecords = new ArrayList<>();
+        int pageNum = 1;
+        int pageSize = 10000;
+        AppIsin appIsin = appIsinMapper.selectOne(Wrappers.<AppIsin>lambdaQuery()
+                .eq(AppIsin::getIsinStatus, 0)
+                .eq(AppIsin::getOriginId, null)
+                .orderByDesc(AppIsin::getUpdateTime)
+                .last("limit 1"));
+        try {
+            while (true) {
+                JSONObject response;
+                if(null != appIsin && null != appIsin.getUpdateTime()){
+                    response = gson.fromJson(queryOpenDoorRecord(pageNum, pageSize, appIsin.getUpdateTime()), JSONObject.class);
+                }else {
+                    response = gson.fromJson(queryOpenDoorRecord(pageNum, pageSize,null), JSONObject.class);
+                }
+
+                // 验证响应有效性
+                if (!isValidResponse(response)) {
+                    log.warn("无效的响应数据,停止获取");
+                    break;
+                }
+                // 提取当前页数据
+                JSONArray dataList = response.getJSONArray("data");
+                List<DoorRecordDTO> currentRecords = JSON.parseArray(
+                        dataList.toJSONString(),
+                        DoorRecordDTO.class
+                );
+                allRecords.addAll(currentRecords);
+
+                // 分页判断
+                int totalCount = response.getIntValue("count");
+                if (pageNum * pageSize >= totalCount) {
+                    break;
+                }
+                pageNum++;
+            }
+
+        } catch (Exception e) {
+            log.error("分页获取门禁记录失败", e);
+        }
+
+        return allRecords;
+    }
+
+    /**
+     * 提取和过滤数据
+     */
+    private List<ExtractedData> extractRequiredData(List<DoorRecordDTO> records) {
+        List<ExtractedData> result = new ArrayList<>();
+
+        if (records != null && !records.isEmpty()) {
+            result = records.stream()
+                    .filter(record -> "ACE-5-75".equals(record.getSecMsgCode()))
+                    .map(record -> new ExtractedData(
+                            record.getEmployeeNo(),
+                            record.getAuthResultMsg(),
+                            record.getGmtCreate(),
+                            record.getPortraitUrl()  // 可能为null,不影响
+                    ))
+                    .filter(data -> data.getEmployeeNo() != null)  // 可选:排除无效数据
+                    .collect(Collectors.toList());
+        }
+
+        return result;
+    }
+
+    /**
+     * 验证响应是否有效
+     */
+    private boolean isValidResponse(JSONObject response) {
+        if (response == null) return false;
+        if (response.getIntValue("code") != 0) return false;
+        return response.containsKey("data");
+    }
+
+    /**
+     * DTO类 - 原始门禁记录
+     */
+    @Data
+    private static class DoorRecordDTO {
+        private String employeeNo;
+        private String authResultMsg;
+        private String gmtCreate;
+        private String portraitUrl;
+        private String secMsgCode;
+    }
+
+    /**
+     * DTO类 - 提取结果
+     */
+    @Data
+    @AllArgsConstructor
+    private static class ExtractedData {
+        private String employeeNo;
+        private String authResultMsg;
+        private String gmtCreate;
+        private String portraitUrl;
+    }
 }

+ 1 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppIsin.java

@@ -37,6 +37,7 @@ public class AppIsin implements Serializable {
     @Excel(name = "部门编号", width = 15)
     @Schema(description = "部门编号")
     private String orgCode;
+    private String originId;
 	/**订单ID*/
 	@Excel(name = "订单ID", width = 15)
     @Schema(description = "订单ID")