考勤机
This commit is contained in:
@ -386,5 +386,5 @@ warm-flow:
|
|||||||
--- # 百度云配置
|
--- # 百度云配置
|
||||||
baidu:
|
baidu:
|
||||||
client:
|
client:
|
||||||
id: zSB7KdLgY7a1tIEx3eTy65TE
|
id: pXACgshs1ABNFa6UM6HvkQ78 #zSB7KdLgY7a1tIEx3eTy65TE
|
||||||
secret: 5nabjclW5BWGV8UwEueDgBDmOveRVkmD
|
secret: EtTCBwd0KjGFOMDkbq84y3RxJxWpIVv4 #5nabjclW5BWGV8UwEueDgBDmOveRVkmD
|
||||||
|
|||||||
@ -36,7 +36,7 @@ public class SubConstructionUserAuthenticationReq implements Serializable {
|
|||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 0:保密 1:男 2女
|
* 0男1女2未知
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "性别不能为空")
|
@NotBlank(message = "性别不能为空")
|
||||||
private String sex;
|
private String sex;
|
||||||
|
|||||||
@ -0,0 +1,159 @@
|
|||||||
|
package org.dromara.dataTransmission;
|
||||||
|
|
||||||
|
import cn.hutool.crypto.asymmetric.KeyType;
|
||||||
|
import cn.hutool.crypto.asymmetric.RSA;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import org.dromara.common.redis.utils.RedisUtils;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
|
||||||
|
@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);
|
||||||
|
String 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);
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
requestBody.put(map.get(USER_NAME), username); // 从 map 中获取接口要求的用户名参数名
|
||||||
|
requestBody.put(map.get(PASSWORD), encryptedPassword); // 加密后的密码
|
||||||
|
String jsonBody = requestBody.toJSONString();
|
||||||
|
System.out.println("请求体:" + jsonBody);
|
||||||
|
|
||||||
|
// 3. 带 Header 发送 POST 请求(Hutool HttpUtil 重载方法)
|
||||||
|
String post = HttpUtil.createPost(authUrl)
|
||||||
|
// .addHeaders(headers) // 设置所有请求头
|
||||||
|
.body(jsonBody) // 设置请求体
|
||||||
|
.execute() // 执行请求
|
||||||
|
.body(); // 获取响应体
|
||||||
|
|
||||||
|
System.out.println("响应结果:" + post);
|
||||||
|
// 3. 解析响应(核心解析逻辑)
|
||||||
|
JSONObject responseJson = JSONObject.parseObject(post);
|
||||||
|
int code = responseJson.getIntValue("code");
|
||||||
|
String msg = responseJson.getString("msg");
|
||||||
|
String token = responseJson.getString("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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package org.dromara.dataTransmission.claritypm;
|
||||||
|
|
||||||
|
|
||||||
|
import org.dromara.dataTransmission.TokenUtils;
|
||||||
|
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 com.alibaba.fastjson.JSONObject;
|
||||||
|
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";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TokenUtils tokenUtils; // 依赖之前的Token获取服务
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增实名制用户信息
|
||||||
|
* @param userList 用户信息列表(建议单次不超过10条)
|
||||||
|
* @return 接口响应结果(JSON格式)
|
||||||
|
* @throws Exception 调用异常
|
||||||
|
*/
|
||||||
|
public static String 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 = JSONObject.parseObject(response);
|
||||||
|
int code = responseJson.getIntValue("code");
|
||||||
|
String msg = responseJson.getString("msg");
|
||||||
|
if (code != 200) {
|
||||||
|
throw new RuntimeException("批量新增用户失败:" + msg + "(状态码:" + code + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -210,8 +210,7 @@ public class AttendanceJob {
|
|||||||
.lt(BusAttendanceRule::getClockOutResultTime, end));
|
.lt(BusAttendanceRule::getClockOutResultTime, end));
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取当前日期
|
|
||||||
LocalDate date = LocalDate.now();
|
|
||||||
|
|
||||||
//管理员关联多个项目,需要记录是否已生成缺卡记录
|
//管理员关联多个项目,需要记录是否已生成缺卡记录
|
||||||
// HashSet<Long> manageUserIds = new HashSet<>();
|
// HashSet<Long> manageUserIds = new HashSet<>();
|
||||||
@ -220,6 +219,8 @@ public class AttendanceJob {
|
|||||||
List<BusAttendance> missList = new ArrayList<>();
|
List<BusAttendance> missList = new ArrayList<>();
|
||||||
|
|
||||||
for (BusAttendanceRule rule : list) {
|
for (BusAttendanceRule rule : list) {
|
||||||
|
//获取当前日期
|
||||||
|
LocalDate date = LocalDate.now();
|
||||||
|
|
||||||
LocalTime clockOutTime = rule.getClockOutTime();
|
LocalTime clockOutTime = rule.getClockOutTime();
|
||||||
LocalTime clockOutResultTime = rule.getClockOutResultTime();
|
LocalTime clockOutResultTime = rule.getClockOutResultTime();
|
||||||
|
|||||||
Reference in New Issue
Block a user