Commit a90b8d05 by 真的三个金的鑫

fix: clarify user-team membership and harden login/org APIs.

Show org names on user list, reclaim orphaned accounts into the current team, make device-org assignment idempotent, and fall back when token org is missing so members can log in with the correct team.

Co-authored-by: Cursor <cursoragent@cursor.com>
parent edb90765
...@@ -50,11 +50,19 @@ public class CustomClaim { ...@@ -50,11 +50,19 @@ public class CustomClaim {
try { try {
Field[] declaredFields = this.getClass().getDeclaredFields(); Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) { for (Field field : declaredFields) {
// 跳过静态字段(如 @Slf4j 的 log)
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
JsonAlias annotation = field.getAnnotation(JsonAlias.class); JsonAlias annotation = field.getAnnotation(JsonAlias.class);
field.setAccessible(true); field.setAccessible(true);
Object value = field.get(this);
if (value == null) {
continue;
}
// The value of key is named underscore. // The value of key is named underscore.
map.put(annotation != null ? annotation.value()[0] : field.getName(), map.put(annotation != null ? annotation.value()[0] : field.getName(),
field.get(this).toString()); value.toString());
} }
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
log.info("CustomClaim converts failed. {}", this.toString()); log.info("CustomClaim converts failed. {}", this.toString());
...@@ -70,10 +78,16 @@ public class CustomClaim { ...@@ -70,10 +78,16 @@ public class CustomClaim {
public CustomClaim (Map<String, Claim> claimMap) { public CustomClaim (Map<String, Claim> claimMap) {
Field[] declaredFields = this.getClass().getDeclaredFields(); Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) { for (Field field : declaredFields) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
field.setAccessible(true); field.setAccessible(true);
JsonAlias annotation = field.getAnnotation(JsonAlias.class); JsonAlias annotation = field.getAnnotation(JsonAlias.class);
Claim value = claimMap.get(annotation == null ? field.getName() : annotation.value()[0]); Claim value = claimMap.get(annotation == null ? field.getName() : annotation.value()[0]);
if (value == null || value.isNull()) {
continue;
}
try { try {
Class<?> type = field.getType(); Class<?> type = field.getType();
if (Integer.class.equals(type)) { if (Integer.class.equals(type)) {
......
...@@ -20,6 +20,8 @@ import javax.servlet.http.HttpServletResponse; ...@@ -20,6 +20,8 @@ import javax.servlet.http.HttpServletResponse;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import org.springframework.util.StringUtils;
import static com.dji.sample.common.util.SecurityUtils.*; import static com.dji.sample.common.util.SecurityUtils.*;
import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM; import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;
...@@ -31,16 +33,34 @@ public class OrgController { ...@@ -31,16 +33,34 @@ public class OrgController {
private IOrgService orgService; private IOrgService orgService;
/** /**
* Gets information about the workspace that the current user is in. * Gets information about the org that the current user is in.
* @param request * Token 中的 org 若已被删除,回退到当前工作区下可用团队,避免直接 failed。
* @return
*/ */
@GetMapping("/current") @GetMapping("/current")
public HttpResultResponse getCurrentOrg(HttpServletRequest request) { public HttpResultResponse getCurrentOrg(HttpServletRequest request) {
CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM); CustomClaim customClaim = (CustomClaim) request.getAttribute(TOKEN_CLAIM);
Optional<OrgDTO> orgOpt = orgService.getOrgByOrgId(customClaim.getOrgId()); if (customClaim == null) {
return HttpResultResponse.error("未登录或登录已失效");
}
return orgOpt.isEmpty() ? HttpResultResponse.error() : HttpResultResponse.success(orgOpt.get()); String orgId = customClaim.getOrgId();
if (StringUtils.hasText(orgId)) {
Optional<OrgDTO> orgOpt = orgService.getOrgByOrgId(orgId);
if (orgOpt.isPresent()) {
return HttpResultResponse.success(orgOpt.get());
}
}
// 回退:当前工作区下找一个可用团队(优先 geoSys)
String workspaceId = customClaim.getWorkspaceId();
if (!StringUtils.hasText(workspaceId)) {
workspaceId = getWorkspaceId();
}
Optional<OrgDTO> fallback = orgService.getFallbackOrg(workspaceId, customClaim.getId(), aboveSysAdminRole());
if (fallback.isPresent()) {
return HttpResultResponse.success(fallback.get());
}
return HttpResultResponse.error("当前工作区下没有可用团队,请先创建或加入团队");
} }
@GetMapping("/getOne") @GetMapping("/getOne")
......
...@@ -92,8 +92,8 @@ public class UserController { ...@@ -92,8 +92,8 @@ public class UserController {
@PostMapping("/{workspace_id}/addUser") @PostMapping("/{workspace_id}/addUser")
public HttpResultResponse addUser(@RequestBody UserEntity user, public HttpResultResponse addUser(@RequestBody UserEntity user,
@PathVariable("workspace_id") String workspaceId) { @PathVariable("workspace_id") String workspaceId) {
userService.addUser(workspaceId, user); String action = userService.addUserToCurrentOrg(workspaceId, user);
return HttpResultResponse.success(); return HttpResultResponse.success(action);
} }
/** /**
......
...@@ -19,6 +19,6 @@ public interface IUserOrgMapper extends BaseMapper<UserOrgEntity> { ...@@ -19,6 +19,6 @@ public interface IUserOrgMapper extends BaseMapper<UserOrgEntity> {
*/ */
@Select("SELECT uo.*, o.org_name FROM manage_user_org uo " + @Select("SELECT uo.*, o.org_name FROM manage_user_org uo " +
"LEFT JOIN manage_org o ON uo.org_id = o.org_id " + "LEFT JOIN manage_org o ON uo.org_id = o.org_id " +
"WHERE uo.user_id = #{userId} AND uo.status = 1") "WHERE uo.user_id = #{userId} AND (uo.status IS NULL OR uo.status = 1)")
List<UserOrgDTO> selectUserOrgs(@Param("userId") String userId); List<UserOrgDTO> selectUserOrgs(@Param("userId") String userId);
} }
...@@ -32,6 +32,11 @@ public class UserListDTO { ...@@ -32,6 +32,11 @@ public class UserListDTO {
private Integer roleType; private Integer roleType;
/**
* 所属团队名称,多个用顿号拼接
*/
private String orgNames;
private String mqttUsername; private String mqttUsername;
private String mqttPassword; private String mqttPassword;
......
...@@ -22,6 +22,12 @@ public interface IOrgService extends IService<OrgEntity> { ...@@ -22,6 +22,12 @@ public interface IOrgService extends IService<OrgEntity> {
*/ */
Optional<OrgDTO> getOrgByOrgId(String orgId); Optional<OrgDTO> getOrgByOrgId(String orgId);
/**
* Token 中团队失效时,回退查找工作区下可用团队。
* 优先 geoSys;非超管优先返回自己加入过的团队。
*/
Optional<OrgDTO> getFallbackOrg(String workspaceId, String userId, boolean sysAdmin);
PaginationData<OrgDTO> getOrgPage(OrgSearchParam param, Long page, Long pageSize); PaginationData<OrgDTO> getOrgPage(OrgSearchParam param, Long page, Long pageSize);
OrgDTO addOrg(OrgDTO orgDTO); OrgDTO addOrg(OrgDTO orgDTO);
......
...@@ -88,6 +88,12 @@ public interface IUserService extends IService<UserEntity> { ...@@ -88,6 +88,12 @@ public interface IUserService extends IService<UserEntity> {
Boolean addUser(String workspaceId, UserEntity user); Boolean addUser(String workspaceId, UserEntity user);
/** /**
* 添加用户到当前团队。
* @return created=新建账号;joined=账号已存在,已加入当前团队
*/
String addUserToCurrentOrg(String workspaceId, UserEntity user);
/**
* Query user's details based on userId * Query user's details based on userId
* @param username * @param username
* @param workspaceId * @param workspaceId
......
...@@ -2049,14 +2049,35 @@ public class DeviceServiceImpl extends ServiceImpl<IDeviceMapper, DeviceEntity> ...@@ -2049,14 +2049,35 @@ public class DeviceServiceImpl extends ServiceImpl<IDeviceMapper, DeviceEntity>
if (!StringUtils.hasText(workspaceId) || !StringUtils.hasText(orgId)) { if (!StringUtils.hasText(workspaceId) || !StringUtils.hasText(orgId)) {
return new ArrayList<>(); return new ArrayList<>();
} }
// 同时兼容 workspace_id 为空的历史脏数据,避免已分配设备重复插入
LambdaQueryWrapper<DeviceOrgEntity> deviceOrgQueryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DeviceOrgEntity> deviceOrgQueryWrapper = new LambdaQueryWrapper<>();
deviceOrgQueryWrapper.eq(DeviceOrgEntity::getWorkspaceId, workspaceId);
deviceOrgQueryWrapper.eq(DeviceOrgEntity::getOrgId, orgId); deviceOrgQueryWrapper.eq(DeviceOrgEntity::getOrgId, orgId);
return deviceOrgService.list(deviceOrgQueryWrapper).stream() deviceOrgQueryWrapper.and(w -> w.eq(DeviceOrgEntity::getWorkspaceId, workspaceId)
.map(DeviceOrgEntity::getDeviceSn) .or().isNull(DeviceOrgEntity::getWorkspaceId)
.filter(StringUtils::hasText) .or().eq(DeviceOrgEntity::getWorkspaceId, ""));
.distinct() List<DeviceOrgEntity> relations = deviceOrgService.list(deviceOrgQueryWrapper);
.collect(Collectors.toList()); if (CollectionUtils.isEmpty(relations)) {
return new ArrayList<>();
}
Set<String> sns = new HashSet<>();
Set<Integer> needLookupIds = new HashSet<>();
for (DeviceOrgEntity rel : relations) {
if (StringUtils.hasText(rel.getDeviceSn())) {
sns.add(rel.getDeviceSn());
} else if (rel.getDeviceId() != null) {
needLookupIds.add(rel.getDeviceId());
}
}
if (!needLookupIds.isEmpty()) {
List<DeviceEntity> devices = this.listByIds(needLookupIds);
for (DeviceEntity device : devices) {
if (StringUtils.hasText(device.getDeviceSn())) {
sns.add(device.getDeviceSn());
}
}
}
return new ArrayList<>(sns);
} }
private List<String> listUserDeviceSns(String workspaceId, String orgId, String userId) { private List<String> listUserDeviceSns(String workspaceId, String orgId, String userId) {
...@@ -2106,13 +2127,6 @@ public class DeviceServiceImpl extends ServiceImpl<IDeviceMapper, DeviceEntity> ...@@ -2106,13 +2127,6 @@ public class DeviceServiceImpl extends ServiceImpl<IDeviceMapper, DeviceEntity>
if (!StringUtils.hasText(deviceSn) || !StringUtils.hasText(workspaceId) || !StringUtils.hasText(orgId)) { if (!StringUtils.hasText(deviceSn) || !StringUtils.hasText(workspaceId) || !StringUtils.hasText(orgId)) {
return; return;
} }
LambdaQueryWrapper<DeviceOrgEntity> existsQuery = new LambdaQueryWrapper<>();
existsQuery.eq(DeviceOrgEntity::getDeviceSn, deviceSn);
existsQuery.eq(DeviceOrgEntity::getOrgId, orgId);
existsQuery.eq(DeviceOrgEntity::getWorkspaceId, workspaceId);
if (deviceOrgService.count(existsQuery) > 0) {
return;
}
DeviceEntity dbDevice = this.getOne(new LambdaQueryWrapper<DeviceEntity>() DeviceEntity dbDevice = this.getOne(new LambdaQueryWrapper<DeviceEntity>()
.eq(DeviceEntity::getDeviceSn, deviceSn)); .eq(DeviceEntity::getDeviceSn, deviceSn));
...@@ -2120,19 +2134,80 @@ public class DeviceServiceImpl extends ServiceImpl<IDeviceMapper, DeviceEntity> ...@@ -2120,19 +2134,80 @@ public class DeviceServiceImpl extends ServiceImpl<IDeviceMapper, DeviceEntity>
throw new RuntimeException("device does not exist: " + deviceSn); throw new RuntimeException("device does not exist: " + deviceSn);
} }
Integer sharedFlag = isShared == null ? 1 : isShared;
// 唯一键 uk_device_org 是 (device_id, org_id),必须以它为准做幂等,避免脏数据导致重复插入
DeviceOrgEntity existing = null;
if (dbDevice.getId() != null) {
existing = deviceOrgService.getOne(new LambdaQueryWrapper<DeviceOrgEntity>()
.eq(DeviceOrgEntity::getDeviceId, dbDevice.getId())
.eq(DeviceOrgEntity::getOrgId, orgId)
.last("LIMIT 1"), false);
}
if (existing == null) {
existing = deviceOrgService.getOne(new LambdaQueryWrapper<DeviceOrgEntity>()
.eq(DeviceOrgEntity::getDeviceSn, deviceSn)
.eq(DeviceOrgEntity::getOrgId, orgId)
.last("LIMIT 1"), false);
}
if (existing != null) {
boolean needUpdate = false;
if (!deviceSn.equals(existing.getDeviceSn())) {
existing.setDeviceSn(deviceSn);
needUpdate = true;
}
if (!workspaceId.equals(existing.getWorkspaceId())) {
existing.setWorkspaceId(workspaceId);
needUpdate = true;
}
if (existing.getDeviceId() == null || !existing.getDeviceId().equals(dbDevice.getId())) {
existing.setDeviceId(dbDevice.getId());
needUpdate = true;
}
if (existing.getIsShared() == null || !existing.getIsShared().equals(sharedFlag)) {
existing.setIsShared(sharedFlag);
needUpdate = true;
}
if (needUpdate) {
existing.setUpdaterId(getUserId());
existing.setUpdaterName(getUsername());
existing.setUpdateTime(System.currentTimeMillis());
deviceOrgService.updateById(existing);
}
return;
}
DeviceOrgEntity deviceOrgEntity = new DeviceOrgEntity(); DeviceOrgEntity deviceOrgEntity = new DeviceOrgEntity();
deviceOrgEntity.setWorkspaceId(workspaceId); deviceOrgEntity.setWorkspaceId(workspaceId);
deviceOrgEntity.setOrgId(orgId); deviceOrgEntity.setOrgId(orgId);
deviceOrgEntity.setDeviceSn(deviceSn); deviceOrgEntity.setDeviceSn(deviceSn);
deviceOrgEntity.setDeviceId(dbDevice.getId()); deviceOrgEntity.setDeviceId(dbDevice.getId());
deviceOrgEntity.setIsShared(isShared == null ? 1 : isShared); deviceOrgEntity.setIsShared(sharedFlag);
deviceOrgEntity.setCreatorId(getUserId()); deviceOrgEntity.setCreatorId(getUserId());
deviceOrgEntity.setCreatorName(getUsername()); deviceOrgEntity.setCreatorName(getUsername());
deviceOrgEntity.setCreateTime(System.currentTimeMillis()); deviceOrgEntity.setCreateTime(System.currentTimeMillis());
deviceOrgEntity.setUpdaterId(getUserId()); deviceOrgEntity.setUpdaterId(getUserId());
deviceOrgEntity.setUpdaterName(getUsername()); deviceOrgEntity.setUpdaterName(getUsername());
deviceOrgEntity.setUpdateTime(System.currentTimeMillis()); deviceOrgEntity.setUpdateTime(System.currentTimeMillis());
deviceOrgService.save(deviceOrgEntity); try {
deviceOrgService.save(deviceOrgEntity);
} catch (org.springframework.dao.DuplicateKeyException e) {
// 并发下偶发撞唯一键:再查一次并修复字段即可
DeviceOrgEntity raced = deviceOrgService.getOne(new LambdaQueryWrapper<DeviceOrgEntity>()
.eq(DeviceOrgEntity::getDeviceId, dbDevice.getId())
.eq(DeviceOrgEntity::getOrgId, orgId)
.last("LIMIT 1"), false);
if (raced != null) {
raced.setDeviceSn(deviceSn);
raced.setWorkspaceId(workspaceId);
raced.setIsShared(sharedFlag);
raced.setUpdaterId(getUserId());
raced.setUpdaterName(getUsername());
raced.setUpdateTime(System.currentTimeMillis());
deviceOrgService.updateById(raced);
}
}
} }
/** /**
......
...@@ -63,6 +63,9 @@ public class OrgServiceImpl extends ServiceImpl<IOrgMapper, OrgEntity> implement ...@@ -63,6 +63,9 @@ public class OrgServiceImpl extends ServiceImpl<IOrgMapper, OrgEntity> implement
@Override @Override
public Optional<OrgDTO> getOrgByOrgId(String orgId) { public Optional<OrgDTO> getOrgByOrgId(String orgId) {
if (!StringUtils.hasText(orgId)) {
return Optional.empty();
}
LambdaQueryWrapper<OrgEntity> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<OrgEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrgEntity::getOrgId, orgId); queryWrapper.eq(OrgEntity::getOrgId, orgId);
...@@ -74,6 +77,44 @@ public class OrgServiceImpl extends ServiceImpl<IOrgMapper, OrgEntity> implement ...@@ -74,6 +77,44 @@ public class OrgServiceImpl extends ServiceImpl<IOrgMapper, OrgEntity> implement
return Optional.of(entityConvertToDto(entity)); return Optional.of(entityConvertToDto(entity));
} }
@Override
public Optional<OrgDTO> getFallbackOrg(String workspaceId, String userId, boolean sysAdmin) {
if (!StringUtils.hasText(workspaceId)) {
return Optional.empty();
}
// 1) 优先默认团队 geoSys
Optional<OrgDTO> geoSys = getOrgByOrgId("geoSys");
if (geoSys.isPresent() && workspaceId.equals(geoSys.get().getWorkspaceId())) {
return geoSys;
}
// 2) 非超管:优先自己加入的团队
if (!sysAdmin && StringUtils.hasText(userId)) {
LambdaQueryWrapper<UserOrgEntity> userOrgWrapper = new LambdaQueryWrapper<>();
userOrgWrapper.eq(UserOrgEntity::getUserId, userId)
.eq(UserOrgEntity::getWorkspaceId, workspaceId)
.and(w -> w.isNull(UserOrgEntity::getStatus).or().eq(UserOrgEntity::getStatus, 1))
.orderByDesc(UserOrgEntity::getUpdateTime)
.last("LIMIT 1");
UserOrgEntity relation = userOrgService.getOne(userOrgWrapper, false);
if (relation != null && StringUtils.hasText(relation.getOrgId())) {
Optional<OrgDTO> joined = getOrgByOrgId(relation.getOrgId());
if (joined.isPresent()) {
return joined;
}
}
}
// 3) 工作区下任意一个团队
LambdaQueryWrapper<OrgEntity> orgWrapper = new LambdaQueryWrapper<>();
orgWrapper.eq(OrgEntity::getWorkspaceId, workspaceId)
.orderByAsc(OrgEntity::getCreateTime)
.last("LIMIT 1");
OrgEntity entity = getOne(orgWrapper, false);
return entity == null ? Optional.empty() : Optional.of(entityConvertToDto(entity));
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public OrgDTO addOrg(OrgDTO orgDTO) { public OrgDTO addOrg(OrgDTO orgDTO) {
...@@ -145,6 +186,7 @@ public class OrgServiceImpl extends ServiceImpl<IOrgMapper, OrgEntity> implement ...@@ -145,6 +186,7 @@ public class OrgServiceImpl extends ServiceImpl<IOrgMapper, OrgEntity> implement
userOrgEntity.setUpdaterId(getUserId()); userOrgEntity.setUpdaterId(getUserId());
userOrgEntity.setUpdaterName(getUsername()); userOrgEntity.setUpdaterName(getUsername());
userOrgEntity.setRoleType(RoleTypeEnum.ORG_ADMIN.getVal()); userOrgEntity.setRoleType(RoleTypeEnum.ORG_ADMIN.getVal());
userOrgEntity.setStatus(1);
boolean userOrgSaveRes = userOrgService.save(userOrgEntity); boolean userOrgSaveRes = userOrgService.save(userOrgEntity);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment