From 204177afa017378dc421a0e73cdb0ac9497f6328 Mon Sep 17 00:00:00 2001 From: zt Date: Thu, 6 Nov 2025 11:15:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/dataTransmission/TokenUtils.java | 150 ++++++++++++++++++ .../clarityPm/dto/AttendanceRecord.java | 71 +++++++++ .../clarityPm/dto/RealUser.java | 92 +++++++++++ .../method/ClarityPmAsyncMethod.java | 123 ++++++++++++++ .../clarityPm/method/ClarityPmClient.java | 132 +++++++++++++++ .../impl/BusProjectTeamMemberServiceImpl.java | 3 + 6 files changed, 571 insertions(+) create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/TokenUtils.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/AttendanceRecord.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/RealUser.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmAsyncMethod.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmClient.java diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/TokenUtils.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/TokenUtils.java new file mode 100644 index 00000000..78a57ed2 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/TokenUtils.java @@ -0,0 +1,150 @@ +package org.dromara.dataTransmission; + +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; + +import java.time.Duration; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.dromara.common.redis.utils.RedisUtils; +import org.springframework.stereotype.Component; + + + +@Component +public class TokenUtils { + + // Token过期时间:25分钟(与Redis存储过期时间保持一致) + private static final long TOKEN_EXPIRE_SECONDS = 1500; + // HTTP请求超时时间:10秒 + private static final Duration HTTP_TIMEOUT = Duration.ofSeconds(10); + //通用字符 + private static final String USER_NAME = "username"; + private static final String PASSWORD = "password"; + private static final String PUBLIC_KEY = "publicKey"; + private static final String URL = "url"; + + //clientId + public static final String CLARITYPM = "claritypm"; + + //接口信息及参数 + private static final Map> tokenMap = new HashMap<>(){ + { + put(CLARITYPM, new HashMap<>(){{ + put(URL, "https://claritypm.powerchina.cn/neSmartsite-api/loginCli"); + put(USER_NAME, "zhangweiwei"); + put(PASSWORD, "Hkrsoft@#2023"); + put(PUBLIC_KEY, "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoaejbjbttHyHuEzHL8lIX5GZZ6zIYrqJpEDlPM4V5LHn19rSAYp2FyAr8y5Ctny9uUdaYbkoFiVQgxWrAYo4X/3O0OFDsowE25FMOLQY0Mn5B6CvVR7Sdt3DqzIzM1tUnJCIbVGNfDMgxLrLwFN8RvOW8MPlB6LgOvlGMDbj+OQIDAQAB"); + }}); + } + }; + + //字段参数 + private static final Map> paramMap = new HashMap<>(){ + { + put(CLARITYPM, new HashMap<>(){{ + + put(USER_NAME, "username"); + put(PASSWORD, "password"); + }}); + } + }; + + /** + * 优先从Redis获取Token,不存在则调用接口获取并存储 + * @param clientId 客户端ID + * @return 有效的Token字符串 + * @throws Exception 当获取过程发生异常时抛出 + */ + public static String getToken(String clientId) throws Exception { + // 1. 定义Redis中的Token键名 + String redisKey = "data_auth:token:" + clientId; + + // 2. 尝试从Redis获取Token + String token = RedisUtils.getCacheObject(redisKey); + if (token != null && !token.isBlank()) { + // 缓存命中,直接返回 + return token; + } + + // 3. 缓存未命中,调用接口获取Token + Map map = tokenMap.get(clientId); + token = fetchTokenFromApi(clientId,map.get(URL), map.get(USER_NAME), rsaEncrypt(map.get(PASSWORD),map.get(PUBLIC_KEY))); + + //4. 存储到Redis(设置25分钟过期) + RedisUtils.setCacheObject(redisKey, token,Duration.ofSeconds(TOKEN_EXPIRE_SECONDS) ); + + return token; + } + + /** + * 从接口获取Token的内部方法 + */ + private static String fetchTokenFromApi(String clientId, String authUrl, String username, String encryptedPassword) throws Exception { + // 构建JSON请求体(包含加密后的密码) + // 1. 构建请求头(与原 Go 代码一致的头信息) +// Map headers = new HashMap<>(); +// headers.put(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8"); +// headers.put("User-Agent", "Mozilla/5.0"); +// headers.put(HttpHeaders.ACCEPT, "application/json"); +// headers.put("Origin", "https://claritypm.powerchina.cn"); +// headers.put("Referer", "https://claritypm.powerchina.cn/"); + + // 2. 构建请求体(保持你的原有逻辑) + Map map = paramMap.get(clientId); + HashMap param = new HashMap<>(); + param.put(map.get(USER_NAME), username); // 从 map 中获取接口要求的用户名参数名 + param.put(map.get(PASSWORD), encryptedPassword); // 加密后的密码 + String jsonStr = JSONUtil.toJsonStr(param); + System.out.println("请求体:" + jsonStr); + + // 3. 带 Header 发送 POST 请求(Hutool HttpUtil 重载方法) + String post = HttpUtil.createPost(authUrl) +// .addHeaders(headers) // 设置所有请求头 + .body(jsonStr) // 设置请求体 + .execute() // 执行请求 + .body(); // 获取响应体 + + System.out.println("响应结果:" + post); + // 3. 解析响应(核心解析逻辑) + JSONObject responseJson = JSONUtil.parseObj(post); + int code = responseJson.getInt("code"); + String msg = responseJson.getStr("msg"); + String token = responseJson.getStr("token"); + + // 4. 校验响应有效性 + if (code != 200) { + throw new RuntimeException("获取 Token 失败,响应信息:" + msg + ",状态码:" + code); + } + if (token == null || token.trim().isEmpty()) { + throw new RuntimeException("响应中未包含有效 Token,响应内容:" + post); + } + + return token; + } + + /** + * RSA公钥加密(核心加密方法) + * @param content 待加密内容(原始密码) + * @param publicKeyStr 公钥字符串(Base64编码) + * @return 加密后的Base64字符串 + * @throws Exception 加密过程异常 + */ + public static String rsaEncrypt(String content, String publicKeyStr) throws Exception { + RSA rsa = new RSA(null, publicKeyStr); + byte[] encryptedBytes = rsa.encrypt(content, KeyType.PublicKey); + return Base64.getEncoder().encodeToString(encryptedBytes); + } + + + public static void main(String[] args) throws Exception { + String claritypm = getToken(CLARITYPM); + System.out.println(claritypm); + } + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/AttendanceRecord.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/AttendanceRecord.java new file mode 100644 index 00000000..07f370d9 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/AttendanceRecord.java @@ -0,0 +1,71 @@ +package org.dromara.dataTransmission.clarityPm.dto; + +import lombok.Data; + +/** + * 考勤打卡实体类 + */ +@Data +public class AttendanceRecord { + + /** + * 用户姓名(必填) + */ + private String userName; + + /** + * 考勤人身份证号码(必填,与实名制用户保持一致) + */ + private String cardNumber; + + /** + * 人员所属企业(必填) + */ + private String userCompanyName; + + /** + * 考勤设备类型(必填) + * 0.考勤闸机 + * 1.移动考勤机 + * 2.APP打卡 + */ + private String deviceType; + + /** + * 考勤设备名称(非必填) + */ + private String deviceName; + + /** + * 考勤打卡时间(必填,日期格式:yyyy-MM-dd HH:mm:ss) + */ + private String clockInTime; + + /** + * 考勤类型(必填) + * 1.正常 + * 2.外勤 + * 3.出差 + */ + private String clockInStatus; + + /** + * 申请事由(非必填,外勤和出差填写) + */ + private String reason; + + /** + * 维度(必填) + */ + private String lat; + + /** + * 经度(必填) + */ + private String lng; + + /** + * 打卡地点(必填) + */ + private String clockInAddress; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/RealUser.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/RealUser.java new file mode 100644 index 00000000..14612e68 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/dto/RealUser.java @@ -0,0 +1,92 @@ +package org.dromara.dataTransmission.clarityPm.dto; + +import lombok.Data; + +import java.time.LocalDate; + + +/** + * 实名制用户信息实体类(对应接口参数) + */ +@Data +public class RealUser { + // 人员姓名(必填) + private String userName; + + // 是否班组长(非必填,0-否 1-是) + private String classManagerFlag; + + // 手机号码(必填) + private String phone; + + // 性别(必填,1.男 2.女 3.未知) + private String sex; + + // 证件类型(必填,0.身份证) + private String cardType; + + // 证件号码(必填,身份证号码) + private String cardNumber; + + // 人员类型(必填,0.作业人员 1.管理人员) + private String userType; + + // 发证机关(非必填) + private String cardDept; + + // 民族(非必填) + private String nation; + + // 生日(非必填,datetime格式) + private LocalDate birthday; + + // 住址(非必填) + private String address; + + // 头像(非必填,http地址) + private String avatar; + + // 采集相片(非必填,http地址) + private String pic; + + // 安全教育表(非必填,http地址) + private String safetyEdu; + + // 技术交底表(非必填,http地址) + private String technology; + + // 证件有效期始(非必填,datetime格式) + private LocalDate cardStartTime; + + // 证件有效期至(非必填,datetime格式) + private LocalDate cardEndTime; + + // 学历(非必填,1-9对应小学至其他) + private String studyLevel; + + // 政治面貌(非必填,0-3对应党员至民主党派) + private String politicsType; + + // 购买保险(非必填,0-否 1-是) + private String insuranceFlag; + + // 重大病史(非必填,0-否 1-是) + private String diseaseFlag; + + // 劳动合同(非必填,0-未签订 1-已签订) + private String laborContractFlag; + + // 紧急联系人(非必填) + private String contacts; + + // 紧急联系人电话(非必填) + private String contactsPhone; + + // 婚姻状况(非必填,0-3对应未婚至丧偶) + private String marryRemark; + + // 企业名称(非必填) + private String companyName; + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmAsyncMethod.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmAsyncMethod.java new file mode 100644 index 00000000..f9dbd779 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmAsyncMethod.java @@ -0,0 +1,123 @@ +package org.dromara.dataTransmission.clarityPm.method; + +import cn.hutool.core.date.LocalDateTimeUtil; +import jakarta.annotation.Resource; +import org.dromara.common.utils.IdCardEncryptorUtil; +import org.dromara.contractor.domain.SubConstructionUser; +import org.dromara.contractor.service.ISubConstructionUserFileService; +import org.dromara.contractor.service.ISubConstructionUserService; +import org.dromara.dataTransmission.clarityPm.dto.AttendanceRecord; +import org.dromara.dataTransmission.clarityPm.dto.RealUser; +import org.dromara.project.domain.BusAttendance; +import org.dromara.project.service.IBusProjectTeamMemberService; +import org.dromara.system.domain.vo.SysOssVo; +import org.dromara.system.service.ISysOssService; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +@Component +public class ClarityPmAsyncMethod { + + // 2. 注入Service(非静态字段) + @Resource + @Lazy + private ISubConstructionUserService constructionUserService; + + @Resource + @Lazy + private ISubConstructionUserFileService constructionUserFileService; + + @Resource + @Lazy + private IBusProjectTeamMemberService teamMemberService; + + @Resource + @Lazy + private IdCardEncryptorUtil idCardEncryptorUtil; + + @Resource + @Lazy + private ISysOssService ossService; + + + @Async + public void transmitUserData(Long constructionId) { + SubConstructionUser constructionUser = constructionUserService.getById(constructionId); + Map fileMap = constructionUserFileService.getFileByUserId(constructionUser.getSysUserId()); + String post = teamMemberService.getPost(constructionUser.getSysUserId(), constructionUser.getProjectId()); + // 安全教育-6 技术交底-5 合同-1 + + List userList = new ArrayList<>(); + RealUser realUser = new RealUser(); + realUser.setUserName(constructionUser.getUserName()); + realUser.setClassManagerFlag(post); + realUser.setPhone(constructionUser.getPhone()); + int sex = Integer.parseInt(constructionUser.getSex()); + realUser.setSex(String.valueOf(sex + 1)); + realUser.setCardType("0"); + realUser.setCardNumber(idCardEncryptorUtil.decrypt(constructionUser.getSfzNumber())); + realUser.setUserType("0"); + realUser.setNation(constructionUser.getNation()); + realUser.setBirthday(constructionUser.getSfzBirth()); + realUser.setAddress(constructionUser.getSfzSite()); + SysOssVo sysOssVo = ossService.getById(Long.valueOf(constructionUser.getFacePic())); + if (sysOssVo != null) { + realUser.setPic(sysOssVo.getUrl()); + } + realUser.setSafetyEdu(fileMap.get("6")); + realUser.setTechnology(fileMap.get("5")); + realUser.setCardStartTime(constructionUser.getSfzStart()); + realUser.setCardEndTime(constructionUser.getSfzEnd()); + realUser.setLaborContractFlag(fileMap.get("1") == null ? "0" : "1"); + + userList.add(realUser); + + try { + ClarityPmClient.batchInsertRealUser(userList); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Async + public void transmitAttendanceRecord(BusAttendance busAttendance) { + + ArrayList attendanceRecords = new ArrayList<>(); + + SubConstructionUser constructionUser = constructionUserService.getBySysUserId(busAttendance.getUserId()); + + AttendanceRecord attendanceRecord = new AttendanceRecord(); + attendanceRecord.setUserName(busAttendance.getUserName()); + attendanceRecord.setCardNumber(idCardEncryptorUtil.decrypt(constructionUser.getSfzNumber())); + attendanceRecord.setUserCompanyName(constructionUser.getCompanyName()); + //0-app,1-考勤机 + String source = busAttendance.getSource(); + + attendanceRecord.setDeviceType("1".equals(source)?"1":"2"); + attendanceRecord.setDeviceName(busAttendance.getSn()); + String format = LocalDateTimeUtil.format(busAttendance.getClockTime(), "yyyy-MM-dd HH:mm:ss"); + attendanceRecord.setClockInTime(format); + attendanceRecord.setClockInStatus("1"); + attendanceRecord.setLat(busAttendance.getLat()); + attendanceRecord.setLng(busAttendance.getLng()); + attendanceRecord.setClockInAddress(busAttendance.getClockLocation()); + + attendanceRecords.add(attendanceRecord); + + try { + ClarityPmClient.batchInsertAttendanceRecord(attendanceRecords); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmClient.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmClient.java new file mode 100644 index 00000000..fca6f6c8 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/dataTransmission/clarityPm/method/ClarityPmClient.java @@ -0,0 +1,132 @@ +package org.dromara.dataTransmission.clarityPm.method; + + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import org.dromara.dataTransmission.TokenUtils; +import org.dromara.dataTransmission.clarityPm.dto.AttendanceRecord; +import org.dromara.dataTransmission.clarityPm.dto.RealUser; +import org.springframework.stereotype.Component; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +@Component +public class ClarityPmClient { + + // 接口地址 + private static final String INSERT_REAL_USER_URL = "https://claritypm.powerchina.cn/neSmartsite-api/realUser/tiandong/insertRealUser"; + + private static final String INSERT_ATTENDANCE_RECORD_URL = "https://claritypm.powerchina.cn/neSmartsite-api/realUser/tiandong/insertAttendance"; + + + @Autowired + private TokenUtils tokenUtils; // 依赖之前的Token获取服务 + + /** + * 批量新增实名制用户信息 + * @param userList 用户信息列表(建议单次不超过10条) + * @throws Exception 调用异常 + */ + public static void batchInsertRealUser(List userList) throws Exception { + // 1. 校验列表大小(建议不超过10条) + if (userList == null || userList.isEmpty()) { + throw new IllegalArgumentException("用户列表不能为空"); + } + if (userList.size() > 10) { + throw new IllegalArgumentException("单次批量新增不能超过10条数据"); + } + + // 2. 获取Token(从之前的TokenService获取) + String token = TokenUtils.getToken(TokenUtils.CLARITYPM); + if ( token.trim().isEmpty()) { + throw new RuntimeException("获取Token失败,无法调用接口"); + } + + // 3. 构建请求头(包含Token认证) + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json; charset=UTF-8"); + headers.put("User-Agent", "Mozilla/5.0"); + headers.put("Accept", "application/json"); + headers.put("Origin", "https://claritypm.powerchina.cn"); + headers.put("Referer", "https://claritypm.powerchina.cn/"); + headers.put("Authorization", "Bearer " + token); // 假设接口使用Bearer Token认证 + + // 4. 构建请求体(JSONArray格式) + JSONArray requestBody = JSONArray.parseArray(JSON.toJSONString(userList)); + String jsonBody = requestBody.toJSONString(); + System.out.println("批量新增用户请求体:" + jsonBody); + + // 5. 发送POST请求 + String response = HttpUtil.createPost(INSERT_REAL_USER_URL) + .addHeaders(headers) + .body(jsonBody) + .execute() + .body(); + System.out.println("批量新增用户响应:" + response); + + // 6. 解析响应(根据实际响应结构调整,此处假设与登录接口类似) + JSONObject responseJson = JSONUtil.parseObj(response); + int code = responseJson.getInt("code"); + String msg = responseJson.getStr("msg"); + if (code != 200) { + throw new RuntimeException("批量新增用户失败:" + msg + "(状态码:" + code + ")"); + } + } + + /** + * 批量新增考勤信息 + * @param records 考勤信息列表(建议单次不超过10条) + * @throws Exception 调用异常 + */ + public static void batchInsertAttendanceRecord(List records) throws Exception { + // 1. 校验列表大小(建议不超过10条) + if (records == null || records.isEmpty()) { + throw new IllegalArgumentException("考勤列表不能为空"); + } + if (records.size() > 10) { + throw new IllegalArgumentException("单次批量新增不能超过10条数据"); + } + + // 2. 获取Token(从之前的TokenService获取) + String token = TokenUtils.getToken(TokenUtils.CLARITYPM); + if ( token.trim().isEmpty()) { + throw new RuntimeException("获取Token失败,无法调用接口"); + } + + // 3. 构建请求头(包含Token认证) + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json; charset=UTF-8"); + headers.put("User-Agent", "Mozilla/5.0"); + headers.put("Accept", "application/json"); + headers.put("Origin", "https://claritypm.powerchina.cn"); + headers.put("Referer", "https://claritypm.powerchina.cn/"); + headers.put("Authorization", "Bearer " + token); // 假设接口使用Bearer Token认证 + + // 4. 构建请求体(JSONArray格式) + JSONArray requestBody = JSONArray.parseArray(JSON.toJSONString(records)); + String jsonBody = requestBody.toJSONString(); + System.out.println("批量新增考勤请求体:" + jsonBody); + + // 5. 发送POST请求 + String response = HttpUtil.createPost(INSERT_ATTENDANCE_RECORD_URL) + .addHeaders(headers) + .body(jsonBody) + .execute() + .body(); + System.out.println("批量新增考勤响应:" + response); + + // 6. 解析响应(根据实际响应结构调整,此处假设与登录接口类似) + JSONObject responseJson = JSONUtil.parseObj(response); + int code = responseJson.getInt("code"); + String msg = responseJson.getStr("msg"); + if (code != 200) { + throw new RuntimeException("批量新增考勤失败:" + msg + "(状态码:" + code + ")"); + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java index 2510aba6..3eee704e 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java @@ -21,6 +21,7 @@ import org.dromara.common.utils.AsyncUtil; import org.dromara.common.utils.IdCardEncryptorUtil; import org.dromara.contractor.domain.SubConstructionUser; import org.dromara.contractor.service.ISubConstructionUserService; +import org.dromara.dataTransmission.clarityPm.method.ClarityPmAsyncMethod; import org.dromara.project.domain.BusConstructionUserExit; import org.dromara.project.domain.BusProjectTeam; import org.dromara.project.domain.BusProjectTeamMember; @@ -94,6 +95,8 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl