对接三方

main
zhangyue 2026-06-08 09:42:02 +08:00
parent b405119e0f
commit efbef9fe41
33 changed files with 937 additions and 53 deletions

View File

@ -53,13 +53,18 @@ public class StudentController {
return studentService.educationUserList(qry);
}
@ApiOperation("获取可推送三方人员")
@PostMapping("/pushCandidateUserList")
public PageResponse<EducationUserCO> pushCandidateUserList(@RequestBody EducationUserQry qry) {
return studentService.pushCandidateUserList(qry);
}
@ApiOperation("一人一档分页")
@PostMapping("/personnelFileList")
public PageResponse<StudentCO> personnelFileList(@RequestBody StudentPageQry qry) {
return studentService.personnelFileListPage(qry);
}
@ApiOperation("培训记录管理分页")
@PostMapping("/listPageClassByStudent")
public PageResponse<StudentCO> listPageClassByStudent(@RequestBody StudentPageQry qry) {
@ -89,6 +94,7 @@ public class StudentController {
public SingleResponse<StudentCO> getInfoByStudentId(@PathVariable("studentId") String studentId) {
return studentService.getInfoByStudentId(studentId);
}
@ApiOperation("班级内学员数")
@GetMapping("/countStudent/{classId}")
public SingleResponse<Long> countStudent(@PathVariable("classId") String classId) {
@ -122,4 +128,3 @@ public class StudentController {
return SingleResponse.buildSuccess();
}
}

View File

@ -0,0 +1,37 @@
package com.zcloud.edu.web.training;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.SingleResponse;
import com.zcloud.edu.api.training.TrainingThirdPartyPushServiceI;
import com.zcloud.edu.dto.clientobject.training.TrainingThirdPartyPushLogCO;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushCmd;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushLogPageQry;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "培训三方推送")
@RequestMapping("/${application.gateway}/trainingThirdPartyPush")
@RestController
@AllArgsConstructor
public class TrainingThirdPartyPushController {
private final TrainingThirdPartyPushServiceI trainingThirdPartyPushService;
@ApiOperation("推送三方培训数据")
@PostMapping("/push")
public SingleResponse<Integer> push(@Validated @RequestBody TrainingThirdPartyPushCmd cmd) {
return trainingThirdPartyPushService.push(cmd);
}
@ApiOperation("三方推送日志分页")
@PostMapping("/log/list")
public PageResponse<TrainingThirdPartyPushLogCO> logPage(@RequestBody TrainingThirdPartyPushLogPageQry qry) {
return trainingThirdPartyPushService.logPage(qry);
}
}

View File

@ -0,0 +1,13 @@
package com.zcloud.edu.command.convertor.training;
import com.zcloud.edu.dto.clientobject.training.TrainingThirdPartyPushLogCO;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushLogDO;
import org.mapstruct.Mapper;
import java.util.List;
@Mapper(componentModel = "spring")
public interface TrainingThirdPartyPushLogCoConvertor {
List<TrainingThirdPartyPushLogCO> converDOsToCOs(List<TrainingThirdPartyPushLogDO> trainingThirdPartyPushLogDOS);
}

View File

@ -256,5 +256,13 @@ public class StudentQueryExe {
PageResponse<EducationUserCO> pageResponse = studentRepository.educationUserList(params);
return pageResponse;
}
public PageResponse<EducationUserCO> pushCandidateUserList(EducationUserQry qry) {
Map<String, Object> params = PageQueryHelper.toHashMap(qry);
if (!Tools.isEmpty(params.get("likePhone"))) {
params.put("phoneArr", String.valueOf(params.get("likePhone")).split(","));
}
return studentRepository.pushCandidateUserList(params);
}
}

View File

@ -0,0 +1,29 @@
package com.zcloud.edu.command.query.training;
import com.alibaba.cola.dto.PageResponse;
import com.zcloud.edu.command.convertor.training.TrainingThirdPartyPushLogCoConvertor;
import com.zcloud.edu.dto.clientobject.training.TrainingThirdPartyPushLogCO;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushLogPageQry;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushLogDO;
import com.zcloud.edu.persistence.repository.training.TrainingThirdPartyPushLogRepository;
import com.zcloud.gbscommon.utils.PageQueryHelper;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
@AllArgsConstructor
public class TrainingThirdPartyPushLogQueryExe {
private final TrainingThirdPartyPushLogRepository trainingThirdPartyPushLogRepository;
private final TrainingThirdPartyPushLogCoConvertor trainingThirdPartyPushLogCoConvertor;
public PageResponse<TrainingThirdPartyPushLogCO> execute(TrainingThirdPartyPushLogPageQry qry) {
Map<String, Object> params = PageQueryHelper.toHashMap(qry);
PageResponse<TrainingThirdPartyPushLogDO> pageResponse = trainingThirdPartyPushLogRepository.listPage(params);
List<TrainingThirdPartyPushLogCO> data = trainingThirdPartyPushLogCoConvertor.converDOsToCOs(pageResponse.getData());
return PageResponse.of(data, pageResponse.getTotalCount(), pageResponse.getPageSize(), pageResponse.getPageIndex());
}
}

