build / build-push (push) Failing after 55s
在 b369527 之上增量移植 FL2.0 后端能力,不改既有线级停发/指派预览/流程图入口:
- 部门目录:FlowDepartmentController(/admin/department/{groups,options,resolve,members})
+ UserBaseDepartmentDirectory(先按会话校域解析引用,再读成员;目录不可用 fail-closed)
- 退回重提 D14:POST /oapi/flow/instance/{instanceId}/resubmit,仅原提交人 CAS 路径放行,
OAPI 侧 submitterId 路径继续 fail-closed
- 授权管理:/api/flow/admin/usage-grant/{schoolList,summary}(校域取管理员会话,无 schId 入参)
- 来源主体:解析器派生 ub-app:{applicationInfo.id},不再读取 UserBase 的 sourcePrincipalId 字段
- 回调来源策略:SELF 身份强校验;NULL 来源不广播;复合幂等键 (event_id,reg_id)
- 测试夹具:许可/主体/开通桩按新契约修正,FlowEngineIT/FormalApplyCommandIT/
CallbackDispatchIT/OapiFlowContractIT 共 117 例在真实 MySQL 8 通过
不含:FlSourceAuthorizationTest(其断言的两个 Repository 派生方法属另一实现线)
424 lines
19 KiB
Java
424 lines
19 KiB
Java
package com.yunhai.flowcenter.service;
|
||
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
import com.yunhai.flowcenter.engine.AssigneeRef;
|
||
import com.yunhai.flowcenter.engine.AssigneeResolver;
|
||
import com.yunhai.flowcenter.engine.DefValidator;
|
||
import com.yunhai.flowcenter.engine.FlowDefModel;
|
||
import com.yunhai.platform.apistd.BizException;
|
||
import com.yunhai.platform.openapi.base.OpenApiGroupClient;
|
||
import com.yunhai.platform.openapi.base.OpenApiUserClient;
|
||
import com.yunhai.platform.openapi.base.model.DepartmentEntry;
|
||
import com.yunhai.platform.openapi.base.model.DepartmentRef;
|
||
import com.yunhai.platform.utils.model.BoaUser;
|
||
import org.junit.jupiter.api.BeforeEach;
|
||
import org.junit.jupiter.api.DisplayName;
|
||
import org.junit.jupiter.api.Test;
|
||
import org.mockito.ArgumentCaptor;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.List;
|
||
import java.util.Set;
|
||
|
||
import static org.assertj.core.api.Assertions.assertThat;
|
||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||
import static org.mockito.ArgumentMatchers.any;
|
||
import static org.mockito.ArgumentMatchers.anyInt;
|
||
import static org.mockito.ArgumentMatchers.anyString;
|
||
import static org.mockito.ArgumentMatchers.eq;
|
||
import static org.mockito.Mockito.mock;
|
||
import static org.mockito.Mockito.never;
|
||
import static org.mockito.Mockito.verify;
|
||
import static org.mockito.Mockito.when;
|
||
|
||
/**
|
||
* UserBase 部门权威化与审批主体规则(工作包 C,2026-10-29)。
|
||
*
|
||
* <p>测试替身边界:{@link OpenApiGroupClient}/{@link OpenApiUserClient} 按 UserBase
|
||
* {@code /oapi/departments/**} 的真实契约打桩(响应形状=SDK 的 DepartmentEntry/BoaUser),
|
||
* <b>这不是跨服务联调</b>——真实 UserBase 可达性、SYSTEM/CUSTOM 数据齐备性由后续 WorkBuddy 验证。</p>
|
||
*
|
||
* <p>钉住的红线:</p>
|
||
* <ul>
|
||
* <li>部门是<b>审批授权主体</b>,不是"前端筛出来的一堆人";解析出的候选仍逐人过 D11 资格终审;</li>
|
||
* <li>一个用户可属多个部门 → 多部门引用按用户去重,只发一条待办;</li>
|
||
* <li>目录不可用一律抛错,<b>不静默退回 flw_dept</b>,也不把空候选当成功;</li>
|
||
* <li>展示口径(显示人员/显示部门)与审批主体分开配置并独立生效。</li>
|
||
* </ul>
|
||
*/
|
||
@DisplayName("UserBase 部门主体解析 + 展示规则(C1/C2/C3)")
|
||
class FlDepartmentSubjectTest {
|
||
@Test void foreignSchoolReferenceCannotExposeMembers() {
|
||
assertThatThrownBy(() -> directory.resolve(SCH,
|
||
Arrays.asList(new DepartmentRef((int) SCH + 1, SYSTEM, 42))))
|
||
.isInstanceOf(BizException.class).hasMessageContaining("学校");
|
||
DepartmentEntry foreign = entry(SYSTEM, 42, true, false);
|
||
foreign.setSchoolId((int) SCH + 1); foreign.setName("foreign");
|
||
when(groupClient.resolveDepartments(any())).thenReturn(Arrays.asList(foreign));
|
||
assertThatThrownBy(() -> directory.membersAtSchool(SCH, SYSTEM, 42, "select"))
|
||
.isInstanceOf(BizException.class).hasMessageContaining("学校");
|
||
org.mockito.Mockito.verify(groupClient, org.mockito.Mockito.never()).getDepartmentMembers(any(), any(), any());
|
||
}
|
||
|
||
private static final long SCH = 5L;
|
||
private static final String SYSTEM = "SYSTEM";
|
||
private static final String CUSTOM = "CUSTOM";
|
||
|
||
private OpenApiGroupClient groupClient;
|
||
private OpenApiUserClient userClient;
|
||
private UserBaseDepartmentDirectory directory;
|
||
private AssigneeResolver resolver;
|
||
private final ObjectMapper mapper = new ObjectMapper();
|
||
|
||
@BeforeEach
|
||
void setUp() {
|
||
groupClient = mock(OpenApiGroupClient.class);
|
||
userClient = mock(OpenApiUserClient.class);
|
||
directory = new UserBaseDepartmentDirectory(groupClient);
|
||
when(groupClient.resolveDepartments(any())).thenAnswer(call -> {
|
||
java.util.List<DepartmentRef> refs = call.getArgument(0);
|
||
if (refs == null) return java.util.Collections.emptyList();
|
||
java.util.List<DepartmentEntry> rows = new java.util.ArrayList<>();
|
||
for (DepartmentRef ref : refs) {
|
||
DepartmentEntry e = entry(ref.getDepartmentType(), ref.getDepartmentId(), true, false);
|
||
e.setSchoolId(ref.getSchoolId()); e.setName("department"); rows.add(e);
|
||
}
|
||
return rows;
|
||
});
|
||
resolver = new AssigneeResolver(mock(FlowDeptAdminService.class), userClient);
|
||
resolver.setDepartmentDirectory(directory);
|
||
givenEligibleUsers(101, 102, 103, 104);
|
||
}
|
||
|
||
// ==================== C1 权威目录接入 ====================
|
||
|
||
@Test
|
||
@DisplayName("DEPT 主体从 UserBase 目录解析候选人(scope=select 严档,不读 flw_dept_member)")
|
||
void departmentSubjectResolvesFromUserBase() {
|
||
when(groupClient.getDepartmentMembers(SYSTEM, 42, "select"))
|
||
.thenReturn(Arrays.asList(user(101, "张老师"), user(102, "李老师")));
|
||
|
||
List<AssigneeRef> refs = resolver.resolve(SCH, approvalNode(deptAssignee(SYSTEM, 42)));
|
||
|
||
assertThat(refs).extracting(AssigneeRef::getUserId).containsExactly(101, 102);
|
||
verify(groupClient).getDepartmentMembers(SYSTEM, 42, "select");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("同一用户归属多个被选部门:只产出一条待办(多组归属不是模型禁忌)")
|
||
void overlappingMembershipDeduplicatesUser() {
|
||
when(groupClient.getDepartmentMembers(SYSTEM, 42, "select"))
|
||
.thenReturn(Arrays.asList(user(101, "张老师"), user(102, "李老师")));
|
||
when(groupClient.getDepartmentMembers(CUSTOM, 7, "select"))
|
||
.thenReturn(Arrays.asList(user(102, "李老师"), user(103, "王老师")));
|
||
|
||
FlowDefModel.Assignee assignee = new FlowDefModel.Assignee();
|
||
assignee.type = FlowDefModel.ASSIGNEE_DEPT;
|
||
assignee.deptRefs = new ArrayList<>(Arrays.asList(deptRef(SYSTEM, 42), deptRef(CUSTOM, 7)));
|
||
|
||
List<AssigneeRef> refs = resolver.resolve(SCH, approvalNode(assignee));
|
||
|
||
assertThat(refs).extracting(AssigneeRef::getUserId).containsExactly(101, 102, 103);
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("目录不可用:拒绝并说明原因,绝不退回 FL 本地科室(双权威 fallback 已禁)")
|
||
void directoryOutageFailsClosed() {
|
||
when(groupClient.getDepartmentMembers(anyString(), anyInt(), anyString()))
|
||
.thenThrow(new RuntimeException("userbase unreachable"));
|
||
FlowDefModel.Node node = approvalNode(deptAssignee(SYSTEM, 42));
|
||
|
||
assertThatThrownBy(() -> resolver.resolve(SCH, node))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("不可用");
|
||
// 未装配目录通道同样拒绝,而不是回读本地表
|
||
AssigneeResolver unwired = new AssigneeResolver(mock(FlowDeptAdminService.class), userClient);
|
||
assertThatThrownBy(() -> unwired.resolve(SCH, node))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("未装配");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("部门解析出 0 人:按「无可用办理人」拒绝,不把空候选当成功")
|
||
void emptyDepartmentIsRejected() {
|
||
when(groupClient.getDepartmentMembers(SYSTEM, 42, "select")).thenReturn(new ArrayList<>());
|
||
|
||
assertThatThrownBy(() -> resolver.resolve(SCH, approvalNode(deptAssignee(SYSTEM, 42))))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("无可用办理人");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("部门里全是禁用/跨校账号:整体拒绝并点名原因(存在≠有资格,D11)")
|
||
void ineligibleMembersAreFilteredAndEmptyIsRejected() {
|
||
when(groupClient.getDepartmentMembers(SYSTEM, 42, "select"))
|
||
.thenReturn(Arrays.asList(user(101, "张老师"), user(102, "李老师")));
|
||
when(userClient.getUserById("101")).thenReturn(disabled(101));
|
||
when(userClient.getUserById("102")).thenReturn(otherSchool(102));
|
||
|
||
assertThatThrownBy(() -> resolver.resolve(SCH, approvalNode(deptAssignee(SYSTEM, 42))))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("不具备办理资格");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("部分成员不合格:剔除不合格者继续,不因个别人卡死整条流程")
|
||
void partiallyIneligibleKeepsEligibleOnes() {
|
||
when(groupClient.getDepartmentMembers(SYSTEM, 42, "select"))
|
||
.thenReturn(Arrays.asList(user(101, "张老师"), user(102, "李老师")));
|
||
when(userClient.getUserById("102")).thenReturn(disabled(102));
|
||
|
||
List<AssigneeRef> refs = resolver.resolve(SCH, approvalNode(deptAssignee(SYSTEM, 42)));
|
||
|
||
assertThat(refs).extracting(AssigneeRef::getUserId).containsExactly(101);
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("部门引用缺类型消歧位:拒绝(同值 id 在 SYSTEM/CUSTOM 不等价)")
|
||
void untypedReferenceRejected() {
|
||
FlowDefModel.Assignee assignee = new FlowDefModel.Assignee();
|
||
assignee.type = FlowDefModel.ASSIGNEE_DEPT;
|
||
FlowDefModel.DeptRef bad = new FlowDefModel.DeptRef();
|
||
bad.departmentId = 42;
|
||
assignee.deptRefs = new ArrayList<>(Arrays.asList(bad));
|
||
|
||
assertThatThrownBy(() -> resolver.resolve(SCH, approvalNode(assignee)))
|
||
.isInstanceOf(BizException.class);
|
||
|
||
assertThatThrownBy(() -> directory.members("OTHER", 42, "select"))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("SYSTEM/CUSTOM");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("历史口径与选择口径分开:resolve-batch 取停用部门名称,select 不返回停用项")
|
||
void historyAndSelectionScopesAreSeparate() {
|
||
when(groupClient.getDepartmentMembers(CUSTOM, 9, "history"))
|
||
.thenReturn(Arrays.asList(user(103, "王老师")));
|
||
assertThat(directory.members(CUSTOM, 9, "history"))
|
||
.extracting(BoaUser::getId).containsExactly(103);
|
||
|
||
DepartmentEntry deleted = new DepartmentEntry();
|
||
deleted.setDepartmentType(CUSTOM);
|
||
deleted.setDepartmentId(9);
|
||
deleted.setName("已撤销的课题组");
|
||
deleted.setSelectable(false);
|
||
deleted.setDeleted(true);
|
||
deleted.setSchoolId((int) SCH);
|
||
when(groupClient.resolveDepartments(any())).thenReturn(Arrays.asList(deleted));
|
||
|
||
List<DepartmentEntry> resolved = directory.resolve(SCH,
|
||
Arrays.asList(new DepartmentRef(null, CUSTOM, 9)));
|
||
|
||
assertThat(resolved).hasSize(1);
|
||
assertThat(resolved.get(0).getName()).isEqualTo("已撤销的课题组");
|
||
assertThat(resolved.get(0).getSelectable()).isFalse();
|
||
// 学校范围由服务端会话派生并补进引用,不接受前端传别校 id
|
||
assertThat(resolved.get(0).getSchoolId()).isEqualTo((int) SCH);
|
||
ArgumentCaptor<List<DepartmentRef>> sent = ArgumentCaptor.forClass(List.class);
|
||
verify(groupClient).resolveDepartments(sent.capture());
|
||
assertThat(sent.getValue().get(0).getSchoolId()).as("校域由服务端会话派生并补进引用").isEqualTo((int) SCH);
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("发布校验白名单来自目录 selectable 项,停用部门不可被新选中")
|
||
void selectableKeysOnlyCatalogSelectable() {
|
||
DepartmentEntry live = entry(SYSTEM, 42, true, false);
|
||
DepartmentEntry stopped = entry(SYSTEM, 43, false, false);
|
||
DepartmentEntry softDeleted = entry(CUSTOM, 9, true, true);
|
||
when(groupClient.getDepartments(eq((int) SCH), any(), any(), any()))
|
||
.thenReturn(Arrays.asList(live, stopped, softDeleted));
|
||
|
||
assertThat(directory.selectableKeys(SCH)).containsExactly("SYSTEM:42");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("空引用批量解析本地短路,不出站也不报错")
|
||
void emptyResolveBatchShortCircuits() {
|
||
assertThat(directory.resolve(SCH, new ArrayList<>())).isEmpty();
|
||
verify(groupClient, never()).resolveDepartments(any());
|
||
}
|
||
|
||
// ==================== C3 展示规则 ====================
|
||
|
||
@Test
|
||
@DisplayName("显示部门:任务节点名取独立展示名;未配展示字段=沿用节点名(历史定义零变化)")
|
||
void displayLabelDrivesTaskNodeName() throws Exception {
|
||
FlowDefModel model = FlowDefModel.parse(mapper, "{\"nodes\":[],\"edges\":[]}");
|
||
FlowDefModel.Node legacy = approvalNode(deptAssignee(SYSTEM, 42));
|
||
assertThat(legacy.displayName()).isEqualTo("二级审批");
|
||
|
||
legacy.displayMode = FlowDefModel.DISPLAY_MODE_DEPT;
|
||
legacy.displayLabel = "校长室";
|
||
assertThat(legacy.displayName()).isEqualTo("校长室");
|
||
|
||
// 按人员展示时不受 displayLabel 影响(只展示本节点自己配置/解析到的人)
|
||
legacy.displayMode = FlowDefModel.DISPLAY_MODE_USER;
|
||
assertThat(legacy.displayName()).isEqualTo("二级审批");
|
||
assertThat(model).isNotNull();
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("发布校验:显示部门必须填展示名;未知 displayMode 取值拒绝")
|
||
void publishRejectsMissingDisplayLabel() {
|
||
FlowDefModel model = twoLevelModel(deptAssignee(SYSTEM, 42));
|
||
model.nodes.get(1).displayMode = FlowDefModel.DISPLAY_MODE_DEPT;
|
||
|
||
assertThatThrownBy(() -> DefValidator.validate(model, null, null,
|
||
new java.util.HashSet<>(Arrays.asList("SYSTEM:42"))))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("展示名称");
|
||
|
||
model.nodes.get(1).displayMode = "BOTH";
|
||
model.nodes.get(1).displayLabel = "校长室";
|
||
assertThatThrownBy(() -> DefValidator.validate(model, null, null,
|
||
new java.util.HashSet<>(Arrays.asList("SYSTEM:42"))))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("displayMode");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("发布校验:部门不在本校可选目录内(跨校/停用/已删)即拦;引用齐备则放行")
|
||
void publishChecksDepartmentAgainstDirectoryWhitelist() {
|
||
FlowDefModel model = twoLevelModel(deptAssignee(SYSTEM, 42));
|
||
|
||
DefValidator.validate(model, null, null, new java.util.HashSet<>(Arrays.asList("SYSTEM:42")));
|
||
|
||
assertThatThrownBy(() -> DefValidator.validate(model, null, null,
|
||
new java.util.HashSet<>(Arrays.asList("SYSTEM:99"))))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("不在本校可选部门目录内");
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("会签/或签对部门主体与人员主体同样可配(D07/D08 主体定候选,策略定完成条件)")
|
||
void countersignConfigurableForBothSubjects() {
|
||
Set<String> whitelist = new java.util.HashSet<>(Arrays.asList("SYSTEM:42"));
|
||
// 部门主体 + 或签:合法(会签/或签不是只对部门生效)
|
||
FlowDefModel deptModel = twoLevelModel(deptAssignee(SYSTEM, 42));
|
||
deptModel.nodes.get(2).countersign = FlowDefModel.COUNTERSIGN_ANY;
|
||
DefValidator.validate(deptModel, null, null, whitelist);
|
||
assertThat(deptModel.nodes.get(2).countersign).isEqualTo(FlowDefModel.COUNTERSIGN_ANY);
|
||
|
||
// 人员主体 + 或签:同样合法
|
||
FlowDefModel usersModel = twoLevelModel(usersAssignee(101, 102));
|
||
usersModel.nodes.get(2).countersign = FlowDefModel.COUNTERSIGN_ANY;
|
||
DefValidator.validate(usersModel, null, null, whitelist);
|
||
|
||
// 非法策略值:两类主体都拦
|
||
deptModel.nodes.get(2).countersign = "MOST";
|
||
assertThatThrownBy(() -> DefValidator.validate(deptModel, null, null, whitelist))
|
||
.isInstanceOf(BizException.class)
|
||
.hasMessageContaining("countersign");
|
||
}
|
||
|
||
// ==================== fixtures ====================
|
||
|
||
private void givenEligibleUsers(int... ids) {
|
||
for (int id : ids) {
|
||
when(userClient.getUserById(String.valueOf(id))).thenReturn(sameSchool(id, "教师" + id));
|
||
}
|
||
}
|
||
|
||
private FlowDefModel.Node approvalNode(FlowDefModel.Assignee assignee) {
|
||
FlowDefModel.Node node = new FlowDefModel.Node();
|
||
node.id = "n2";
|
||
node.type = FlowDefModel.TYPE_APPROVAL;
|
||
node.name = "二级审批";
|
||
node.assignee = assignee;
|
||
return node;
|
||
}
|
||
|
||
private FlowDefModel twoLevelModel(FlowDefModel.Assignee assignee) {
|
||
FlowDefModel model = FlowDefModel.parse(mapper, "{\"start\":\"start\",\"nodes\":["
|
||
+ "{\"id\":\"start\",\"type\":\"START\"},"
|
||
+ "{\"id\":\"n1\",\"type\":\"APPROVAL\",\"name\":\"一级\",\"assignee\":{\"type\":\"USERS\",\"userIds\":[101]}},"
|
||
+ "{\"id\":\"end\",\"type\":\"END\"}],\"edges\":["
|
||
+ "{\"from\":\"start\",\"to\":\"n1\"},{\"from\":\"n1\",\"to\":\"end\"}]}");
|
||
FlowDefModel.Node deptNode = new FlowDefModel.Node();
|
||
deptNode.id = "n2";
|
||
deptNode.type = FlowDefModel.TYPE_APPROVAL;
|
||
deptNode.name = "二级审批";
|
||
deptNode.assignee = assignee;
|
||
model.nodes.add(2, deptNode);
|
||
model.edges.add(edge("n1", "n2"));
|
||
model.edges.add(edge("n2", "end"));
|
||
// 一级→end 的原边由 n1→n2 取代
|
||
model.edges.removeIf(e -> "n1".equals(e.from) && "end".equals(e.to));
|
||
return model;
|
||
}
|
||
|
||
private FlowDefModel.Edge edge(String from, String to) {
|
||
FlowDefModel.Edge e = new FlowDefModel.Edge();
|
||
e.from = from;
|
||
e.to = to;
|
||
return e;
|
||
}
|
||
|
||
private FlowDefModel.Assignee deptAssignee(String type, int id) {
|
||
FlowDefModel.Assignee assignee = new FlowDefModel.Assignee();
|
||
assignee.type = FlowDefModel.ASSIGNEE_DEPT;
|
||
assignee.deptRefs = new ArrayList<>(Arrays.asList(deptRef(type, id)));
|
||
return assignee;
|
||
}
|
||
|
||
private FlowDefModel.Assignee usersAssignee(int... ids) {
|
||
FlowDefModel.Assignee assignee = new FlowDefModel.Assignee();
|
||
assignee.type = FlowDefModel.ASSIGNEE_USERS;
|
||
List<Integer> list = new ArrayList<>();
|
||
for (int id : ids) {
|
||
list.add(id);
|
||
}
|
||
assignee.userIds = list;
|
||
return assignee;
|
||
}
|
||
|
||
private FlowDefModel.DeptRef deptRef(String type, int id) {
|
||
FlowDefModel.DeptRef ref = new FlowDefModel.DeptRef();
|
||
ref.departmentType = type;
|
||
ref.departmentId = id;
|
||
return ref;
|
||
}
|
||
|
||
private DepartmentEntry entry(String type, int id, boolean enabled, boolean deleted) {
|
||
DepartmentEntry e = new DepartmentEntry();
|
||
e.setSchoolId((int) SCH);
|
||
e.setDepartmentType(type);
|
||
e.setDepartmentId(id);
|
||
e.setName(type + "-" + id);
|
||
e.setEnabled(enabled);
|
||
e.setDeleted(deleted);
|
||
e.setSelectable(enabled && !deleted);
|
||
return e;
|
||
}
|
||
|
||
private BoaUser user(int id, String name) {
|
||
return sameSchool(id, name);
|
||
}
|
||
|
||
private BoaUser sameSchool(int id, String name) {
|
||
BoaUser u = new BoaUser();
|
||
u.setId(id);
|
||
u.setName(name);
|
||
u.setStatus(1);
|
||
com.yunhai.platform.utils.model.BoaGroup school = new com.yunhai.platform.utils.model.BoaGroup();
|
||
school.setId((int) SCH);
|
||
u.setSchool(school);
|
||
return u;
|
||
}
|
||
|
||
private BoaUser disabled(int id) {
|
||
BoaUser u = sameSchool(id, "停用账号");
|
||
u.setStatus(0);
|
||
return u;
|
||
}
|
||
|
||
private BoaUser otherSchool(int id) {
|
||
BoaUser u = sameSchool(id, "他校账号");
|
||
u.getSchool().setId((int) SCH + 1);
|
||
return u;
|
||
}
|
||
|
||
}
|