feat(app): 新增 /oapi/app/authorized-codes 判权端点(消息跳转收口 C1)——findUserAuthorizedAppIds 判权 + listAppCodesByIds 映射 app_code(空值跳过/首现去重);数据面与控制器用例补齐
This commit is contained in:
@@ -8,6 +8,7 @@ import com.yunhai.userbase.entity.ApplicationInfo;
|
||||
import com.yunhai.userbase.model.application.ApplicationInfoVo;
|
||||
import com.yunhai.userbase.service.IAppCategoryService;
|
||||
import com.yunhai.userbase.service.IApplicationInfoService;
|
||||
import com.yunhai.userbase.service.IGroupAuthzService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -33,6 +34,9 @@ public class AppController {
|
||||
@Autowired
|
||||
private IApplicationInfoService applicationInfoService;
|
||||
|
||||
@Autowired
|
||||
private IGroupAuthzService groupAuthzService;
|
||||
|
||||
/**
|
||||
* 按主键 ID 查询应用信息(带缓存)。<br>
|
||||
* 与客户端契约 {@code GET /oapi/app/{appId}} 对应;查不到返回 null(HTTP 200 + data:null 信封)。<br>
|
||||
@@ -73,6 +77,21 @@ public class AppController {
|
||||
return toVo(applicationInfoService.getByAppKey(appKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户有权应用的 app_code 集合(消息跳转判权,C1)。<br>
|
||||
* 判权复用 {@code IGroupAuthzService.findUserAuthorizedAppIds}(组开通 ∪ 启用角色模块所属应用,
|
||||
* 超级管理员返回全部),再映射为 app_code(空值行跳过)。只读,不透出 appSecret。<br>
|
||||
* 消费方(UserHome 消息代理)以「集合是否包含消息 jump_app」决定是否显示跳转入口;
|
||||
* 本端点不可达时消费方放行(不判权),绝不因判权通道故障批量隐藏入口。
|
||||
*
|
||||
* @param userId 用户ID(必填)
|
||||
* @return 有权应用的 app_code 集合(可能为空,不为 null)
|
||||
*/
|
||||
@GetMapping("authorized-codes")
|
||||
public List<String> authorizedCodes(@RequestParam("userId") Integer userId) {
|
||||
return applicationInfoService.listAppCodesByIds(groupAuthzService.findUserAuthorizedAppIds(userId));
|
||||
}
|
||||
|
||||
private ApplicationInfoVo toVo(ApplicationInfo info) {
|
||||
if (info == null) {
|
||||
return null;
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.yunhai.userbase.model.application.PushSettingVo;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -45,6 +46,15 @@ public interface IApplicationInfoService {
|
||||
*/
|
||||
ApplicationInfo getByAppKey(Integer appKey);
|
||||
|
||||
/**
|
||||
* 按应用 id 集合取技术标识 app_code 列表(/oapi/app/authorized-codes 数据面,判权由调用方完成)。<br>
|
||||
* 顺序跟随入参集合;app_code 为空的行跳过(存量历史行允许 null);结果按首次出现去重。
|
||||
*
|
||||
* @param ids 应用主键 id 集合
|
||||
* @return app_code 列表(可能为空,不为 null)
|
||||
*/
|
||||
List<String> listAppCodesByIds(Collection<Integer> ids);
|
||||
|
||||
/**
|
||||
* 保存/更新应用信息(写后同步更新缓存)。
|
||||
*/
|
||||
|
||||
@@ -37,8 +37,11 @@ import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@@ -120,6 +123,22 @@ public class ApplicationInfoService implements IApplicationInfoService {
|
||||
return id == null ? null : getById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listAppCodesByIds(Collection<Integer> ids) {
|
||||
List<String> codes = new ArrayList<>();
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return codes;
|
||||
}
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (ApplicationInfo info : applicationInfoRepository.findAllById(ids)) {
|
||||
String code = info.getAppCode();
|
||||
if (StringUtils.isNotBlank(code) && seen.add(code)) {
|
||||
codes.add(code);
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用保存:方法返回后发布变更事件,缓存失效由 AFTER_COMMIT 监听器统一执行。
|
||||
*/
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.yunhai.userbase.controller.oapi;
|
||||
|
||||
import com.yunhai.userbase.service.IApplicationInfoService;
|
||||
import com.yunhai.userbase.service.IGroupAuthzService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* /oapi/app/authorized-codes 判权端点单测(消息跳转收口 C1)。<br>
|
||||
* 控制器薄委托:判权来源 {@code IGroupAuthzService.findUserAuthorizedAppIds}(超管返回全部 /
|
||||
* 普通用户返回子集 / 空权限返回空),数据面 {@code listAppCodesByIds} 映射 app_code
|
||||
* (空值跳过/去重语义见 ApplicationInfoLookupTest)。
|
||||
*/
|
||||
@DisplayName("AppController.authorizedCodes - 有权 app_code 集合")
|
||||
class AppControllerAuthorizedCodesTest {
|
||||
|
||||
@InjectMocks
|
||||
private AppController controller;
|
||||
|
||||
@Mock
|
||||
private IGroupAuthzService groupAuthzService;
|
||||
|
||||
@Mock
|
||||
private IApplicationInfoService applicationInfoService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void 超管_返回全部应用code() {
|
||||
when(groupAuthzService.findUserAuthorizedAppIds(10006)).thenReturn(Arrays.asList(100011, 100013, 100015));
|
||||
when(applicationInfoService.listAppCodesByIds(Arrays.asList(100011, 100013, 100015)))
|
||||
.thenReturn(Arrays.asList("flow-center", "notice", "userhome"));
|
||||
|
||||
List<String> codes = controller.authorizedCodes(10006);
|
||||
|
||||
assertEquals(Arrays.asList("flow-center", "notice", "userhome"), codes);
|
||||
verify(applicationInfoService).listAppCodesByIds(Arrays.asList(100011, 100013, 100015));
|
||||
}
|
||||
|
||||
@Test
|
||||
void 普通用户_仅返回有权子集() {
|
||||
when(groupAuthzService.findUserAuthorizedAppIds(201)).thenReturn(Collections.singletonList(100013));
|
||||
when(applicationInfoService.listAppCodesByIds(Collections.singletonList(100013)))
|
||||
.thenReturn(Collections.singletonList("notice"));
|
||||
|
||||
assertEquals(Collections.singletonList("notice"), controller.authorizedCodes(201));
|
||||
}
|
||||
|
||||
@Test
|
||||
void 空权限_返回空集合() {
|
||||
when(groupAuthzService.findUserAuthorizedAppIds(202)).thenReturn(Collections.emptyList());
|
||||
when(applicationInfoService.listAppCodesByIds(Collections.emptyList()))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
assertEquals(Collections.emptyList(), controller.authorizedCodes(202));
|
||||
verify(groupAuthzService).findUserAuthorizedAppIds(202);
|
||||
verify(applicationInfoService).listAppCodesByIds(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -108,4 +108,31 @@ class ApplicationInfoLookupTest {
|
||||
assertNull(existing.getAppNature(), "性质同样不得被 null 覆盖");
|
||||
verify(applicationInfoRepository, never()).existsByAppCodeAndIdNot(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAppCodesByIds_空appCode行跳过_按首现去重() {
|
||||
ApplicationInfo center = flowCenter();
|
||||
ApplicationInfo legacy = new ApplicationInfo();
|
||||
legacy.setId(100012);
|
||||
legacy.setAppCode(null);
|
||||
ApplicationInfo notice = new ApplicationInfo();
|
||||
notice.setId(100013);
|
||||
notice.setAppCode("notice");
|
||||
when(applicationInfoRepository.findAllById(any())).thenReturn(
|
||||
java.util.Arrays.asList(center, legacy, notice, center));
|
||||
|
||||
java.util.List<String> codes = applicationInfoService.listAppCodesByIds(
|
||||
java.util.Arrays.asList(100011, 100012, 100013));
|
||||
|
||||
assertEquals(java.util.Arrays.asList("flow-center", "notice"), codes,
|
||||
"null app_code 跳过(存量历史行),重复去重");
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAppCodesByIds_空入参返回空不出站() {
|
||||
assertEquals(java.util.Collections.emptyList(), applicationInfoService.listAppCodesByIds(null));
|
||||
assertEquals(java.util.Collections.emptyList(), applicationInfoService.listAppCodesByIds(
|
||||
java.util.Collections.emptyList()));
|
||||
verify(applicationInfoRepository, never()).findAllById(any());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user