View File

@ -0,0 +1,25 @@
package com.zcloud.edu.command.training;
import com.alibaba.cola.exception.BizException;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@AllArgsConstructor
public class ThirdPartyPushFacade {
private final ThirdPartyPushSupport thirdPartyPushSupport;
public void push(List<TrainingThirdPartyPushSourceDO> sourceList) {
try {
thirdPartyPushSupport.push(sourceList);
} catch (BizException e) {
throw e;
} catch (Exception e) {
throw new BizException("推送三方失败:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,186 @@
package com.zcloud.edu.command.training;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.cola.exception.BizException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class ThirdPartyPushSupport {
private static final String SIGN_HEADER = "gjxy-sign";
private static final String NONCE_HEADER = "gjxy-nonce";
private static final String TIMESTAMP_HEADER = "gjxy-timestamp";
@Value("${gongJiangXueYuanUrl:}")
private String serverUrl;
@Value("${gongJiangXueYuanSendPublicKey:}")
private String rsaPublicKey;
@Value("${gongJiangXueYuanSendPrivateKey:}")
private String secretKey;
public void push(List<TrainingThirdPartyPushSourceDO> sourceList) throws Exception {
if (sourceList == null || sourceList.isEmpty()) {
return;
}
if (StrUtil.isBlank(serverUrl) || StrUtil.isBlank(rsaPublicKey) || StrUtil.isBlank(secretKey)) {
throw new BizException("三方推送配置不完整");
}
// 将新系统查询到的人员信息转换成三方要求的推送结构。
List<UserDTO> users = sourceList.stream().map(item -> {
UserDTO dto = new UserDTO();
dto.setUserName(item.getUsername());
dto.setRealName(item.getRealName());
dto.setUserSex(resolveUserSex(item.getIdCard()));
dto.setIdCard(item.getIdCard());
dto.setOrgTree(item.getOrgTree());
dto.setAvatarUrl(item.getAvatarUrl());
dto.setPhoneNumber(item.getPhoneNumber());
return dto;
}).collect(Collectors.toList());
Map<String, Object> paramDataMap = new HashMap<>();
paramDataMap.put("userList", users);
String body = createPostFormHttpRequest("/API-BACKEND/openapi/user/v1/syncNotify", paramDataMap);
Map<String, String> response = JSONObject.parseObject(body, new TypeReference<Map<String, String>>() {
});
if (!"1".equals(response.get("code"))) {
throw new BizException(response.get("message"));
}
Map<String, String> data = JSONObject.parseObject(response.get("data"), new TypeReference<Map<String, String>>() {
});
if (data != null && !String.valueOf(data.get("syncUserTotal")).equals(String.valueOf(data.get("syncSuccessCount")))) {
throw new BizException("三方返回的成功数量与推送数量不一致");
}
}
private String createPostFormHttpRequest(String uri, Map<String, Object> paramDataMap) throws Exception {
String paramDataJsonStr = JSON.toJSONString(paramDataMap);
String paramDataRsaEncrypt = encrypt(paramDataJsonStr, rsaPublicKey);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("param", paramDataRsaEncrypt);
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = RandomUtil.randomString(16);
String sign = signBySHA256(secretKey, nonce, timestamp, paramMap);
HttpResponse response = HttpUtil.createPost(serverUrl + uri)
.header(NONCE_HEADER, nonce)
.header(SIGN_HEADER, sign)
.header(TIMESTAMP_HEADER, timestamp)
.form("param", paramDataRsaEncrypt)
.execute();
return response.body();
}
/**
* SHA-256
*/
private String signBySHA256(String secretKey, String nonce, String timestamp, Map<String, Object> paramMap) {
try {
List<String> paramNames = new ArrayList<>(paramMap.keySet());
Collections.sort(paramNames);
StringBuilder result = new StringBuilder();
for (String paramName : paramNames) {
if (result.length() > 1) {
result.append("|");
}
String value = paramMap.get(paramName) == null ? "" : String.valueOf(paramMap.get(paramName));
if (value.length() > 4096) {
value = value.substring(0, 4096);
}
result.append(paramName).append("=").append(value);
}
result.append(":").append(nonce).append(":").append(secretKey).append("&").append(timestamp);
return sha256(result.toString());
} catch (Exception e) {
throw new BizException("三方签名失败:" + e.getMessage());
}
}
/**
* 使 RSA
*/
private String encrypt(String text, String publicKey) throws Exception {
String normalized = publicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
byte[] keyBytes = Base64.getDecoder().decode(normalized);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] data = text.getBytes(StandardCharsets.UTF_8);
int blockSize = 117;
int offset = 0;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while (data.length - offset > 0) {
int len = Math.min(data.length - offset, blockSize);
byte[] cache = cipher.doFinal(data, offset, len);
out.write(cache, 0, cache.length);
offset += len;
}
return Base64.getEncoder().encodeToString(out.toByteArray());
}
private String sha256(String text) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
private String resolveUserSex(String idCard) {
if (StrUtil.isBlank(idCard) || idCard.length() < 17) {
return null;
}
char genderCode = idCard.charAt(16);
if (!Character.isDigit(genderCode)) {
return null;
}
return ((genderCode - '0') % 2 == 0) ? "女" : "男";
}
@Data
public static class UserDTO {
private String userName;
private String realName;
private String userSex;
private String idCard;
private String orgTree;
private String avatarUrl;
private String phoneNumber;
}
}

View File

@ -0,0 +1,147 @@
package com.zcloud.edu.command.training;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cola.exception.BizException;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushCmd;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushLogDO;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO;
import com.zcloud.edu.persistence.repository.training.TrainingThirdPartyPushLogRepository;
import com.zcloud.edu.persistence.repository.training.TrainingUserRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Component
@AllArgsConstructor
public class TrainingThirdPartyPushExe {
private final TrainingUserRepository trainingUserRepository;
private final TrainingThirdPartyPushLogRepository trainingThirdPartyPushLogRepository;
private final ThirdPartyPushFacade thirdPartyPushFacade;
@Transactional(rollbackFor = Exception.class)
public Integer execute(TrainingThirdPartyPushCmd cmd) {
List<String> usernameList = normalizeUsernames(cmd.getUsernameList());
if (CollUtil.isEmpty(usernameList)) {
throw new BizException("用户名列表不能为空");
}
Map<String, Object> params = new HashMap<>();
params.put("usernameList", usernameList);
List<TrainingThirdPartyPushSourceDO> sourceList = trainingUserRepository.listThirdPartyPushUsers(params);
if (CollUtil.isEmpty(sourceList)) {
throw new BizException("未查询到可推送的用户信息");
}
List<TrainingThirdPartyPushSourceDO> validSourceList = sourceList.stream()
.filter(item -> StrUtil.isNotBlank(item.getUsername()))
.collect(Collectors.toList());
if (CollUtil.isEmpty(validSourceList)) {
throw new BizException("未查询到有效的推送用户信息");
}
// 先调用三方接口,确保三方接收成功后再落日志和回写标记。
thirdPartyPushFacade.push(validSourceList);
// 记录本次成功推送的人员快照,便于后续分页查询和追溯。
List<TrainingThirdPartyPushLogDO> logDOList = buildLogList(validSourceList);
trainingThirdPartyPushLogRepository.saveBatch(logDOList);
// 推送成功后,将 training_user 中对应手机号标记为第三方培训人员。
List<String> phones = validSourceList.stream()
.map(TrainingThirdPartyPushSourceDO::getPhoneNumber)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(phones)) {
trainingUserRepository.updateThirdPartyFlagByPhones(phones);
}
return validSourceList.size();
}
private List<String> normalizeUsernames(List<String> usernameList) {
if (CollUtil.isEmpty(usernameList)) {
return new ArrayList<>();
}
Set<String> unique = new LinkedHashSet<>();
for (String username : usernameList) {
if (StrUtil.isNotBlank(username)) {
unique.add(username.trim());
}
}
return new ArrayList<>(unique);
}
private List<TrainingThirdPartyPushLogDO> buildLogList(List<TrainingThirdPartyPushSourceDO> sourceList) {
SSOUser currentUser = AuthContext.getCurrentUser();
LocalDateTime now = LocalDateTime.now();
return sourceList.stream().map(item -> {
TrainingThirdPartyPushLogDO logDO = new TrainingThirdPartyPushLogDO();
logDO.setTrainingThirdPartyPushLogId(IdUtil.simpleUUID());
logDO.setUsername(item.getUsername());
logDO.setRealName(item.getRealName());
logDO.setUserSex(resolveUserSex(item.getIdCard()));
logDO.setIdCard(item.getIdCard());
logDO.setOrgTree(item.getOrgTree());
logDO.setAvatarUrl(item.getAvatarUrl());
logDO.setPhoneNumber(item.getPhoneNumber());
logDO.setRelatedCorpNames(item.getRelatedCorpNames());
logDO.setDeleteEnum("FALSE");
logDO.setCreateTime(now);
logDO.setUpdateTime(now);
if (currentUser != null) {
logDO.setCreateId(currentUser.getUserId());
logDO.setUpdateId(currentUser.getUserId());
logDO.setCreateName(resolveCurrentUserName(currentUser));
logDO.setUpdateName(resolveCurrentUserName(currentUser));
logDO.setTenantId(currentUser.getTenantId());
logDO.setOrgId(currentUser.getOrgId());
}
return logDO;
}).collect(Collectors.toList());
}
private String resolveCurrentUserName(SSOUser currentUser) {
String[] candidates = new String[]{"getUsername", "getUserName", "getName", "getRealName"};
for (String methodName : candidates) {
try {
Method method = currentUser.getClass().getMethod(methodName);
Object value = method.invoke(currentUser);
if (value != null && StrUtil.isNotBlank(String.valueOf(value))) {
return String.valueOf(value);
}
} catch (Exception ignored) {
}
}
return String.valueOf(currentUser.getUserId());
}
/**
* 17
*
*/
private String resolveUserSex(String idCard) {
if (StrUtil.isBlank(idCard) || idCard.length() < 17) {
return null;
}
char genderCode = idCard.charAt(16);
if (!Character.isDigit(genderCode)) {
return null;
}
return ((genderCode - '0') % 2 == 0) ? "女" : "男";
}
}

View File

@ -0,0 +1,30 @@
package com.zcloud.edu.service;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.SingleResponse;
import com.zcloud.edu.api.training.TrainingThirdPartyPushServiceI;
import com.zcloud.edu.command.query.training.TrainingThirdPartyPushLogQueryExe;
import com.zcloud.edu.command.training.TrainingThirdPartyPushExe;
import com.zcloud.edu.dto.clientobject.training.TrainingThirdPartyPushLogCO;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushCmd;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushLogPageQry;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class TrainingThirdPartyPushServiceImpl implements TrainingThirdPartyPushServiceI {
private final TrainingThirdPartyPushExe trainingThirdPartyPushExe;
private final TrainingThirdPartyPushLogQueryExe trainingThirdPartyPushLogQueryExe;
@Override
public SingleResponse<Integer> push(TrainingThirdPartyPushCmd cmd) {
return SingleResponse.of(trainingThirdPartyPushExe.execute(cmd));
}
@Override
public PageResponse<TrainingThirdPartyPushLogCO> logPage(TrainingThirdPartyPushLogPageQry qry) {
return trainingThirdPartyPushLogQueryExe.execute(qry);
}
}

View File

@ -121,5 +121,10 @@ public class StudentServiceImpl implements StudentServiceI {
public PageResponse<EducationUserCO> educationUserList(EducationUserQry qry) {
return studentQueryExe.educationUserList(qry);
}
@Override
public PageResponse<EducationUserCO> pushCandidateUserList(EducationUserQry qry) {
return studentQueryExe.pushCandidateUserList(qry);
}
}

View File

@ -0,0 +1,34 @@
//package com.zcloud.edu.utils;
//
//
//import lombok.extern.slf4j.Slf4j;
//import net.coobird.thumbnailator.Thumbnails;
//import org.apache.commons.io.IOUtils;
//
//import java.io.ByteArrayInputStream;
//import java.io.ByteArrayOutputStream;
//import java.io.InputStream;
//import java.net.URL;
//
///**
// * @author zhangyue
// * @date 2026/3/11 10:23
// */
//public class ImageOrientationUtil {
// public static byte[] fixImageOrientation(String imageUrl) {
// try (InputStream is = new URL(imageUrl).openStream()) {
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
//
// // Thumbnailator 会自动读取 EXIF 方向信息并正确旋转图片
// Thumbnails.of(is)
// .keepAspectRatio(true)
// .outputFormat("JPEG")
// .toOutputStream(baos);
//
// return baos.toByteArray();
// } catch (Exception e) {
// e.printStackTrace();
// throw new RuntimeException(e);
// }
// }
//}

View File

@ -52,5 +52,7 @@ public interface StudentServiceI {
SingleResponse<StudentCO> getInfoByStudentId(String studentId);
PageResponse<EducationUserCO> educationUserList(EducationUserQry qry);
PageResponse<EducationUserCO> pushCandidateUserList(EducationUserQry qry);
}

View File

@ -0,0 +1,14 @@
package com.zcloud.edu.api.training;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.dto.SingleResponse;
import com.zcloud.edu.dto.clientobject.training.TrainingThirdPartyPushLogCO;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushCmd;
import com.zcloud.edu.dto.training.TrainingThirdPartyPushLogPageQry;
public interface TrainingThirdPartyPushServiceI {
SingleResponse<Integer> push(TrainingThirdPartyPushCmd cmd);
PageResponse<TrainingThirdPartyPushLogCO> logPage(TrainingThirdPartyPushLogPageQry qry);
}

View File

@ -15,7 +15,8 @@ public class FileUrlConfig {
prefixUrl = prefixUrlProperties;
}
public String getPrefixUrl() {
// return "http://192.168.192.201:8991/file/uploadFiles2/";
return prefixUrl;
// 正式环境
return "http://192.168.192.201:8991/file/uploadFiles2/";
// return prefixUrl;
}
}

View File

@ -0,0 +1,46 @@
package com.zcloud.edu.dto.clientobject.training;
import com.alibaba.cola.dto.ClientObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class TrainingThirdPartyPushLogCO extends ClientObject {
@ApiModelProperty(value = "业务主键id")
private String trainingThirdPartyPushLogId;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "姓名")
private String realName;
@ApiModelProperty(value = "性别")
private String userSex;
@ApiModelProperty(value = "身份证号")
private String idCard;
@ApiModelProperty(value = "组织树(所属企业/部门)")
private String orgTree;
@ApiModelProperty(value = "免冠照片url地址")
private String avatarUrl;
@ApiModelProperty(value = "手机号")
private String phoneNumber;
@ApiModelProperty(value = "所属相关方")
private String relatedCorpNames;
@ApiModelProperty(value = "推送时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@ApiModelProperty(value = "推送人")
private String createName;
}

View File

@ -0,0 +1,16 @@
package com.zcloud.edu.dto.training;
import com.alibaba.cola.dto.Command;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
public class TrainingThirdPartyPushCmd extends Command {
@ApiModelProperty(value = "用户名列表", required = true)
@NotEmpty(message = "用户名列表不能为空")
private List<String> usernameList;
}

View File

@ -0,0 +1,21 @@
package com.zcloud.edu.dto.training;
import com.alibaba.cola.dto.PageQuery;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class TrainingThirdPartyPushLogPageQry extends PageQuery {
@ApiModelProperty(value = "用户名模糊查询")
private String likeUsername;
@ApiModelProperty(value = "姓名模糊查询")
private String likeRealName;
@ApiModelProperty(value = "所属相关方模糊查询")
private String likeRelatedCorpNames;
@ApiModelProperty(value = "推送人模糊查询")
private String likeCreateName;
}

View File

@ -78,13 +78,13 @@ public class StudentSignE extends BaseE {
map.put("pic2", faceUrl);
String jsonParam = JSON.toJSONString(map);
// 正式环境
// String compareResult = HttpRequestUtil.doPost("http://192.168.192.201:8971/gbs-face/face/compareFace", jsonParam);
// JSONObject jsonObject = JSONObject.parseObject(compareResult);
// jsonObject.getJSONObject("info").getString("confidence");
// String compareResultStr = jsonObject.getJSONObject("info").getString("confidence");
String compareResult = HttpRequestUtil.doPost("http://192.168.192.201:8971/gbs-face/face/compareFace", jsonParam);
JSONObject jsonObject = JSONObject.parseObject(compareResult);
jsonObject.getJSONObject("info").getString("confidence");
String compareResultStr = jsonObject.getJSONObject("info").getString("confidence");
// 测试环境
String compareResultStr = FaceUtil.compareFace(templateFace, faceUrl);
// 测试环境
// String compareResultStr = FaceUtil.compareFace(templateFace, faceUrl);
if (Double.valueOf(compareResultStr) < 75) {
throw new BizException("人脸不匹配");
}

View File

@ -345,4 +345,4 @@ public class FixedPictureUtils {
.create();
}
}
}
}

