获取手机号

This commit is contained in:
2026-09-14 20:21:23 +08:00
parent 1faebf3bc2
commit 4373cc0095
10 changed files with 333 additions and 133 deletions
@@ -230,7 +230,7 @@ public class FlowTodoCenterController {
dao.execute(todoSql);
int todoCount = todoSql.getInt();
// 查询我的已办任务
// 已办统计排除发起节点 startTask,与已办列表保持一致,避免将本人发起流程计入已办数量。
Sql doneSql = Sqls.create("""
SELECT
count(1)
@@ -240,6 +240,7 @@ public class FlowTodoCenterController {
WHERE
ta.actorId = @userId
AND t.taskState = 20
AND t.taskName != 'startTask'
""");
doneSql.setParam("userId", SecurityUtil.getUserId());
doneSql.setCallback(Sqls.callback.integer());
@@ -69,17 +69,6 @@ public class SysDataUserPullController {
return Result.success();
}
@At
@SaCheckPermission("sys.data.user.pull")
@ApiOperation("同步教职工手机号")
public Result syncTeacherMobile() {
try {
return Result.success(sysUserPullService.syncTeacherMobile());
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@SaCheckPermission("sys.data.user.pull")
@ApiOperation("删除用户数据")
@@ -17,6 +17,8 @@ import com.budwk.app.sys.services.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
@@ -47,6 +49,8 @@ public class SysDataUserUpdateController {
@Inject
private SysUserService sysUserService;
@Inject
private SysUserMobileSyncService sysUserMobileSyncService;
@Inject
private SysDataUserPullService sysDataUserPullService;
@Inject
private SysDataUnitPullService sysDataUnitPullService;
@@ -62,7 +66,8 @@ public class SysDataUserUpdateController {
@At
@SaCheckPermission("sys.data.user.pull")
@ApiOperation("分页数据")
@Ok("json:{locked:'password|idCard|mobile'}")
// 手机号用于列表核对拉取结果;密码和身份证号仍不返回。
@Ok("json:{locked:'password|idCard'}")
public Result pageData(@Valid SysDataUserUpdatePageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
@@ -136,4 +141,46 @@ public class SysDataUserUpdateController {
sysDataDictPullService.pullDataDict();
return Result.success();
}
/**
* 预览:scope传ALL/MEMBER/FILTERmode传FILL/REFRESHpageForm传列表筛选条件。
* 返回NutMapscopeCount/existingCount/cooldownCount/total分别为范围、已有号、冷却及待请求工号数。
*/
@At
@SaCheckPermission("sys.data.user.update")
public Result mobilePreview(String scope, String mode, SysDataUserUpdatePageForm pageForm) {
return Result.success(sysUserMobileSyncService.preview(scope, mode, pageForm));
}
/**
* 启动:scope/mode及pageForm同预览;action传NEW/RESUME/RETRYsourceTaskId仅继续或重试时传原任务编号。
* 返回NutMap进度:taskId任务编号、status状态及total/processed/updated/empty/failed/skipped各项人数。
*/
@At
@SaCheckPermission("sys.data.user.update")
@Aop(TransAop.READ_COMMITTED)
@SLog(tag = "手机号拉取", msg = "启动手机号后台拉取任务")
public Result mobileStart(String scope, String mode, SysDataUserUpdatePageForm pageForm, String sourceTaskId, String action) {
return Result.success(sysUserMobileSyncService.start(scope, mode, pageForm, sourceTaskId, action));
}
/**
* 进度:taskId可为空(最近任务)或传启动接口返回的编号;返回与启动一致的NutMap,不返回手机号与工号队列。
*/
@At
@SaCheckPermission("sys.data.user.update")
public Result mobileStatus(String taskId) {
return Result.success(sysUserMobileSyncService.status(taskId));
}
/**
* 停止:taskId必须传当前任务编号;返回NutMap最新进度,当前请求结束后停止且保留已完成结果。
*/
@At
@SaCheckPermission("sys.data.user.update")
@Aop(TransAop.READ_COMMITTED)
@SLog(tag = "手机号拉取", msg = "停止手机号后台拉取任务")
public Result mobileStop(String taskId) {
return Result.success(sysUserMobileSyncService.stop(taskId));
}
}
@@ -53,4 +53,20 @@ public interface SysDataUserPullService extends BaseService<Sys_user_source> {
* @return 同步过程统计信息,包含工号手机号映射
*/
NutMap syncTeacherMobileByJobNos(List<String> jobNos);
/**
* 按单个工号读取数据中心手机号,不修改数据库;空字符串表示接口明确没有号码。
* @param jobNo 非空教职工工号
* @return 已校验的手机号;接口异常、工号不匹配或号码格式异常时抛出异常
*/
String fetchTeacherMobile(String jobNo);
/**
* 保存单人工号的有效号码,使用请求前快照避免覆盖期间的人工修改。
* @param jobNo 数据中心查询工号
* @param mobile 已校验的非空手机号
* @param originalMobiles 请求前的用户ID到原手机号映射
* @param fillOnly true仅补空号,false核对更新已有号码
* @return 实际更新的系统用户行数
*/
int saveTeacherMobile(String jobNo, String mobile, Map<String, String> originalMobiles, boolean fillOnly);
}
@@ -438,6 +438,101 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
return listMap(sql);
}
/**
* 后台任务逐工号读取号码:每个分页请求均设置超时,且不输出号码或接口原文。
* 分页间等待一秒,避免单人工号多页响应绕过任务限速。
*/
@Override
public String fetchTeacherMobile(String jobNo) {
DataCenterProperties.Credential credential = dataCenterProperties.credential(TEACHER_MOBILE_CONFIG_KEY);
Set<String> numbers = new LinkedHashSet<>();
int pages = 1;
for (int page = 1; page <= pages; page++) {
if (page > 1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BaseException("手机号拉取已中断");
}
}
String body;
try (cn.hutool.http.HttpResponse response = HttpUtil.createPost(credential.getUrl())
.header(Header.CONTENT_TYPE, "application/json")
.header("X-H3C-TOKEN", credential.getToken())
.timeout(10000)
.body(JSONUtil.toJsonStr(buildTeacherMobileRequestBody(jobNo, page, TEACHER_MOBILE_PAGE_SIZE)))
.execute()) {
if (response.getStatus() != 200) {
throw new BaseException("手机号接口HTTP异常,请稍后重试");
}
body = response.body();
}
pages = collectTeacherMobilePage(body, jobNo, numbers);
}
return numbers.isEmpty() ? "" : numbers.iterator().next();
}
/** 校验单工号响应结构、页数与号码一致性;只有明确的空records或空号码才视为无号码。 */
private int collectTeacherMobilePage(String body, String jobNo, Set<String> numbers) {
JSONObject response = JSONUtil.parseObj(body);
if (!"0".equals(response.getStr("code"))) {
throw new BaseException("手机号接口未成功返回,请检查接口授权或稍后重试");
}
JSONObject data = response.getJSONObject("data");
JSONObject pagination = data == null ? response : data;
int pages = Math.max(pagination.getInt("pages", 1),
(int) Math.ceil(pagination.getInt("total", 0) / (double) TEACHER_MOBILE_PAGE_SIZE));
// 单工号异常大分页通常意味着过滤条件失效,禁止继续当作全量接口拉取。
if (pages > 5) {
throw new BaseException("单工号返回数据量异常");
}
JSONArray records = response.getJSONArray("records");
if (records == null && data != null) records = data.getJSONArray("records");
if (records == null) {
throw new BaseException("手机号接口缺少records字段");
}
for (Object record : records) {
JSONObject row = JSONUtil.parseObj(record);
if (!jobNo.equals(StrUtil.trimToEmpty(row.getStr("gh")))) {
throw new BaseException("手机号接口返回工号不匹配");
}
String mobile = StrUtil.trimToEmpty(row.getStr("sjh"));
if (mobile.startsWith("+86")) mobile = mobile.substring(3);
if (mobile.startsWith("0086")) mobile = mobile.substring(4);
if (StrUtil.isNotBlank(mobile)) {
if (!mobile.matches("1[3-9][0-9]{9}")) {
throw new BaseException("接口手机号格式异常");
}
numbers.add(mobile);
}
}
if (numbers.size() > 1) {
throw new BaseException("同一工号返回多个不同手机号,需人工核实");
}
return pages;
}
/**
* 仅在原手机号未被他人修改时落库;空返回值不清空原号,补齐模式不覆盖已有号码。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public int saveTeacherMobile(String jobNo, String mobile, Map<String, String> originalMobiles, boolean fillOnly) {
if (StrUtil.isBlank(mobile) || !mobile.matches("1[3-9][0-9]{9}")) return 0;
int updated = 0;
for (Map.Entry<String, String> entry : originalMobiles.entrySet()) {
String oldMobile = entry.getValue();
if (Objects.equals(oldMobile, mobile) || (fillOnly && StrUtil.isNotBlank(oldMobile))) continue;
Cnd cnd = Cnd.where(Sys_user::getId, "=", entry.getKey())
.and(Sys_user::getLoginname, "=", jobNo);
if (oldMobile == null) cnd.and("mobile", "IS", null);
else cnd.and("mobile", "=", oldMobile);
updated += dao().update(Sys_user.class, Chain.make("mobile", mobile), cnd);
}
return updated;
}
/**
* 从数据中心分页同步教职工手机号,并按工号更新 sys_user.mobile。
*/
@@ -5,11 +5,8 @@ import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.DesensitizedUtil;
import com.alibaba.excel.EasyExcel;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.services.SysDataUserPullService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.zhgh.staffmanage.member.models.MemberHistory;
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm;
import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService;
@@ -51,9 +48,6 @@ public class MemberInfoGroupController {
@Inject
private MemberInfoService memberInfoService;
@Inject
private SysDataUserPullService sysUserPullService;
@At("")
@Ok("beetl:/platform/zhgh/staffmanage/member/info/group/index.html")
@SaCheckPermission("member.info.group")
@@ -86,29 +80,6 @@ public class MemberInfoGroupController {
return Result.success(memberInfoService.dao().count(MemberHistory.class, Cnd.where("year", "=", year)));
}
@At
@SaCheckPermission("member.info.group")
@Aop(TransAop.READ_COMMITTED)
@SLog(tag = "会员档案管理", msg = "获取会员手机号")
public Result syncMobile() {
try {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("当前用户无权获取会员手机号");
}
List<String> jobNos = memberInfoService.queryRandomBlankMobileMemberJobNos(500);
if (jobNos.isEmpty()) {
return Result.error("当前没有手机号为空的会员");
}
NutMap result = sysUserPullService.syncTeacherMobileByJobNos(jobNos);
result.put("sampleCount", jobNos.size());
return Result.success(result);
} catch (Exception e) {
log.error("会员手机号获取失败", e);
return Result.error(e.getMessage());
}
}
@At
@SaCheckPermission("member.info.group")
@Aop(TransAop.READ_COMMITTED)