数据推送

This commit is contained in:
zt
2025-11-06 11:15:43 +08:00
parent 1d8d9c0236
commit 204177afa0
6 changed files with 571 additions and 0 deletions

View File

@ -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<String, Map<String, String>> 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<String, Map<String, String>> 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<String, String> 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<String, String> 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<String, String> map = paramMap.get(clientId);
HashMap<String, String> 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);
}
}

View File

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

View File

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

View File

@ -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<String, String> fileMap = constructionUserFileService.getFileByUserId(constructionUser.getSysUserId());
String post = teamMemberService.getPost(constructionUser.getSysUserId(), constructionUser.getProjectId());
// 安全教育-6 技术交底-5 合同-1
List<RealUser> 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<AttendanceRecord> 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);
}
}
}

View File

@ -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<RealUser> 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<String, String> 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<AttendanceRecord> 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<String, String> 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 + "");
}
}
}

View File

@ -21,6 +21,7 @@ import org.dromara.common.utils.AsyncUtil;
import org.dromara.common.utils.IdCardEncryptorUtil; import org.dromara.common.utils.IdCardEncryptorUtil;
import org.dromara.contractor.domain.SubConstructionUser; import org.dromara.contractor.domain.SubConstructionUser;
import org.dromara.contractor.service.ISubConstructionUserService; import org.dromara.contractor.service.ISubConstructionUserService;
import org.dromara.dataTransmission.clarityPm.method.ClarityPmAsyncMethod;
import org.dromara.project.domain.BusConstructionUserExit; import org.dromara.project.domain.BusConstructionUserExit;
import org.dromara.project.domain.BusProjectTeam; import org.dromara.project.domain.BusProjectTeam;
import org.dromara.project.domain.BusProjectTeamMember; import org.dromara.project.domain.BusProjectTeamMember;
@ -94,6 +95,8 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl<BusProjectTeamM
@Resource @Resource
private AsyncUtil asyncUtil; private AsyncUtil asyncUtil;
// @Resource
// private ClarityPmAsyncMethod clarityPmAsyncMethod;
/** /**