View File

@ -0,0 +1,46 @@
package com.zcloud.edu.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@TableName("training_third_party_push_log")
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TrainingThirdPartyPushLogDO extends BaseDO {
@ApiModelProperty(value = "业务主键id")
private String trainingThirdPartyPushLogId;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "姓名")
private String realName;
@ApiModelProperty(value = "性别")
private String userSex;
@ApiModelProperty(value = "身份证号")
private String idCard;
@ApiModelProperty(value = "组织树(所属企业/部门)")
private String orgTree;
@ApiModelProperty(value = "免冠照片url地址")
private String avatarUrl;
@ApiModelProperty(value = "手机号")
private String phoneNumber;
@ApiModelProperty(value = "所属相关方")
private String relatedCorpNames;
public TrainingThirdPartyPushLogDO(String trainingThirdPartyPushLogId) {
this.trainingThirdPartyPushLogId = trainingThirdPartyPushLogId;
}
}

View File

@ -0,0 +1,32 @@
package com.zcloud.edu.persistence.dataobject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class TrainingThirdPartyPushSourceDO {
@ApiModelProperty(value = "用户id")
private Long userId;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "姓名")
private String realName;
@ApiModelProperty(value = "身份证号")
private String idCard;
@ApiModelProperty(value = "组织树")
private String orgTree;
@ApiModelProperty(value = "头像地址")
private String avatarUrl;
@ApiModelProperty(value = "手机号")
private String phoneNumber;
@ApiModelProperty(value = "所属相关方")
private String relatedCorpNames;
}

