feat(app): 应用设置·应用管理——flw_app 白名单 + 学校池同步 + defSave 归属应用校验
- flw_app(sch,app_key uk):学校池(UserBase list-by-school,学校组单点已开通、clientType=null 不过滤端) diff 同步——新增即 enabled=1、改名静默刷新、池外保留;UserBase 不可达降级本地行 - /admin/app/list(触发同步)/ /admin/app/enable;defSave 显式 appKey 须 ∈ 启用集合(不传不拦,升版本继承不受影响) - SQL 备案:flw_app 建表 + UserBase 菜单 107 新增/104 下线(人工执行) - 单测 +8;E2E 13/13(local-debug/four-topics-c-e2e.py) - 关联 SDK 修复:BoaApp.createTime @JsonFormat 对齐 UserBase 时间契约(BasePlatform 单独 commit,Nexus -73)
This commit is contained in:
@@ -50,6 +50,7 @@ public class FlowAdminController {
|
||||
private final FlowDiagramService diagramService;
|
||||
private final com.yunhai.flowcenter.service.DiagramImageService imageService;
|
||||
private final com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
||||
private final com.yunhai.flowcenter.service.FlowAppAdminService appAdminService;
|
||||
|
||||
// ==================== 流程定义 ====================
|
||||
|
||||
@@ -66,6 +67,27 @@ public class FlowAdminController {
|
||||
return defAdminService.distinctAppKeys(adminGuard.requireAdmin(user, null));
|
||||
}
|
||||
|
||||
// ==================== 应用设置(2026-10-10 四议题) ====================
|
||||
|
||||
/**
|
||||
* 应用管理列表:本校"可对接流程"白名单(flw_app)。附带一次学校池静默同步
|
||||
* (UserBase list-by-school;不可达降级本地行)。开通过程在 UserBase 管理端做。
|
||||
*/
|
||||
@GetMapping("/app/list")
|
||||
public List<com.yunhai.flowcenter.entity.FlwApp> appList(BoaUser user) {
|
||||
return appAdminService.list(adminGuard.requireAdmin(user, null));
|
||||
}
|
||||
|
||||
/** 开关某应用是否对接流程(停用后 defSave 归属应用校验拦截新引用) */
|
||||
@PostMapping("/app/enable")
|
||||
public void appEnable(BoaUser user,
|
||||
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) {
|
||||
Long schId = adminGuard.requireAdmin(user, null);
|
||||
appAdminService.enable(schId,
|
||||
body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id"))),
|
||||
Boolean.parseBoolean(String.valueOf(body.get("enabled"))));
|
||||
}
|
||||
|
||||
@GetMapping("/def/get")
|
||||
public FlwDef defGet(BoaUser user, @RequestParam("id") Long id) {
|
||||
return defAdminService.get(adminGuard.requireAdmin(user, null), id);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.yunhai.flowcenter.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
/**
|
||||
* 流程对接应用(2026-10-10 四议题·应用设置):本校"哪些应用可对接流程"的白名单。
|
||||
* 行来源=应用设置页从学校池(UserBase /oapi/app/list-by-school,学校组单点已开通)同步,
|
||||
* 同步即 enabled=1;管理员可停用(停用后 defSave 的归属应用校验拦截新引用)。
|
||||
* app_name 是学校池快照(含学校别名),每次同步静默刷新。
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "flw_app",
|
||||
uniqueConstraints = @UniqueConstraint(name = "uk_flw_app_sch_key", columnNames = {"sch_id", "app_key"}),
|
||||
indexes = @Index(name = "idx_flw_app_sch_enabled", columnList = "sch_id, enabled"))
|
||||
public class FlwApp extends BaseEntity {
|
||||
|
||||
/** 租户键 */
|
||||
@Column(name = "sch_id", nullable = false)
|
||||
private Long schId;
|
||||
|
||||
/** 子应用 appKey(UserBase application_info.app_key 的字符串形态,与 flw_def.app_key 同口径) */
|
||||
@Column(name = "app_key", nullable = false, length = 64)
|
||||
private String appKey;
|
||||
|
||||
/** 应用名快照(学校池返回值,含学校别名;同步时刷新) */
|
||||
@Column(name = "app_name", nullable = false, length = 128)
|
||||
private String appName;
|
||||
|
||||
/** 1=对接流程(白名单内);0=停用(defSave 校验拦截) */
|
||||
@Column(name = "enabled", nullable = false)
|
||||
private Integer enabled = 1;
|
||||
|
||||
/** 展示排序(学校池 orderNumber 快照,可空=0) */
|
||||
@Column(name = "sort_no")
|
||||
private Integer sortNo;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.yunhai.flowcenter.repository;
|
||||
|
||||
import com.yunhai.flowcenter.entity.FlwApp;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface FlwAppRepository extends JpaRepository<FlwApp, Long> {
|
||||
|
||||
List<FlwApp> findBySchIdOrderBySortNoAscIdAsc(Long schId);
|
||||
|
||||
List<FlwApp> findBySchIdAndEnabledOrderBySortNoAscIdAsc(Long schId, Integer enabled);
|
||||
|
||||
/** defSave 归属应用白名单校验:本校启用集合是否包含该 appKey */
|
||||
@Query("select count(a) > 0 from FlwApp a where a.schId = :schId and a.appKey = :appKey and a.enabled = 1")
|
||||
boolean isEnabledApp(@Param("schId") Long schId, @Param("appKey") String appKey);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.yunhai.flowcenter.service;
|
||||
|
||||
import com.yunhai.flowcenter.entity.FlwApp;
|
||||
import com.yunhai.flowcenter.repository.FlwAppRepository;
|
||||
import com.yunhai.platform.openapi.base.OpenApiAppClient;
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 应用设置·应用管理(2026-10-10 四议题):flw_app 白名单的同步与开关。
|
||||
*
|
||||
* <p><b>源池口径(拍板 D7)</b>:UserBase {@code /oapi/app/list-by-school}——学校组单点已开通
|
||||
* (sys_group_app_rel,不并子组/角色模块),clientType 传 null 不过滤端(拍板 D3,避免漏纯移动端应用)。
|
||||
* 同步即入白名单 enabled=1(拍板 D2:只做最优实现,不做存量 appKey 兜底——不在学校池的应用
|
||||
* 进不了白名单,defSave 引用会被拦,属拍板接受的后果);池内应用名变化静默刷新(学校别名);
|
||||
* 学校池已移除的本地行保留(enabled 交管理员处置)。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FlowAppAdminService {
|
||||
|
||||
private final FlwAppRepository appRepository;
|
||||
private final OpenApiAppClient appClient;
|
||||
|
||||
/**
|
||||
* 列表(管理端应用设置页)。附带一次静默学校池同步:UserBase 不可达/学校未开通任何应用时
|
||||
* 降级只返回本地行(首次使用前 flw_app 为空表是正常态,管理员可先在 UserBase 侧开通)。
|
||||
*/
|
||||
public List<FlwApp> list(Long schId) {
|
||||
try {
|
||||
syncFromSchoolPool(schId);
|
||||
} catch (Exception e) {
|
||||
log.warn("[flow-center] 学校池同步失败,降级返回本地应用行:sch={} {}", schId, e.toString());
|
||||
}
|
||||
return appRepository.findBySchIdOrderBySortNoAscIdAsc(schId);
|
||||
}
|
||||
|
||||
/** 开关某应用是否对接流程(停用后 defSave 归属应用校验拦截新引用;存量定义不受影响) */
|
||||
@Transactional
|
||||
public FlwApp enable(Long schId, Long id, boolean enabled) {
|
||||
FlwApp app = appRepository.findById(id)
|
||||
.orElseThrow(() -> new com.yunhai.platform.apistd.BizException(
|
||||
com.yunhai.platform.utils.exception.ErrorCode.PARAM_ERROR, "应用行不存在:" + id));
|
||||
if (!app.getSchId().equals(schId)) {
|
||||
throw new com.yunhai.platform.apistd.BizException(
|
||||
com.yunhai.platform.utils.exception.ErrorCode.FORBIDDEN, "应用行不属于该学校");
|
||||
}
|
||||
app.setEnabled(enabled ? 1 : 0);
|
||||
return appRepository.save(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* 学校池 → flw_app diff 同步(新增 enabled=1 / 改名静默刷新 / 池外保留)。
|
||||
* 幂等:按 (sch_id, app_key) 唯一键 upsert。
|
||||
*/
|
||||
@Transactional
|
||||
public void syncFromSchoolPool(Long schId) {
|
||||
List<BoaAppType> pool = appClient.getSchoolApp(schId.intValue(), null);
|
||||
Map<String, BoaApp> poolApps = new HashMap<>();
|
||||
for (BoaAppType type : pool) {
|
||||
for (BoaApp app : type.getApps()) {
|
||||
if (app.getAppKey() != null) {
|
||||
poolApps.put(String.valueOf(app.getAppKey()), app);
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, FlwApp> local = new HashMap<>();
|
||||
for (FlwApp row : appRepository.findBySchIdOrderBySortNoAscIdAsc(schId)) {
|
||||
local.put(row.getAppKey(), row);
|
||||
}
|
||||
int inserted = 0;
|
||||
int refreshed = 0;
|
||||
for (Map.Entry<String, BoaApp> e : poolApps.entrySet()) {
|
||||
BoaApp poolApp = e.getValue();
|
||||
FlwApp row = local.get(e.getKey());
|
||||
if (row == null) {
|
||||
row = new FlwApp();
|
||||
row.setSchId(schId);
|
||||
row.setAppKey(e.getKey());
|
||||
row.setAppName(poolApp.getName() == null || poolApp.getName().isEmpty()
|
||||
? e.getKey() : poolApp.getName());
|
||||
row.setEnabled(1);
|
||||
row.setSortNo(poolApp.getOrderNumber() == null ? 0 : poolApp.getOrderNumber());
|
||||
appRepository.save(row);
|
||||
inserted++;
|
||||
} else if (poolApp.getName() != null && !poolApp.getName().isEmpty()
|
||||
&& !poolApp.getName().equals(row.getAppName())) {
|
||||
// 学校别名变化:静默刷新名称快照(开关不动)
|
||||
row.setAppName(poolApp.getName());
|
||||
row.setSortNo(poolApp.getOrderNumber() == null ? row.getSortNo() : poolApp.getOrderNumber());
|
||||
appRepository.save(row);
|
||||
refreshed++;
|
||||
}
|
||||
}
|
||||
if (inserted > 0 || refreshed > 0) {
|
||||
log.info("[flow-center] 学校池同步:sch={} 池内 {} 应用,新增 {} 行,刷新名 {} 行",
|
||||
schId, poolApps.size(), inserted, refreshed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public class FlowDefAdminService {
|
||||
private final FlwAssigneeMapRepository mapRepository;
|
||||
private final com.yunhai.flowcenter.repository.FlwRouterRepository routerRepository;
|
||||
private final com.yunhai.flowcenter.repository.FlwCallbackRegRepository regRepository;
|
||||
private final com.yunhai.flowcenter.repository.FlwAppRepository appRepository;
|
||||
private final FlowEngine flowEngine;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@@ -79,6 +80,12 @@ public class FlowDefAdminService {
|
||||
@Transactional
|
||||
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid, String appKey) {
|
||||
String cleanAppKey = (appKey == null || appKey.trim().isEmpty()) ? null : appKey.trim();
|
||||
// 归属应用白名单(2026-10-10 应用设置):显式传入非空时必须 ∈ flw_app(sch, enabled=1);
|
||||
// 不传(升版本继承源线)不拦。不在学校池/未开启的应用由此拦下(拍板 D2:不做存量兜底)
|
||||
if (cleanAppKey != null && !appRepository.isEnabledApp(schId, cleanAppKey)) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR,
|
||||
"归属应用未开启或不存在(" + cleanAppKey + "):请在 流程管理 > 应用设置 中开启后重试");
|
||||
}
|
||||
// JSON 先解析再落库(语法错误不进库)
|
||||
FlowDefModel parsed = FlowDefModel.parse(objectMapper, contentJson);
|
||||
if (code == null || code.trim().isEmpty() || name == null || name.trim().isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 2026-10-10 四议题·应用设置:流程对接应用白名单表
|
||||
-- ddl-auto=update 会自动建表;本脚本为生产备案(生产建议停自动 DDL 后手工执行)
|
||||
CREATE TABLE IF NOT EXISTS `flw_app` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`sch_id` BIGINT NOT NULL COMMENT '租户键',
|
||||
`app_key` VARCHAR(64) NOT NULL COMMENT '子应用 appKey(UserBase application_info.app_key 字符串形态)',
|
||||
`app_name` VARCHAR(128) NOT NULL COMMENT '应用名快照(学校池返回值,含学校别名;同步刷新)',
|
||||
`enabled` INT NOT NULL DEFAULT 1 COMMENT '1=对接流程(白名单);0=停用(defSave 校验拦截)',
|
||||
`sort_no` INT NULL COMMENT '展示排序(学校池 orderNumber 快照)',
|
||||
`create_time` DATETIME(6) NULL,
|
||||
`update_time` DATETIME(6) NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_flw_app_sch_key` (`sch_id`, `app_key`),
|
||||
KEY `idx_flw_app_sch_enabled` (`sch_id`, `enabled`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '流程对接应用白名单(应用设置)';
|
||||
@@ -0,0 +1,56 @@
|
||||
-- =====================================================================
|
||||
-- flow-center 四议题(2026-10-10):UserBase app_module 变更
|
||||
-- ① 新增 107「应用设置」(/admin/app-setting,应用管理 + 科室管理两个 tab)
|
||||
-- ② 下线 104「办理人映射」(/admin/maps 由 应用设置-科室管理 承载,Part E 上线时执行 ②段)
|
||||
-- 纪律:AI 不代跑,dev / prod 由人工执行;MySQL 5.7 兼容;幂等可重复执行。
|
||||
-- 前置:flow-center 前端(含 AppSetting.vue 路由)已发布,否则菜单点进去 404。
|
||||
-- =====================================================================
|
||||
|
||||
-- 步骤 0:校验身份链(app_module.app_id 存的是 application_info 主键,非 app_key)
|
||||
SELECT id AS app_id_pk, app_key, name
|
||||
FROM application_info
|
||||
WHERE id = 100011 OR app_key = 100019;
|
||||
-- 期望同一行(id=100011 且 app_key=100019);不同则把 @app_id 改成 app_key=100019 那行的 id。
|
||||
|
||||
SET @app_id = 100011;
|
||||
SET @operator = 0;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- ① 新增 107 应用设置(幂等:不存在才插;path 与 routes.ts 逐字一致)
|
||||
-- ---------------------------------------------------------------------
|
||||
INSERT INTO app_module
|
||||
(app_id, parent_id, name, custom_id, path, icon, level, sort_no, create_user, create_time)
|
||||
SELECT @app_id, NULL, '应用设置', '107', '/admin/app-setting', NULL, 1, 107, @operator, NOW()
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM app_module m WHERE m.app_id = @app_id AND m.custom_id = '107'
|
||||
);
|
||||
|
||||
-- ①授权(角色路径,@role_id 换成实际选中的启用角色;见 flow-center接入UserBase菜单.sql §3.1 查询)
|
||||
SET @role_id = 0; -- ← 改这里
|
||||
INSERT INTO sys_app_role_module_rel (role_id, module_id, create_user, create_time)
|
||||
SELECT @role_id, m.id, @operator, NOW()
|
||||
FROM app_module m
|
||||
WHERE m.app_id = @app_id AND m.custom_id = '107'
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_app_role_module_rel r
|
||||
WHERE r.role_id = @role_id AND r.module_id = m.id);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- ② 下线 104 办理人映射(Part E 科室管理上线时执行;先删授权再删模块行)
|
||||
-- 如需保留回滚能力,可改为 UPDATE app_module SET sort_no = 9999 隐藏(菜单仍下发)。
|
||||
-- ---------------------------------------------------------------------
|
||||
-- DELETE r FROM sys_app_role_module_rel r
|
||||
-- JOIN app_module m ON m.id = r.module_id
|
||||
-- WHERE m.app_id = @app_id AND m.custom_id = '104';
|
||||
-- DELETE p FROM sys_group_module_perm p
|
||||
-- JOIN app_module m ON m.id = p.module_id
|
||||
-- WHERE m.app_id = @app_id AND m.custom_id = '104';
|
||||
-- DELETE m FROM app_module m
|
||||
-- WHERE m.app_id = @app_id AND m.custom_id = '104';
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 验收
|
||||
-- ---------------------------------------------------------------------
|
||||
SELECT id, app_id, custom_id, name, path, level, sort_no
|
||||
FROM app_module
|
||||
WHERE app_id = @app_id
|
||||
ORDER BY CAST(custom_id AS UNSIGNED);
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.yunhai.flowcenter.service;
|
||||
|
||||
import com.yunhai.flowcenter.entity.FlwApp;
|
||||
import com.yunhai.flowcenter.repository.FlwAppRepository;
|
||||
import com.yunhai.platform.openapi.base.OpenApiAppClient;
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 应用设置·应用管理单测(2026-10-10):学校池 diff 同步(新增 enabled=1/改名刷新/池外保留)、
|
||||
* UserBase 不可达降级、开关与跨校守卫。口径拍板:D2 只做最优实现(不做存量 appKey 兜底)、
|
||||
* D3 clientType=null 不过滤端、D7 学校组单点池。
|
||||
*/
|
||||
class FlowAppAdminServiceTest {
|
||||
|
||||
private static final Long SCH = 990001L;
|
||||
|
||||
private FlwAppRepository appRepository;
|
||||
private OpenApiAppClient appClient;
|
||||
private FlowAppAdminService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
appRepository = mock(FlwAppRepository.class);
|
||||
appClient = mock(OpenApiAppClient.class);
|
||||
service = new FlowAppAdminService(appRepository, appClient);
|
||||
when(appRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
}
|
||||
|
||||
private BoaAppType type(BoaApp... apps) {
|
||||
BoaAppType t = new BoaAppType();
|
||||
t.setId(2);
|
||||
t.setName("办公管理");
|
||||
t.setApps(Arrays.asList(apps));
|
||||
return t;
|
||||
}
|
||||
|
||||
private BoaApp app(Integer appKey, String name, Integer orderNumber) {
|
||||
BoaApp a = new BoaApp();
|
||||
a.setAppKey(appKey);
|
||||
a.setName(name);
|
||||
a.setOrderNumber(orderNumber);
|
||||
return a;
|
||||
}
|
||||
|
||||
private FlwApp row(String appKey, String name, int enabled) {
|
||||
FlwApp r = new FlwApp();
|
||||
r.setId((long) appKey.hashCode());
|
||||
r.setSchId(SCH);
|
||||
r.setAppKey(appKey);
|
||||
r.setAppName(name);
|
||||
r.setEnabled(enabled);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Test
|
||||
void sync_池内新应用_插入且默认启用() {
|
||||
when(appClient.getSchoolApp(990001, null)).thenReturn(Collections.singletonList(
|
||||
type(app(100002, "通知公告", 1))));
|
||||
when(appRepository.findBySchIdOrderBySortNoAscIdAsc(SCH)).thenReturn(Collections.emptyList());
|
||||
|
||||
service.syncFromSchoolPool(SCH);
|
||||
|
||||
ArgumentCaptor<FlwApp> cap = ArgumentCaptor.forClass(FlwApp.class);
|
||||
verify(appRepository, times(1)).save(cap.capture());
|
||||
assertEquals("100002", cap.getValue().getAppKey());
|
||||
assertEquals("通知公告", cap.getValue().getAppName());
|
||||
assertEquals(1, cap.getValue().getEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sync_改名静默刷新_开关不动_池外行保留() {
|
||||
FlwApp renamed = row("100002", "通知公告", 1);
|
||||
FlwApp untouchedEnabled = row("100008", "请假", 0); // 池外且已停用:保留不动
|
||||
FlwApp untouchedName = row("100010", "旧名", 1); // 池外:名称也不动
|
||||
when(appClient.getSchoolApp(990001, null)).thenReturn(Collections.singletonList(
|
||||
type(app(100002, "通知公告(校内别名)", 9))));
|
||||
when(appRepository.findBySchIdOrderBySortNoAscIdAsc(SCH))
|
||||
.thenReturn(Arrays.asList(renamed, untouchedEnabled, untouchedName));
|
||||
|
||||
service.syncFromSchoolPool(SCH);
|
||||
|
||||
verify(appRepository, times(1)).save(any(FlwApp.class));
|
||||
assertEquals("通知公告(校内别名)", renamed.getAppName());
|
||||
assertEquals(1, renamed.getEnabled());
|
||||
assertEquals("旧名", untouchedName.getAppName());
|
||||
assertEquals(0, untouchedEnabled.getEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void list_UserBase不可达_降级返回本地行() {
|
||||
when(appClient.getSchoolApp(any(), any())).thenThrow(new RuntimeException("connect timeout"));
|
||||
when(appRepository.findBySchIdOrderBySortNoAscIdAsc(SCH))
|
||||
.thenReturn(Collections.singletonList(row("100002", "通知公告", 1)));
|
||||
|
||||
List<FlwApp> rows = service.list(SCH);
|
||||
|
||||
assertEquals(1, rows.size());
|
||||
assertEquals("100002", rows.get(0).getAppKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void list_正常链路_先同步再返回() {
|
||||
when(appClient.getSchoolApp(990001, null)).thenReturn(Collections.emptyList());
|
||||
when(appRepository.findBySchIdOrderBySortNoAscIdAsc(SCH)).thenReturn(Collections.emptyList());
|
||||
assertEquals(0, service.list(SCH).size());
|
||||
verify(appClient, times(1)).getSchoolApp(990001, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void enable_切换与守卫() {
|
||||
FlwApp r = row("100002", "通知公告", 1);
|
||||
when(appRepository.findById(r.getId())).thenReturn(java.util.Optional.of(r));
|
||||
|
||||
service.enable(SCH, r.getId(), false);
|
||||
assertEquals(0, r.getEnabled());
|
||||
|
||||
service.enable(SCH, r.getId(), true);
|
||||
assertEquals(1, r.getEnabled());
|
||||
|
||||
FlwApp other = row("100002", "x", 1);
|
||||
other.setSchId(2L);
|
||||
when(appRepository.findById(other.getId())).thenReturn(java.util.Optional.of(other));
|
||||
assertThrows(com.yunhai.platform.apistd.BizException.class, () -> service.enable(SCH, other.getId(), true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sync_池为空_零写入() {
|
||||
when(appClient.getSchoolApp(990001, null)).thenReturn(Collections.emptyList());
|
||||
when(appRepository.findBySchIdOrderBySortNoAscIdAsc(SCH)).thenReturn(Collections.emptyList());
|
||||
|
||||
service.syncFromSchoolPool(SCH);
|
||||
|
||||
verify(appRepository, never()).save(any());
|
||||
assertTrue(service.list(SCH).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -37,18 +37,23 @@ class FlowDefAdminServiceSaveTest {
|
||||
private static final String JSON = "{\"nodes\":[],\"edges\":[]}";
|
||||
|
||||
private FlwDefRepository defRepository;
|
||||
private com.yunhai.flowcenter.repository.FlwAppRepository appRepository;
|
||||
private FlowDefAdminService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
defRepository = mock(FlwDefRepository.class);
|
||||
appRepository = mock(com.yunhai.flowcenter.repository.FlwAppRepository.class);
|
||||
service = new FlowDefAdminService(defRepository,
|
||||
mock(FlwAssigneeMapRepository.class),
|
||||
mock(FlwRouterRepository.class),
|
||||
mock(com.yunhai.flowcenter.repository.FlwCallbackRegRepository.class),
|
||||
appRepository,
|
||||
mock(FlowEngine.class),
|
||||
new ObjectMapper());
|
||||
when(defRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
// 归属应用白名单:非空 appKey 一律放行(校验拦截语义由专门用例覆盖)
|
||||
when(appRepository.isEnabledApp(any(), any())).thenReturn(true);
|
||||
}
|
||||
|
||||
private FlwDef def(long id, String flowGuid, String code, int version, int status) {
|
||||
@@ -168,4 +173,23 @@ class FlowDefAdminServiceSaveTest {
|
||||
() -> service.save(SCH, 9L, "work-leave", "请假", JSON, null, null));
|
||||
assertTrue(e.getMessage().contains("保存冲突"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void appKey不在启用白名单_保存被拦() {
|
||||
when(appRepository.isEnabledApp(SCH, "999999")).thenReturn(false);
|
||||
BizException e = assertThrows(BizException.class,
|
||||
() -> service.save(SCH, null, "work-leave", "请假", JSON, null, "999999"));
|
||||
assertTrue(e.getMessage().contains("应用设置"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void appKey不传_白名单校验不触发() {
|
||||
// 升版本继承源线(cleanAppKey=null)不查白名单——存量线升版本不受切换影响
|
||||
when(defRepository.findById(9L)).thenReturn(Optional.of(def(9L, "G", "work-leave", 4, FlwEnums.DefStatus.PUBLISHED.getCode())));
|
||||
when(defRepository.maxVersionByGuid(SCH, "G")).thenReturn(4);
|
||||
when(defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(SCH, "G")).thenReturn(Optional.empty());
|
||||
FlwDef saved = service.save(SCH, 9L, "work-leave", "请假", JSON, null, null);
|
||||
org.mockito.Mockito.verify(appRepository, org.mockito.Mockito.never()).isEnabledApp(any(), any());
|
||||
assertNull(saved.getAppKey());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +483,8 @@ class FlowDiagramServiceTest {
|
||||
mock(com.yunhai.flowcenter.service.FlowRouterAdminService.class),
|
||||
mock(FlwInstanceRepository.class), diagramService,
|
||||
mock(com.yunhai.flowcenter.service.DiagramImageService.class),
|
||||
new ObjectMapper());
|
||||
new ObjectMapper(),
|
||||
mock(com.yunhai.flowcenter.service.FlowAppAdminService.class));
|
||||
}
|
||||
|
||||
private BoaUser user(int id, Integer type, Integer schId, Integer... roleIds) {
|
||||
|
||||
Reference in New Issue
Block a user