This commit is contained in:
dzc committed 2026-09-10 13:55:01 +08:00
1 parent b254ef0783
commit 3effd6f9be
13 files changed
+275 -24

No files matched your search

@@ -34,4 +34,9 @@ public class HardOfflineUserSyncDto {
*/
@Min(value = 0, message = "离线人员同步快照上界无效")
private Long snapshotMaxId;
/**
* 是否同步全租户人员;仅非生产调试使用,生产端点行为与本部门一致。
*/
private Boolean allTenant;
}
@@ -27,11 +27,11 @@ import com.bsd.pjcloud.wallet.api.feign.RemoteWalletService;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -57,6 +57,7 @@ public class HardApiServiceImpl implements HardApiService {
private final RemoteDeptService remoteDeptService;
private final HardManagerMapper hardManagerMapper;
private final HardManagerService hardManagerService;
private final Environment environment;
@Override
public HardAdUserVo getUserById(Long userId, HardTypeEnum type) {
@@ -121,12 +122,13 @@ public class HardApiServiceImpl implements HardApiService {
}
/**
* 按设备绑定部门树返回脱敏后的离线人员游标快照。
* 按设备绑定部门树(或全租户)返回脱敏后的离线人员游标快照。
*
* <p>部门范围、钱包类型全部由 DEVICE-ID 对应的真实绑定关系解析;姓名只保留
* 首字符,其余以星号替代,且不返回手机号和头像。未绑定当前类型钱包、卡号为空
* 或钱包已冻结的用户不进入快照;游标始终按部门全部用户主键推进,被过滤用户
* 不会造成分页重复。</p>
* 或钱包已冻结的用户不进入快照;游标始终按用户主键推进,被过滤用户不会造成
* 分页重复。allTenant 仅用于非生产调试的全租户同步,生产环境调用时仍按设备
* 绑定部门返回。</p>
*/
@Override
public HardOfflineUserSyncSnapshotVo syncOfflineUserSnapshot(Long hardId, HardOfflineUserSyncDto dto) {
@@ -140,27 +142,25 @@ public class HardApiServiceImpl implements HardApiService {
? WalletTypeEnum.EAT
: WalletTypeEnum.PAY;
List<Long> deptIds = resolveDepartmentTree(trustedDevice.merchantDeptId());
if (deptIds.isEmpty()) {
final boolean allTenant = Boolean.TRUE.equals(dto.getAllTenant()) && !isProdProfile();
final List<Long> deptIds = allTenant
? null
: resolveDepartmentTree(trustedDevice.merchantDeptId());
if (!allTenant && deptIds.isEmpty()) {
throw new RuntimeException("设备绑定部门不存在或已删除");
}
R<List<SysUser>> userResponse = userService.getUserIdListByDeptIdList(deptIds);
if (userResponse == null || !userResponse.isOk() || userResponse.getData() == null) {
String message = userResponse == null || StrUtil.isBlank(userResponse.getMsg())
? "查询部门用户失败"
: userResponse.getMsg();
throw new RuntimeException(message);
}
List<SysUser> users = userResponse.getData().stream()
.filter(user -> user.getUserId() != null && StrUtil.isNotBlank(user.getName()))
.sorted(Comparator.comparing(SysUser::getUserId))
.toList();
long normalizedAfterId = dto.getAfterId();
Long requestedSnapshotMaxId = dto.getSnapshotMaxId();
if (requestedSnapshotMaxId == null) {
requestedSnapshotMaxId = users.isEmpty() ? 0L : users.get(users.size() - 1).getUserId();
R<Long> maxIdResponse = userService.getOfflineUserMaxId(deptIds);
if (maxIdResponse == null || !maxIdResponse.isOk()) {
String message = maxIdResponse == null || StrUtil.isBlank(maxIdResponse.getMsg())
? "查询离线人员快照上界失败"
: maxIdResponse.getMsg();
throw new RuntimeException(message);
}
requestedSnapshotMaxId = maxIdResponse.getData() == null ? 0L : maxIdResponse.getData();
}
final long normalizedSnapshotMaxId = requestedSnapshotMaxId;
if (normalizedAfterId > normalizedSnapshotMaxId) {
@@ -168,11 +168,8 @@ public class HardApiServiceImpl implements HardApiService {
}
int normalizedLimit = dto.getLimit() == null ? OFFLINE_USER_SYNC_DEFAULT_LIMIT : dto.getLimit();
List<SysUser> fetchedPage = users.stream()
.filter(user -> user.getUserId() > normalizedAfterId
&& user.getUserId() <= normalizedSnapshotMaxId)
.limit(normalizedLimit + 1L)
.toList();
// 多取一条用于判断是否还有下一页;用户过滤发生在钱包聚合之后,不影响游标推进。
List<SysUser> fetchedPage = fetchUserCursorPage(deptIds, normalizedAfterId, normalizedLimit + 1);
boolean hasMore = fetchedPage.size() > normalizedLimit;
List<SysUser> pageUsers = hasMore ? fetchedPage.subList(0, normalizedLimit) : fetchedPage;
@@ -187,6 +184,34 @@ public class HardApiServiceImpl implements HardApiService {
return snapshot;
}
/**
* 从用户服务按主键游标取一页部门范围(空为全租户)内的用户并过滤无效记录。
*/
private List<SysUser> fetchUserCursorPage(List<Long> deptIds, long afterId, int limit) {
R<List<SysUser>> response = userService.getOfflineUserCursor(deptIds, afterId, limit);
if (response == null || !response.isOk() || response.getData() == null) {
String message = response == null || StrUtil.isBlank(response.getMsg())
? "查询部门用户失败"
: response.getMsg();
throw new RuntimeException(message);
}
return response.getData().stream()
.filter(user -> user.getUserId() != null && StrUtil.isNotBlank(user.getName()))
.toList();
}
/**
* 当前服务是否运行在生产配置;生产环境强制收敛为部门范围同步。
*/
private boolean isProdProfile() {
for (String profile : environment.getActiveProfiles()) {
if (StrUtil.containsIgnoreCase(profile, "prod")) {
return true;
}
}
return false;
}
/**
* 将一页部门用户组装为脱敏离线人员记录;食堂归属按部门聚合缓存一次查询。
*/