View File

@ -1,6 +1,7 @@
package com.zcloud.edu.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jjb.saas.framework.repository.basedo.BaseDO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -9,70 +10,58 @@ import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* web-infrastructure
* @Author makejava
* @Date 2026-01-12 15:41:03
*/
@Data
@TableName("training_user")
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TrainingUserDO extends BaseDO {
//业务主键id
@ApiModelProperty(value = "业务主键id")
private String trainingUserId;
//手机号
@ApiModelProperty(value = "手机号")
private String phone;
//申请状态1:待审批2:审批通过,3:审批不通过
@ApiModelProperty(value = "申请状态1:待审批2:审批通过,3:审批不通过")
@ApiModelProperty(value = "申请状态")
private Long applyStatus;
//有效期开始时间
@ApiModelProperty(value = "有效期开始时间")
private LocalDateTime startTime;
//有效期结束时间
@ApiModelProperty(value = "有效期结束时间")
private LocalDateTime endTime;
//考试状态:1.待考试2:考试通过,3:考试不通过
@ApiModelProperty(value = "考试状态:1.待考试2:考试通过,3:考试不通过")
@ApiModelProperty(value = "考试状态")
private Long examineStatus;
@ApiModelProperty(value = "当前培训证书有效状态 0-无效 1-有效")
@ApiModelProperty(value = "是否由第三方培训 0-否 1-是")
private Integer isThirdParty;
@ApiModelProperty(value = "当前培训证书有效状态 0-无效 1-有效")
@TableField(exist = false)
private Integer stateFlag;
//姓名
@ApiModelProperty(value = "姓名")
@TableField(exist = false)
private String name;
//所属部门
@ApiModelProperty(value = "所属部门")
@TableField(exist = false)
private String departmentName;
//所属部门id
@ApiModelProperty(value = "所属部门id")
@TableField(exist = false)
private Long departmentId;
//所属岗位
@ApiModelProperty(value = "所属岗位")
@TableField(exist = false)
private String postName;
//所属岗位id
@ApiModelProperty(value = "所属岗位id")
@TableField(exist = false)
private Long postId;
public TrainingUserDO(String trainingUserId) {
this.trainingUserId = trainingUserId;
}
}

