数据转换
This commit is contained in:
		| @ -18,6 +18,11 @@ | ||||
|  | ||||
|     <dependencies> | ||||
|  | ||||
| <!--        <dependency>--> | ||||
| <!--            <groupId>com.drewnoakes</groupId>--> | ||||
| <!--            <artifactId>metadata-extractor</artifactId>--> | ||||
| <!--            <version>2.18.0</version>--> | ||||
| <!--        </dependency>--> | ||||
|  | ||||
|  | ||||
| <!--        <dependency>--> | ||||
|  | ||||
| @ -20,11 +20,16 @@ import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.math.BigDecimal; | ||||
| import java.math.RoundingMode; | ||||
| import java.text.DecimalFormat; | ||||
| import java.time.LocalDate; | ||||
| import java.time.temporal.ChronoUnit; | ||||
| import java.util.List; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 企业级大屏 | ||||
| @ -133,13 +138,17 @@ public class EnterpriseBigScreenController { | ||||
|             .map(BusUserProjectRelevancy::getUserId) | ||||
|             .distinct().count()); | ||||
|  | ||||
|         peopleCountVo.setManagersCount(list.stream().filter(item -> "2".equals(item.getUserType())) | ||||
|             .map(BusUserProjectRelevancy::getUserId) | ||||
|             .distinct().count()); | ||||
| //        peopleCountVo.setManagersCount(list.stream().filter(item -> "2".equals(item.getUserType())) | ||||
| //            .map(BusUserProjectRelevancy::getUserId) | ||||
| //            .distinct().count()); | ||||
|         peopleCountVo.setManagersCount(69L); | ||||
|  | ||||
| //        peopleCountVo.setSubcontractorsCount(list.stream().filter(item -> "3".equals(item.getUserType())) | ||||
| //            .map(BusUserProjectRelevancy::getUserId) | ||||
| //            .distinct().count()); | ||||
|  | ||||
|         peopleCountVo.setManagersCount(9L); | ||||
|  | ||||
|         peopleCountVo.setSubcontractorsCount(list.stream().filter(item -> "3".equals(item.getUserType())) | ||||
|             .map(BusUserProjectRelevancy::getUserId) | ||||
|             .distinct().count()); | ||||
|         Integer projectUserCount = projectBigScreenMapper.getUserCount(); | ||||
|         peopleCountVo.setConstructionPersonnelCount(Long.valueOf(projectUserCount)); | ||||
|         return R.ok(peopleCountVo); | ||||
|  | ||||
| @ -358,41 +358,104 @@ public class ProjectBigScreenController { | ||||
|         List<Map<String, Object>> sxtChildrenMap = new ArrayList<>(); | ||||
|         HashMap<String, Object> map1 = new HashMap<>(); | ||||
|         map1.put("id", "55"); | ||||
|         map1.put("label", "1222222"); | ||||
|         map1.put("label", "那荷4号方阵-1"); | ||||
|         map1.put("name", "22"); | ||||
|         map1.put("type", "camera"); | ||||
|         map1.put("lng", 106.48349615411811); | ||||
|         map1.put("lat", 29.54856374364732); | ||||
|         map1.put("lng", 107.111325); | ||||
|         map1.put("lat", 23.820919); | ||||
|         map1.put("alt", 0); | ||||
|         HashMap<String, Object> map2 = new HashMap<>(); | ||||
|         map2.put("id", "56"); | ||||
|         map2.put("label", "1222223"); | ||||
|         map2.put("label", "甫必 1号方阵"); | ||||
|         map2.put("name", "23"); | ||||
|         map2.put("type", "camera"); | ||||
|         map2.put("lng", 106.48442273257676); | ||||
|         map2.put("lat", 29.53841670498476); | ||||
|         map2.put("lng", 107.091297); | ||||
|         map2.put("lat", 23.813567); | ||||
|         map2.put("alt", 0); | ||||
|         HashMap<String, Object> map3 = new HashMap<>(); | ||||
|         map3.put("id", "57"); | ||||
|         map3.put("label", "1222224"); | ||||
|         map3.put("name", "24"); | ||||
|         map3.put("type", "camera"); | ||||
|         map3.put("lng", 106.49197896482423); | ||||
|         map3.put("lat", 29.52931974282576); | ||||
|         map3.put("lng", 107.085442); | ||||
|         map3.put("lat", 23.811958); | ||||
|         map3.put("alt", 0); | ||||
|         HashMap<String, Object> map4 = new HashMap<>(); | ||||
|         map4.put("id", "58"); | ||||
|         map4.put("label", "1222225"); | ||||
|         map4.put("label", "甫必2号方阵-1"); | ||||
|         map4.put("name", "25"); | ||||
|         map4.put("type", "camera"); | ||||
|         map4.put("lng", 106.50293584930655); | ||||
|         map4.put("lat", 29.533025743929034); | ||||
|         map4.put("lng", 107.085181); | ||||
|         map4.put("lat", 23.810556); | ||||
|         map4.put("alt", 0); | ||||
|         HashMap<String, Object> map5 = new HashMap<>(); | ||||
|         map5.put("id", "58"); | ||||
|         map5.put("label", "甫必 4号方阵"); | ||||
|         map5.put("name", "25"); | ||||
|         map5.put("type", "camera"); | ||||
|         map5.put("lng", 107.081747); | ||||
|         map5.put("lat", 23.808131); | ||||
|         map5.put("alt", 0); | ||||
|         HashMap<String, Object> map6 = new HashMap<>(); | ||||
|         map6.put("id", "58"); | ||||
|         map6.put("label", "甫必 7号方阵-1"); | ||||
|         map6.put("name", "25"); | ||||
|         map6.put("type", "camera"); | ||||
|         map6.put("lng", 107.077922); | ||||
|         map6.put("lat", 23.798344); | ||||
|         map6.put("alt", 0); | ||||
|         HashMap<String, Object> map7 = new HashMap<>(); | ||||
|         map7.put("id", "58"); | ||||
|         map7.put("label", "68甫必6"); | ||||
|         map7.put("name", "25"); | ||||
|         map7.put("type", "camera"); | ||||
|         map7.put("lng", 107.077333); | ||||
|         map7.put("lat", 23.797969); | ||||
|         map7.put("alt", 0); | ||||
|         HashMap<String, Object> map8 = new HashMap<>(); | ||||
|         map8.put("id", "58"); | ||||
|         map8.put("label", "甫必5号方阵"); | ||||
|         map8.put("name", "25"); | ||||
|         map8.put("type", "camera"); | ||||
|         map8.put("lng", 107.075853); | ||||
|         map8.put("lat", 23.796711); | ||||
|         map8.put("alt", 0); | ||||
|         HashMap<String, Object> map9 = new HashMap<>(); | ||||
|         map9.put("id", "58"); | ||||
|         map9.put("label", "西牛2号方阵"); | ||||
|         map9.put("name", "25"); | ||||
|         map9.put("type", "camera"); | ||||
|         map9.put("lng", 107.078942); | ||||
|         map9.put("lat", 23.789306); | ||||
|         map9.put("alt", 0); | ||||
|         HashMap<String, Object> map10 = new HashMap<>(); | ||||
|         map10.put("id", "58"); | ||||
|         map10.put("label", "福绿1号方阵"); | ||||
|         map10.put("name", "25"); | ||||
|         map10.put("type", "camera"); | ||||
|         map10.put("lng", 107.090061); | ||||
|         map10.put("lat", 23.790411); | ||||
|         map10.put("alt", 0); | ||||
|         HashMap<String, Object> map11 = new HashMap<>(); | ||||
|         map11.put("id", "58"); | ||||
|         map11.put("label", "福绿6号方阵-2"); | ||||
|         map11.put("name", "25"); | ||||
|         map11.put("type", "camera"); | ||||
|         map11.put("lng",  107.112883); | ||||
|         map11.put("lat", 23.771378); | ||||
|         map11.put("alt", 0); | ||||
|  | ||||
|         sxtChildrenMap.add(map1); | ||||
|         sxtChildrenMap.add(map2); | ||||
|         sxtChildrenMap.add(map3); | ||||
|         sxtChildrenMap.add(map4); | ||||
|         sxtChildrenMap.add(map5); | ||||
|         sxtChildrenMap.add(map6); | ||||
|         sxtChildrenMap.add(map7); | ||||
|         sxtChildrenMap.add(map8); | ||||
|         sxtChildrenMap.add(map9); | ||||
|         sxtChildrenMap.add(map10); | ||||
|         sxtChildrenMap.add(map11); | ||||
|  | ||||
|         return sxtChildrenMap; | ||||
|     } | ||||
| @ -401,41 +464,22 @@ public class ProjectBigScreenController { | ||||
|         List<Map<String, Object>> sxtChildrenMap = new ArrayList<>(); | ||||
|         HashMap<String, Object> map1 = new HashMap<>(); | ||||
|         map1.put("id", "65"); | ||||
|         map1.put("label", "6222222"); | ||||
|         map1.put("label", "田东无人机"); | ||||
|         map1.put("name", "32"); | ||||
|         map1.put("type", "drone"); | ||||
|         map1.put("lng", 106.49556855602525); | ||||
|         map1.put("lat", 29.534393226355515); | ||||
|         map1.put("lng", 107.12744694624267); | ||||
|         map1.put("lat", 23.615965741917278); | ||||
|         map1.put("alt", 0); | ||||
|         HashMap<String, Object> map2 = new HashMap<>(); | ||||
|         map2.put("id", "66"); | ||||
|         map2.put("label", "2222223"); | ||||
|         map2.put("name", "33"); | ||||
|         map2.put("type", "drone"); | ||||
|         map2.put("lng", 106.49142431645038); | ||||
|         map2.put("lat", 29.534472802500083); | ||||
|         map2.put("alt", 0); | ||||
|         HashMap<String, Object> map3 = new HashMap<>(); | ||||
|         map3.put("id", "67"); | ||||
|         map3.put("label", "2222224"); | ||||
|         map3.put("name", "34"); | ||||
|         map3.put("type", "drone"); | ||||
|         map3.put("lng", 106.49142125177437); | ||||
|         map3.put("lat", 29.541881138875755); | ||||
|         map3.put("alt", 0); | ||||
|         HashMap<String, Object> map4 = new HashMap<>(); | ||||
|         map4.put("id", "68"); | ||||
|         map4.put("label", "2222225"); | ||||
|         map4.put("name", "35"); | ||||
|         map4.put("type", "drone"); | ||||
|         map4.put("lng", 106.50256649933792); | ||||
|         map4.put("lat", 29.54260793685717); | ||||
|         map4.put("alt", 0); | ||||
|  | ||||
| //        HashMap<String, Object> map2 = new HashMap<>(); | ||||
| //        map2.put("id", "66"); | ||||
| //        map2.put("label", "长顺无人机"); | ||||
| //        map2.put("name", "33"); | ||||
| //        map2.put("type", "drone"); | ||||
| //        map2.put("lng", 106.49142431645038); | ||||
| //        map2.put("lat", 29.534472802500083); | ||||
| //        map2.put("alt", 0); | ||||
|         sxtChildrenMap.add(map1); | ||||
|         sxtChildrenMap.add(map2); | ||||
|         sxtChildrenMap.add(map3); | ||||
|         sxtChildrenMap.add(map4); | ||||
| //        sxtChildrenMap.add(map2); | ||||
|  | ||||
|         return sxtChildrenMap; | ||||
|     } | ||||
|  | ||||
| @ -21,7 +21,7 @@ import java.io.Serial; | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @TableName("bus_attendance") | ||||
| @TableName("bus_attendance_copy1") | ||||
| public class BusAttendance extends BaseEntity { | ||||
|  | ||||
|     @Serial | ||||
|  | ||||
| @ -188,6 +188,7 @@ public class BusProjectTeamServiceImpl extends ServiceImpl<BusProjectTeamMapper, | ||||
|         // 判断是否重名 | ||||
|         String teamName = entity.getTeamName(); | ||||
|         LambdaQueryWrapper<BusProjectTeam> queryWrapper = new LambdaQueryWrapper<>(); | ||||
|         queryWrapper.eq(BusProjectTeam::getProjectId, entity.getProjectId()); | ||||
|         queryWrapper.eq(BusProjectTeam::getTeamName, teamName); | ||||
|         if (entity.getId() != null) { | ||||
|             queryWrapper.ne(BusProjectTeam::getId, entity.getId()); | ||||
|  | ||||
| @ -24,6 +24,7 @@ import org.dromara.common.oss.core.OssClient; | ||||
| import org.dromara.common.oss.entity.UploadResult; | ||||
| import org.dromara.common.oss.enumd.AccessPolicyType; | ||||
| import org.dromara.common.oss.factory.OssFactory; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.system.domain.SysOss; | ||||
| import org.dromara.system.domain.bo.SysOssBo; | ||||
| import org.dromara.system.domain.vo.SysOssUploadVo; | ||||
| @ -44,10 +45,8 @@ import java.net.URI; | ||||
| import java.net.URL; | ||||
| import java.net.URLConnection; | ||||
| import java.time.Duration; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * 文件上传 服务层实现 | ||||
| @ -400,6 +399,13 @@ public class SysOssServiceImpl implements ISysOssService, OssService { | ||||
|         oss.setFileName(uploadResult.getFilename()); | ||||
|         oss.setOriginalName(originalfileName); | ||||
|         oss.setService(configKey); | ||||
|  | ||||
|         //罗成负责删掉 | ||||
|         oss.setCreateBy(1L); | ||||
|         oss.setUpdateBy(1L); | ||||
|         oss.setCreateTime(new Date()); | ||||
|         oss.setUpdateTime(new Date()); | ||||
|  | ||||
|         baseMapper.insert(oss); | ||||
|         SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class); | ||||
|         return this.matchingUrl(sysOssVo); | ||||
|  | ||||
| @ -1,10 +1,15 @@ | ||||
| package org.dromara.transferData.controller; | ||||
|  | ||||
| import cn.dev33.satoken.annotation.SaCheckPermission; | ||||
| import cn.hutool.core.collection.CollectionUtil; | ||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||
| import jakarta.activation.MimetypesFileTypeMap; | ||||
| import jakarta.annotation.Resource; | ||||
| import jakarta.validation.constraints.NotNull; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.dromara.common.core.domain.R; | ||||
| import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo; | ||||
| import org.dromara.project.domain.BusAttendanceRule; | ||||
| import org.dromara.project.domain.vo.BusAttendanceRuleVo; | ||||
| import org.dromara.transferData.domain.ConstructionUserCopy; | ||||
| import org.dromara.transferData.domain.OldAttendance; | ||||
| @ -15,6 +20,9 @@ import org.dromara.project.service.IBusAttendanceRuleService; | ||||
| import org.dromara.project.service.IBusAttendanceService; | ||||
| import org.dromara.system.domain.vo.SysOssVo; | ||||
| import org.dromara.system.service.ISysOssService; | ||||
| import org.dromara.transferData.service.TransferDataService; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| @ -27,9 +35,15 @@ import java.nio.file.Paths; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.LocalTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.time.format.DateTimeParseException; | ||||
| import java.time.temporal.ChronoUnit; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @RestController | ||||
| @RequestMapping("/transferData") | ||||
| @ -39,13 +53,10 @@ public class TransferDataController { | ||||
|     @Resource | ||||
|     private TransferDataMapper transferDataMapper; | ||||
|     @Resource | ||||
|     private IBusAttendanceService attendanceService; | ||||
|     @Resource | ||||
|     private IBusAttendanceRuleService attendanceRuleService; | ||||
|     @Resource | ||||
|     private ISubConstructionUserService constructionUserService; | ||||
|     @Resource | ||||
|     private ISysOssService ossService; | ||||
|     private TransferDataService transferDataService; | ||||
|  | ||||
|  | ||||
|     // 两个候选基础URL | ||||
|     private static final String[] BASE_URLS = { | ||||
| @ -56,7 +67,14 @@ public class TransferDataController { | ||||
|  | ||||
|     @RequestMapping("/transferAttendance") | ||||
|     public void transferAttendance() { | ||||
|         List<BusAttendance> arrs = new ArrayList<>(); | ||||
|  | ||||
|         List<OldAttendance> data = transferDataMapper.getData(); | ||||
|  | ||||
|         List<BusAttendanceRule> list = attendanceRuleService.list(Wrappers.<BusAttendanceRule>lambdaQuery() | ||||
|             .in(BusAttendanceRule::getProjectId, Arrays.asList(1897160897167638529L, 1897161054676336641L))); | ||||
|         Map<Long, BusAttendanceRule> rules = list.stream().collect(Collectors.toMap(BusAttendanceRule::getProjectId, vo -> vo)); | ||||
|  | ||||
|         for (OldAttendance oldAttendance : data) { | ||||
|             ConstructionUserCopy constructionUserCopy = transferDataMapper.getConstructionUserCopy(oldAttendance.getOpenid()); | ||||
|  | ||||
| @ -64,27 +82,37 @@ public class TransferDataController { | ||||
|                 continue; | ||||
|             } | ||||
|             LocalDate clockDate = LocalDate.parse(oldAttendance.getPrintingDate()); | ||||
|             //判定是否重读 | ||||
|             List<BusAttendance> list = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class) | ||||
|                 .eq(BusAttendance::getUserId, constructionUserCopy.getSysUserId()) | ||||
|                 .eq(BusAttendance::getProjectId, constructionUserCopy.getProjectId()) | ||||
|                 .eq(BusAttendance::getClockDate, clockDate) | ||||
|                 .eq(BusAttendance::getClockType, oldAttendance.getCommuter()) | ||||
|             ); | ||||
|             if(CollectionUtil.isNotEmpty(list)){ | ||||
|                 continue; | ||||
| //            //判定是否重读 | ||||
| //            List<BusAttendance> list = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class) | ||||
| //                .eq(BusAttendance::getUserId, constructionUserCopy.getSysUserId()) | ||||
| //                .eq(BusAttendance::getProjectId, constructionUserCopy.getProjectId()) | ||||
| //                .eq(BusAttendance::getClockDate, clockDate) | ||||
| //                .eq(BusAttendance::getClockType, oldAttendance.getCommuter()) | ||||
| //            ); | ||||
| //            if(CollectionUtil.isNotEmpty(list)){ | ||||
| //                continue; | ||||
| //            } | ||||
|             if (oldAttendance.getProjectId() == 60){ | ||||
|                 oldAttendance.setProjectId(1897160897167638529L); | ||||
|             } | ||||
|             if (oldAttendance.getProjectId() == 59){ | ||||
|                 oldAttendance.setProjectId(1897161054676336641L); | ||||
|             } | ||||
|  | ||||
|             BusAttendance busAttendance = new BusAttendance(); | ||||
|  | ||||
|             if(oldAttendance.getPacePhoto()!=null){ | ||||
|                 Long l = handleFaceImage(oldAttendance.getPacePhoto()); | ||||
|                 busAttendance.setFacePic(l==null?"":l.toString()); | ||||
|             } | ||||
|             //处理照片 | ||||
|  | ||||
|             busAttendance.setFacePic(oldAttendance.getPacePhoto()); | ||||
|  | ||||
| //            if(oldAttendance.getPacePhoto()!=null){ | ||||
| //                Long l = handleFaceImage(oldAttendance.getPacePhoto()); | ||||
| //                busAttendance.setFacePic(l==null?"":l.toString()); | ||||
| //            } | ||||
|  | ||||
|             busAttendance.setUserName(constructionUserCopy.getUserName()); | ||||
|             busAttendance.setUserId(constructionUserCopy.getSysUserId()); | ||||
|             busAttendance.setProjectId(constructionUserCopy.getProjectId()); | ||||
| //            busAttendance.setProjectId(constructionUserCopy.getProjectId()); | ||||
|  | ||||
|             // 转换日期字段 | ||||
|             busAttendance.setClockDate(clockDate); | ||||
| @ -102,7 +130,7 @@ public class TransferDataController { | ||||
|                 busAttendance.setClockTime(parseClockOn(oldAttendance.getClockOn(), busAttendance)); | ||||
|             } | ||||
|             //规则和迟到早退时间计算 | ||||
|             BusAttendanceRuleVo busAttendanceRuleVo = attendanceRuleService.queryByProjectId(busAttendance.getProjectId()); | ||||
|             BusAttendanceRule busAttendanceRuleVo = rules.get(busAttendance.getProjectId()); | ||||
|             if (busAttendanceRuleVo != null) { | ||||
|                 LocalTime clockInTime = busAttendanceRuleVo.getClockInTime(); | ||||
|                 LocalTime clockOutTime = busAttendanceRuleVo.getClockOutTime(); | ||||
| @ -125,53 +153,35 @@ public class TransferDataController { | ||||
|                 } | ||||
|                 busAttendance.setHandle("5".equals(oldAttendance.getIsPinch())?"1":"0"); | ||||
|             } | ||||
|             arrs.add(busAttendance); | ||||
|             if(arrs.size() >= 1000){ | ||||
|                 List<BusAttendance> batchList = new ArrayList<>(arrs); | ||||
|                 // 提交异步任务:处理照片 + 批量保存 | ||||
|                 transferDataService.handlePhotoAndSaveBatch(batchList); | ||||
|                 arrs.clear(); | ||||
|             } | ||||
|         } | ||||
|         if (CollectionUtil.isNotEmpty(arrs)) { | ||||
|             List<BusAttendance> batchList = new ArrayList<>(arrs); | ||||
|             transferDataService.handlePhotoAndSaveBatch(batchList); | ||||
|         } | ||||
| //        attendanceService.saveBatch(arrs); | ||||
|  | ||||
| //        transferDataMapper.saveBatchCopy(arrs); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public Long handleFaceImage(String relativePath) { | ||||
|         String normalizedPath = relativePath.replace("\\", "/"); | ||||
|         String filename = Paths.get(normalizedPath).getFileName().toString(); | ||||
|  | ||||
|         // 使用 MimetypesFileTypeMap 解析 | ||||
|         MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); | ||||
|         String contentType = fileTypeMap.getContentType(filename); | ||||
|  | ||||
|         for (String baseUrl : BASE_URLS) { | ||||
|             String fullUrl = baseUrl + normalizedPath; | ||||
|             try { | ||||
|                 HttpClient client = HttpClient.newHttpClient(); | ||||
|                 HttpRequest request = HttpRequest.newBuilder() | ||||
|                     .uri(URI.create(fullUrl)) | ||||
|                     .GET() | ||||
|                     .build(); | ||||
|  | ||||
|                 HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); | ||||
|  | ||||
|                 if (response.statusCode() == 200) { | ||||
|                     long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1); | ||||
|                     SysOssVo ossVo = ossService.upload(response.body(), filename, contentType, contentLength); | ||||
|                     return ossVo.getOssId(); | ||||
|                 } | ||||
|             } catch (Exception e) { | ||||
|                 log.warn("尝试URL失败: {}", fullUrl, e); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private LocalDateTime parseClockOn(String clockOn, BusAttendance busAttendance) { | ||||
|         if (clockOn == null || "缺卡".equals(clockOn)) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||||
|         DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd H:mm:ss"); | ||||
|         try { | ||||
|  | ||||
|             // 1. 尝试完整日期时间解析(如 "2023-10-14 07:35:07") | ||||
|             return LocalDateTime.parse(clockOn); | ||||
|             return LocalDateTime.parse(clockOn,formatter); | ||||
|         } catch (DateTimeParseException e) { | ||||
|             try { | ||||
|                 // 2. 仅时间解析(如 "5:38:00")并结合已有的日期 | ||||
| @ -179,7 +189,7 @@ public class TransferDataController { | ||||
|                 if (date == null) { | ||||
|                     return null; // 日期不存在时返回 null | ||||
|                 } | ||||
|                 LocalTime time = LocalTime.parse(clockOn); | ||||
|                 LocalTime time = LocalTime.parse(clockOn,formatter1); | ||||
|                 return LocalDateTime.of(date, time); | ||||
|             } catch (DateTimeParseException ex) { | ||||
|                 log.warn("无法解析打卡时间: {}", clockOn); | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| package org.dromara.transferData.domain; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.IdType; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import lombok.Data; | ||||
| import java.math.BigDecimal; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| @ -1,9 +1,11 @@ | ||||
| package org.dromara.transferData.mapper; | ||||
|  | ||||
| import com.baomidou.dynamic.datasource.annotation.DS; | ||||
| import org.apache.ibatis.annotations.Insert; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
| import org.apache.ibatis.annotations.Param; | ||||
| import org.apache.ibatis.annotations.Select; | ||||
| import org.dromara.project.domain.BusAttendance; | ||||
| import org.dromara.transferData.domain.ConstructionUserCopy; | ||||
| import org.dromara.transferData.domain.OldAttendance; | ||||
|  | ||||
| @ -15,10 +17,27 @@ public interface TransferDataMapper { | ||||
|  | ||||
|  | ||||
|     @DS("slave") | ||||
|     @Select("select * from bus_attendance") | ||||
|     @Select("select * from bus_attendance where  project_id in (59,60)") | ||||
|     List<OldAttendance> getData(); | ||||
|  | ||||
|  | ||||
|     @Select("select id,sys_user_id,project_id,user_name from sub_construction_user_copy1 where go_openid = #{openId}") | ||||
|     ConstructionUserCopy getConstructionUserCopy(@Param("openId") String openId); | ||||
|  | ||||
|  | ||||
|     @Insert("<script>" + | ||||
|         "insert into sub_construction_user_copy1 " + | ||||
|         "(id, sys_user_id, project_id, user_name, go_openid, " + | ||||
|         "face_pic, clock_date, clock_time, clock_status, " + | ||||
|         "minute_count, clock_type, clock_location, lng, lat, " + | ||||
|         "rule_time, handle, create_time, update_time) " + | ||||
|         "values " + | ||||
|         "<foreach collection='list' item='item' separator=','>" + | ||||
|         "(#{item.id}, #{item.userId}, #{item.projectId}, #{item.userName}, #{item.openId}, " + | ||||
|         "#{item.facePic}, #{item.clockDate}, #{item.clockTime}, #{item.clockStatus}, " + | ||||
|         "#{item.minuteCount}, #{item.clockType}, #{item.clockLocation}, #{item.lng}, #{item.lat}, " + | ||||
|         "#{item.ruleTime}, #{item.handle}, #{item.createTime}, #{item.updateTime})" + | ||||
|         "</foreach>" + | ||||
|         "</script>") | ||||
|     int saveBatchCopy(@Param("list") List<BusAttendance> busAttendanceList); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,29 @@ | ||||
| package org.dromara.transferData.service; | ||||
|  | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
|  | ||||
| @Configuration | ||||
| public class TransferAsyncConfig { | ||||
|  | ||||
|     /** | ||||
|      * 考勤异步任务专用线程池 | ||||
|      */ | ||||
|     @Bean("attendanceAsyncPool") | ||||
|     public Executor attendanceAsyncPool() { | ||||
|         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||||
|         int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数:CPU核心数*2 | ||||
|         executor.setCorePoolSize(corePoolSize); | ||||
|         executor.setMaxPoolSize(corePoolSize * 2); // 最大线程数 | ||||
|         executor.setQueueCapacity(1000); // 任务队列容量 | ||||
|         executor.setKeepAliveSeconds(60); // 空闲线程存活时间 | ||||
|         executor.setThreadNamePrefix("attendance-async-"); // 线程名称前缀(便于排查) | ||||
|         // 拒绝策略:任务满时,由提交任务的线程(如主线程)执行,避免任务丢失 | ||||
|         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | ||||
|         executor.initialize(); | ||||
|         return executor; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,229 @@ | ||||
| package org.dromara.transferData.service; | ||||
|  | ||||
| import jakarta.activation.MimetypesFileTypeMap; | ||||
| import jakarta.annotation.Resource; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.SerializationUtils; | ||||
| import org.dromara.common.core.service.OssService; | ||||
| import org.dromara.contractor.service.ISubConstructionUserService; | ||||
| import org.dromara.project.domain.BusAttendance; | ||||
| import org.dromara.project.service.IBusAttendanceRuleService; | ||||
| import org.dromara.project.service.IBusAttendanceService; | ||||
| import org.dromara.system.domain.vo.SysOssVo; | ||||
| import org.dromara.system.service.ISysOssService; | ||||
| import org.springframework.beans.factory.annotation.Qualifier; | ||||
| import org.springframework.scheduling.annotation.Async; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import java.io.InputStream; | ||||
| import java.net.URI; | ||||
| import java.net.http.HttpClient; | ||||
| import java.net.http.HttpRequest; | ||||
| import java.net.http.HttpResponse; | ||||
| import java.nio.file.Paths; | ||||
| import java.time.Duration; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.concurrent.Executor; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * @Author 铁憨憨 | ||||
|  * @Date 2025/9/14 10:53 | ||||
|  * @Version 1.0 | ||||
|  */ | ||||
| @Service | ||||
| @Slf4j | ||||
| public class TransferDataService { | ||||
|     // 1. 全局复用HttpClient(设置超时,线程安全) | ||||
|     private final HttpClient httpClient = HttpClient.newBuilder() | ||||
|         .connectTimeout(Duration.ofSeconds(5)) // 连接超时5秒 | ||||
|         .followRedirects(HttpClient.Redirect.NORMAL) // 跟随重定向 | ||||
|         .build(); | ||||
|  | ||||
|     // 2. 注入依赖(原依赖不变) | ||||
|     @Resource | ||||
|     private IBusAttendanceService attendanceService; | ||||
|     @Resource | ||||
|     private ISysOssService ossService; | ||||
|     @Resource | ||||
|     @Qualifier("attendanceAsyncPool") // 对应之前 AsyncConfig 中定义的线程池Bean名 | ||||
|     private Executor attendanceAsyncExecutor; | ||||
|  | ||||
|  | ||||
|     // 两个候选基础URL | ||||
|     private static final String[] BASE_URLS = { | ||||
|         "http://xny.yj-3d.com:7464", | ||||
|         "http://xny.yj-3d.com:7363" | ||||
|     }; | ||||
|     /** | ||||
|      * 异步处理照片并批量保存(修复线程安全 + 性能优化) | ||||
|      * @param batchList 原始批量数据(浅拷贝) | ||||
|      */ | ||||
|     // 2. 修复后的照片批量异步处理逻辑 | ||||
|     public CompletableFuture<Boolean> handlePhotoAndSaveBatch(List<BusAttendance> batchList) { | ||||
|         log.info("异步处理照片和保存开始,条数:{}", batchList.size()); | ||||
|  | ||||
|         try { | ||||
|             // 步骤1:深度拷贝集合(不变,确保线程安全) | ||||
|             List<BusAttendance> deepCopyList = batchList.stream() | ||||
|                 .map(attendance -> { | ||||
|                     // 深拷贝实现(二选一,根据实体类是否实现Serializable) | ||||
|                     // 方式1:Spring SerializationUtils(需实体类实现Serializable) | ||||
|                     BusAttendance copy = SerializationUtils.deserialize(SerializationUtils.serialize(attendance)); | ||||
|                     // 方式2:手动拷贝(无Serializable依赖) | ||||
|                     // BusAttendance copy = new BusAttendance(); | ||||
|                     // BeanUtils.copyProperties(attendance, copy); | ||||
|                     copy.setCreateBy(1L); | ||||
|                     copy.setUpdateBy(1L); | ||||
|                     return copy; | ||||
|                 }) | ||||
|                 .collect(Collectors.toList()); | ||||
|  | ||||
|             // 步骤2:修复Stream流语法,生成CompletableFuture数组(关键修复点) | ||||
|             // 核心:map(中间操作)→ collect(终止操作生成列表)→ toArray(转数组) | ||||
|             CompletableFuture<Void> photoHandleFuture = CompletableFuture.allOf( | ||||
|                 // 2.1 流处理:每条数据生成一个CompletableFuture | ||||
|                 deepCopyList.stream() | ||||
|                     .map(attendance -> | ||||
|                         // 2.2 异步执行单条照片处理(使用自定义线程池,避免复用HttpClient线程池) | ||||
|                         CompletableFuture.runAsync(() -> { | ||||
|                             try { | ||||
|                                 handleSinglePhoto(attendance); // 处理单张照片 | ||||
|                             } catch (Exception e) { | ||||
|                                 log.error("处理单条照片失败,userId={}, clockDate={}", | ||||
|                                     attendance.getUserId(), attendance.getClockDate(), e); | ||||
|                                 attendance.setFacePic(""); // 失败标记,不影响整体保存 | ||||
|                             } | ||||
|                         }, attendanceAsyncExecutor) // 改用自定义线程池(核心优化) | ||||
|                     ) | ||||
|                     // 2.3 终止操作:将Stream<CompletableFuture<Void>> 转为 List | ||||
|                     .collect(Collectors.toList()) | ||||
|                     // 2.4 转数组:List → 数组,适配CompletableFuture.allOf的参数要求 | ||||
|                     .toArray(new CompletableFuture[0]) | ||||
|             ); | ||||
|  | ||||
|             // 步骤3:等待所有照片处理完成(阻塞当前异步任务线程,不阻塞主线程) | ||||
|             photoHandleFuture.get(); // 若需超时控制,可加参数:photoHandleFuture.get(30, TimeUnit.SECONDS) | ||||
|  | ||||
|             // 步骤4:批量保存数据库(不变) | ||||
|             boolean saveSuccess = attendanceService.saveBatch(deepCopyList); | ||||
|             if (saveSuccess) { | ||||
|                 log.info("异步批量保存成功,条数:{}", deepCopyList.size()); | ||||
|             } else { | ||||
|                 log.error("异步批量保存失败,条数:{}", deepCopyList.size()); | ||||
|                 // 可选:重试逻辑 | ||||
|                 saveSuccess = attendanceService.saveBatch(deepCopyList); | ||||
|                 log.info("异步批量保存重试结果:{},条数:{}", saveSuccess ? "成功" : "失败", deepCopyList.size()); | ||||
|             } | ||||
|  | ||||
|             return CompletableFuture.completedFuture(saveSuccess); | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             log.error("异步处理照片和保存整体失败,条数:{}", batchList.size(), e); | ||||
|             return CompletableFuture.completedFuture(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理单条照片(修复资源释放 + 超时控制 + 重试) | ||||
|      */ | ||||
|     private void handleSinglePhoto(BusAttendance attendance) { | ||||
|         String facePicRelativePath = attendance.getFacePic(); | ||||
|         if (facePicRelativePath == null || facePicRelativePath.isEmpty()) { | ||||
|             attendance.setFacePic(""); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 1. 规范化路径(原逻辑不变) | ||||
|         String normalizedPath = facePicRelativePath.replace("\\", "/"); | ||||
|         String filename = Paths.get(normalizedPath).getFileName().toString(); | ||||
|  | ||||
|         // 2. 解析文件类型(优化:优先从HTTP响应头获取,其次用文件名) | ||||
|         String contentType = "application/octet-stream"; // 默认二进制类型 | ||||
|  | ||||
|         // 3. 多URL重试(原逻辑,增加超时和流关闭) | ||||
|         for (int retry = 0; retry < 2; retry++) { // 重试1次(共2次机会) | ||||
|             for (String baseUrl : BASE_URLS) { | ||||
|                 String fullUrl = baseUrl + normalizedPath; | ||||
|                 try { | ||||
|                     // 构建带超时的HTTP请求 | ||||
|                     HttpRequest request = HttpRequest.newBuilder() | ||||
|                         .uri(URI.create(fullUrl)) | ||||
|                         .GET() | ||||
|                         .timeout(Duration.ofSeconds(10)) // 读取超时10秒 | ||||
|                         .build(); | ||||
|  | ||||
|                     // 发送请求并处理响应 | ||||
|                     try (InputStream inputStream = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()).body()) { | ||||
|                         // 从响应头获取真实Content-Type(比文件名解析更准确) | ||||
|                         HttpResponse<?> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); | ||||
|                         contentType = response.headers().firstValue("Content-Type").orElse(contentType); | ||||
|                         long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1); | ||||
|  | ||||
|                         // 上传OSS(此时流已通过try-with-resources自动关闭) | ||||
|                         SysOssVo ossVo = ossService.upload(inputStream, filename, contentType, contentLength); | ||||
|                         attendance.setFacePic(ossVo.getOssId() == null ? "" : ossVo.getOssId().toString()); | ||||
|                         return; // 成功则跳出所有循环 | ||||
|                     } | ||||
|                 } catch (Exception e) { | ||||
|                     log.warn("尝试URL失败(重试{}次): {}", retry + 1, fullUrl, e); | ||||
|                     continue; // 重试下一个URL | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 所有URL和重试都失败 | ||||
|         log.error("照片处理失败,所有URL重试完毕,relativePath={}", normalizedPath); | ||||
|         attendance.setFacePic(""); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private Long handleSinglePhoto1(String facePicRelativePath) { | ||||
|  | ||||
|         if (facePicRelativePath == null || facePicRelativePath.isEmpty()) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // 1. 规范化路径(原逻辑不变) | ||||
|         String normalizedPath = facePicRelativePath.replace("\\", "/"); | ||||
|         String filename = Paths.get(normalizedPath).getFileName().toString(); | ||||
|  | ||||
|         // 2. 解析文件类型(优化:优先从HTTP响应头获取,其次用文件名) | ||||
|         String contentType = "application/octet-stream"; // 默认二进制类型 | ||||
|  | ||||
|         // 3. 多URL重试(原逻辑,增加超时和流关闭) | ||||
|         for (int retry = 0; retry < 2; retry++) { // 重试1次(共2次机会) | ||||
|             for (String baseUrl : BASE_URLS) { | ||||
|                 String fullUrl = baseUrl + normalizedPath; | ||||
|                 try { | ||||
|                     // 构建带超时的HTTP请求 | ||||
|                     HttpRequest request = HttpRequest.newBuilder() | ||||
|                         .uri(URI.create(fullUrl)) | ||||
|                         .GET() | ||||
|                         .timeout(Duration.ofSeconds(10)) // 读取超时10秒 | ||||
|                         .build(); | ||||
|  | ||||
|                     // 发送请求并处理响应 | ||||
|                     try (InputStream inputStream = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()).body()) { | ||||
|                         // 从响应头获取真实Content-Type(比文件名解析更准确) | ||||
|                         HttpResponse<?> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); | ||||
|                         contentType = response.headers().firstValue("Content-Type").orElse(contentType); | ||||
|                         long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1); | ||||
|  | ||||
|                         // 上传OSS(此时流已通过try-with-resources自动关闭) | ||||
|                         SysOssVo ossVo = ossService.upload(inputStream, filename, contentType, contentLength); | ||||
|                         return ossVo.getOssId(); // 成功则跳出所有循环 | ||||
|                     } | ||||
|                 } catch (Exception e) { | ||||
|                     log.warn("尝试URL失败(重试{}次): {}", retry + 1, fullUrl, e); | ||||
|                     continue; // 重试下一个URL | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 所有URL和重试都失败 | ||||
|         log.error("照片处理失败,所有URL重试完毕,relativePath={}", normalizedPath); | ||||
|        return null; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user