View File

@ -0,0 +1,9 @@
package com.zcloud.edu.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TrainingThirdPartyPushLogMapper extends BaseMapper<TrainingThirdPartyPushLogDO> {
}

View File

@ -1,6 +1,7 @@
package com.zcloud.edu.persistence.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO;
import com.zcloud.edu.persistence.dataobject.TrainingUserDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@ -23,5 +24,7 @@ public interface TrainingUserMapper extends BaseMapper<TrainingUserDO> {
IPage<TrainingUserDO> listEduUserPage(IPage<TrainingUserDO> page, @Param("params") Map<String, Object> params);
List<TrainingUserDO> listEduUser(@Param("params") Map<String, Object> params);
List<TrainingThirdPartyPushSourceDO> listThirdPartyPushUsers(@Param("params") Map<String, Object> params);
}

View File

@ -53,5 +53,7 @@ public interface StudentMapper extends BaseMapper<StudentDO> {
IPage<EducationUserCO> educationUserList(Page<Map<String, Object>> page, Map<String, Object> params, String menuPerms);
IPage<EducationUserCO> pushCandidateUserList(Page<Map<String, Object>> page, Map<String, Object> params, String menuPerms);
}

View File

@ -0,0 +1,35 @@
package com.zcloud.edu.persistence.repository.impl;
import com.alibaba.cola.dto.PageResponse;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jjb.saas.framework.repository.common.PageHelper;
import com.jjb.saas.framework.repository.repo.impl.BaseRepositoryImpl;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushLogDO;
import com.zcloud.edu.persistence.mapper.TrainingThirdPartyPushLogMapper;
import com.zcloud.edu.persistence.repository.training.TrainingThirdPartyPushLogRepository;
import com.zcloud.gbscommon.utils.PageQueryHelper;
import com.zcloud.gbscommon.utils.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class TrainingThirdPartyPushLogRepositoryImpl extends BaseRepositoryImpl<TrainingThirdPartyPushLogMapper, TrainingThirdPartyPushLogDO>
implements TrainingThirdPartyPushLogRepository {
private final TrainingThirdPartyPushLogMapper trainingThirdPartyPushLogMapper;
@Override
public PageResponse<TrainingThirdPartyPushLogDO> listPage(Map<String, Object> params) {
IPage<TrainingThirdPartyPushLogDO> iPage = new Query<TrainingThirdPartyPushLogDO>().getPage(params);
QueryWrapper<TrainingThirdPartyPushLogDO> queryWrapper = new QueryWrapper<>();
queryWrapper = PageQueryHelper.createPageQueryWrapper(queryWrapper, params);
queryWrapper.eq("delete_enum", "FALSE");
queryWrapper.orderByDesc("create_time");
IPage<TrainingThirdPartyPushLogDO> result = trainingThirdPartyPushLogMapper.selectPage(iPage, queryWrapper);
return PageHelper.pageToResponse(result, result.getRecords());
}
}

View File

@ -3,6 +3,7 @@ package com.zcloud.edu.persistence.repository.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.jjb.saas.framework.repository.common.PageHelper;
import com.zcloud.edu.domain.model.training.TrainingUserE;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO;
import com.zcloud.edu.persistence.dataobject.TrainingUserDO;
import com.zcloud.edu.persistence.mapper.TrainingUserMapper;
import com.zcloud.edu.persistence.repository.training.TrainingUserRepository;
@ -60,5 +61,21 @@ public class TrainingUserRepositoryImpl extends BaseRepositoryImpl<TrainingUserM
public List<TrainingUserDO> listAllByPhones(Map<String, Object> params) {
return trainingUserMapper.listAllByPhones(params);
}
@Override
public List<TrainingThirdPartyPushSourceDO> listThirdPartyPushUsers(Map<String, Object> params) {
return trainingUserMapper.listThirdPartyPushUsers(params);
}
@Override
public void updateThirdPartyFlagByPhones(List<String> phones) {
if (phones == null || phones.isEmpty()) {
return;
}
UpdateWrapper<TrainingUserDO> updateWrapper = new UpdateWrapper<>();
updateWrapper.in("phone", phones)
.set("is_third_party", 1);
trainingUserMapper.update(null, updateWrapper);
}
}

View File

@ -149,5 +149,16 @@ public class StudentRepositoryImpl extends BaseRepositoryImpl<StudentMapper, Stu
return PageHelper.pageToResponse(iPage, iPage.getRecords());
}
@Override
public PageResponse<EducationUserCO> pushCandidateUserList(Map<String, Object> params) {
Page<Map<String, Object>> page = new Page<>(Integer.parseInt(params.get("pageIndex").toString()), Integer.parseInt(params.get("pageSize").toString()));
String menuPerms = "";
if (!org.springframework.util.ObjectUtils.isEmpty(params.get("menuPath"))) {
menuPerms = MenuEnum.getMenuKeyByPath(params.get("menuPath").toString());
}
IPage<EducationUserCO> iPage = studentMapper.pushCandidateUserList(page, params, menuPerms);
return PageHelper.pageToResponse(iPage, iPage.getRecords());
}
}

View File

@ -49,5 +49,7 @@ public interface StudentRepository extends BaseRepository<StudentDO> {
List<StudentDO> listStudentCount(Map<String, Object> params);
PageResponse<EducationUserCO> educationUserList(Map<String, Object> params);
PageResponse<EducationUserCO> pushCandidateUserList(Map<String, Object> params);
}

View File

@ -0,0 +1,12 @@
package com.zcloud.edu.persistence.repository.training;
import com.alibaba.cola.dto.PageResponse;
import com.jjb.saas.framework.repository.repo.BaseRepository;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushLogDO;
import java.util.Map;
public interface TrainingThirdPartyPushLogRepository extends BaseRepository<TrainingThirdPartyPushLogDO> {
PageResponse<TrainingThirdPartyPushLogDO> listPage(Map<String, Object> params);
}

View File

@ -1,6 +1,7 @@
package com.zcloud.edu.persistence.repository.training;
import com.zcloud.edu.domain.model.training.TrainingUserE;
import com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO;
import com.zcloud.edu.persistence.dataobject.TrainingUserDO;
import com.alibaba.cola.dto.PageResponse;
import com.jjb.saas.framework.repository.repo.BaseRepository;
@ -24,5 +25,9 @@ public interface TrainingUserRepository extends BaseRepository<TrainingUserDO> {
List<TrainingUserDO> listAllByPhones(Map<String, Object> params);
List<TrainingThirdPartyPushSourceDO> listThirdPartyPushUsers(Map<String, Object> params);
void updateThirdPartyFlagByPhones(List<String> phones);
}

View File

@ -10,22 +10,18 @@
end_time = #{params.endTime}
</set>
<where>
phone = #{params.phone}
and ( end_time is null or end_time &lt; #{params.endTime})
phone = #{params.phone}
and (end_time is null or end_time &lt; #{params.endTime})
</where>
</update>
<select id="listAllByPhones" resultType="com.zcloud.edu.persistence.dataobject.TrainingUserDO">
select
phone,
start_time,
end_time,
case when start_time &lt; now() and end_time > now() then 1 else 0 end stateFlag
from
training_user
from training_user
<where>
delete_enum = 'false'
<if test="params.phones != null">
@ -88,10 +84,9 @@
u.org_id,
u.env,
u.open_id,
tu.phone
-- case when tu.start_time &lt; now() and tu.end_time > now() then 1 else 0 end stateFlag
from
user u
tu.phone,
tu.is_third_party
from user u
left join training_user tu on tu.phone = u.username and tu.delete_enum = 'FALSE'
left join department d on d.id = u.department_id
<where>
@ -175,10 +170,9 @@
u.org_id,
u.env,
u.open_id,
tu.phone
-- case when tu.start_time &lt; now() and tu.end_time > now() then 1 else 0 end stateFlag
from
user u
tu.phone,
tu.is_third_party
from user u
left join training_user tu on tu.phone = u.username and tu.delete_enum = 'FALSE'
left join department d on d.id = u.department_id
<where>
@ -189,7 +183,7 @@
and u.id != u.corpinfo_id
</if>
<if test="params.name != null and params.name != ''">
AND u.name LIKE CONCAT('%', #{params.name}, '%')
and u.name LIKE CONCAT('%', #{params.name}, '%')
</if>
<if test="params.corpinfoId != null">
and u.corpinfo_id = #{params.corpinfoId}
@ -211,6 +205,35 @@
order by u.create_time desc
</select>
<select id="listThirdPartyPushUsers" resultType="com.zcloud.edu.persistence.dataobject.TrainingThirdPartyPushSourceDO">
select
u.id as user_id,
u.username as username,
u.name as real_name,
u.user_id_card as id_card,
concat_ws('/', c.corp_name, d.name) as org_tree,
u.user_avatar_url as avatar_url,
case
when u.phone is not null and u.phone != '' then u.phone
else u.username
end as phone_number,
group_concat(distinct rc.corp_name separator ', ') as related_corp_names
from user u
left join corp_info c on c.id = u.corpinfo_id and c.delete_enum = 'FALSE'
left join department d on d.id = u.department_id and d.delete_enum = 'FALSE'
left join project_user pu on pu.user_id = u.id and pu.delete_enum = 'FALSE'
left join project p on p.id = pu.project_id and p.delete_enum = 'FALSE'
left join corp_info rc on rc.id = p.corpinfo_id and rc.delete_enum = 'FALSE'
<where>
u.delete_enum = 'FALSE'
<if test="params.usernameList != null and params.usernameList.size() > 0">
and u.username in
<foreach collection="params.usernameList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
</where>
group by u.id, u.username, u.name, u.user_id_card, c.corp_name, d.name, u.user_avatar_url, u.phone
</select>
</mapper>

View File

@ -484,5 +484,84 @@
group by tau.phone
</select>
<select id="pushCandidateUserList" resultType="com.zcloud.edu.dto.clientobject.study.EducationUserCO">
SELECT
GROUP_CONCAT(DISTINCT c.id SEPARATOR ', ') AS corpinfo_id,
GROUP_CONCAT(DISTINCT c.corp_name SEPARATOR ', ') AS corpinfo_name,
GROUP_CONCAT(DISTINCT p.project_id SEPARATOR ', ') AS project_id,
GROUP_CONCAT(DISTINCT p.project_name SEPARATOR ', ') AS project_name,
u.name,
po.post_name,
d.name dept_name,
u.phone,
u.user_id_card,
tu.apply_status,
tu.start_time,
tu.end_time,
tu.examine_status
FROM training_apply_user tau
left join training_apply_record tar on tau.training_apply_record_id = tar.training_apply_record_id
left join (
SELECT *, ROW_NUMBER() OVER (PARTITION BY training_apply_record_id ORDER BY apply_type DESC) as rn
FROM training_apply_process
WHERE delete_enum = 'FALSE' AND apply_type IN (2, 3)
) tap ON tap.training_apply_record_id = tau.training_apply_record_id AND tap.rn = 1
left join user u on tau.user_id = u.id
left join corp_info c on c.id = u.corpinfo_id
left join project_user f on u.id = f.user_id and f.delete_enum = 'FALSE'
left join project p on p.id = f.project_id and p.project_status = 4
left join department d on d.id = u.department_id
left join post po on u.post_id = po.id
left join training_user tu on tu.phone = u.phone
LEFT JOIN project_approval_flow paf on paf.project_id = p.id and paf.delete_enum = 'FALSE' and paf.audit_type = 2 and paf.approval_status = 2
and paf.corpinfo_id = #{params.eqProjectCorpInfoId}
where
tap.corpinfo_id = #{params.eqProjectCorpInfoId}
and tau.delete_enum = 'FALSE'
and tar.delete_enum = 'FALSE'
and tau.apply_status = 2
and ifnull(tu.is_third_party, 0) != 1
and not exists (
select 1
from student s2
left join class c2 on c2.class_id = s2.class_id
where s2.delete_enum = 'FALSE'
and c2.delete_enum = 'FALSE'
and c2.state != 1
and s2.phone = u.phone
and s2.state = 0
and c2.start_time &lt;= now()
and c2.end_time &gt;= now()
)
<if test="params.endTimeStatus != null and params.endTimeStatus == 1">
and tu.end_time is not null
</if>
<if test="params.endTimeStatus != null and params.endTimeStatus == -1">
and tu.end_time is null
</if>
<if test="params.eqApplicationProjectCorpInfoId != null and params.eqApplicationProjectCorpInfoId != ''">
and p.corpinfo_id = #{params.eqApplicationProjectCorpInfoId}
</if>
<if test="params.eqApplyStatus != null and params.eqApplyStatus != ''">
and tu.apply_status = #{params.eqApplyStatus}
</if>
<if test="params.likeName != null and params.likeName != ''">
and u.name like concat('%',#{params.likeName},'%')
</if>
<if test="params.likeProjectName != null and params.likeProjectName != ''">
and p.project_name like concat('%',#{params.likeProjectName},'%')
</if>
<if test="params.likePhone != null and params.likePhone != ''">
and u.phone like concat('%',#{params.likePhone},'%')
</if>
group by tau.phone
</select>
</mapper>