style(@vben/docs): markdownlint 自动格式化设计稿
lint-md 对上一条设计稿做了表格管道对齐和多空格归一化, 收进单独提交保持每次提交可 diff。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
- Generated by /office-hours on 2026-04-23
|
||||
- Frontend branch: `feat/multi-tenant-project`(yudao-ui-admin-vben)
|
||||
- Backend branch: `feat/multi-tenant`(aiot-platform-cloud)
|
||||
- Backend branch: `feat/multi-tenant`(aiot-platform-cloud)
|
||||
- Status: APPROVED
|
||||
- Mode: 内部项目(双端联动)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
### 后端(aiot-platform-cloud · `viewsh-module-system`)已就位
|
||||
|
||||
| 组件 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| --- | --- | --- |
|
||||
| `system_project` 表 | `sql/mysql/project/01-create-tables.sql` | 含租户隔离、`CODE=DEFAULT` 约定 |
|
||||
| `system_user_project` 表 | 同上 | 中间表 `(user_id, project_id)` 唯一约束 + `tenant_id` |
|
||||
| `ProjectDO` / `UserProjectDO` | `dal/dataobject/project/` | UserProjectDO 只有二元组字段 |
|
||||
@@ -38,7 +38,7 @@
|
||||
### 前端(yudao-ui-admin-vben · `apps/web-antd`)已就位
|
||||
|
||||
| 组件 | 路径 |
|
||||
|---|---|
|
||||
| --- | --- |
|
||||
| 项目 CRUD 页面 | `views/system/project/{index.vue, modules/form.vue, data.ts}` |
|
||||
| 项目 API | `api/system/project/index.ts` |
|
||||
| 顶栏项目切换器 | `packages/effects/layouts/src/widgets/project-dropdown/project-dropdown.vue` |
|
||||
@@ -68,6 +68,7 @@
|
||||
## Approaches Considered
|
||||
|
||||
### Approach A: 一次到位(已选)
|
||||
|
||||
一个 PR 同时交付:UserProject 管理 API + 双入口前端弹窗 + `simple-list` bug 修复。
|
||||
|
||||
- Completeness: 9/10
|
||||
@@ -77,6 +78,7 @@
|
||||
- Reuses: `UserProjectMapper`、`PermissionController.assign-user-role` 模式、`assign-role-form.vue` 模板
|
||||
|
||||
### Approach B: 分两步交付(备选)
|
||||
|
||||
PR1 只做"用户视角"(后端 2 个接口 + 修 bug + 前端用户页入口);PR2 做"项目视角"。
|
||||
|
||||
- Completeness: 8/10
|
||||
@@ -85,6 +87,7 @@ PR1 只做"用户视角"(后端 2 个接口 + 修 bug + 前端用户页入口
|
||||
- Cons: 项目经理视角的体验要等下个迭代
|
||||
|
||||
### Approach C: 极简版(备选)
|
||||
|
||||
在用户编辑表单里直接加"所属项目"多选下拉框(类似 `deptId`/`postIds`),不做独立弹窗,不改项目页。
|
||||
|
||||
- Completeness: 5/10
|
||||
@@ -104,15 +107,14 @@ PR1 只做"用户视角"(后端 2 个接口 + 修 bug + 前端用户页入口
|
||||
|
||||
#### 1. 新建 `UserProjectController`
|
||||
|
||||
路径:`controller/admin/project/UserProjectController.java`
|
||||
基础路径:`/system/user-project`
|
||||
路径:`controller/admin/project/UserProjectController.java` 基础路径:`/system/user-project`
|
||||
|
||||
| 方法 | 路径 | 权限 | Body / Param | 返回 |
|
||||
|---|---|---|---|---|
|
||||
| --- | --- | --- | --- | --- |
|
||||
| POST | `/assign-user-projects` | `system:user:assign-project` | `{userId, projectIds: Set<Long>}` | `Boolean` |
|
||||
| POST | `/assign-project-users` | `system:project:assign-user` | `{projectId, userIds: Set<Long>}` | `Boolean` |
|
||||
| GET | `/list-project-ids-by-user` | `system:user:assign-project` | `?userId=` | `Set<Long>` |
|
||||
| GET | `/list-user-ids-by-project` | `system:project:assign-user` | `?projectId=` | `Set<Long>` |
|
||||
| GET | `/list-project-ids-by-user` | `system:user:assign-project` | `?userId=` | `Set<Long>` |
|
||||
| GET | `/list-user-ids-by-project` | `system:project:assign-user` | `?projectId=` | `Set<Long>` |
|
||||
|
||||
两个 `assign-*` 都是**幂等覆盖写入**(传全集,Service 内 diff)。
|
||||
|
||||
@@ -183,10 +185,10 @@ public CommonResult<List<ProjectRespVO>> getAllProjectSimpleList() {
|
||||
#### 4. 边界守卫(Service 层)
|
||||
|
||||
- **`assignUserProjects`**:若 `projectIds` 不包含用户当前 `ProjectContextHolder.getProjectId()`,**且调用者 `SecurityFrameworkUtils.getLoginUserId()` 就是 `userId` 本人**(即自己改自己的分配),抛 `USER_PROJECT_CANNOT_REMOVE_SELF_CURRENT` 业务异常。
|
||||
- *注释要写清*:本守卫只阻止自己把自己踢出当前项目;管理员给别人改分配不受此影响(即使被改的用户当前正在访问某项目)。
|
||||
- _注释要写清_:本守卫只阻止自己把自己踢出当前项目;管理员给别人改分配不受此影响(即使被改的用户当前正在访问某项目)。
|
||||
- **`assignProjectUsers`**:
|
||||
- *superadmin 守卫*:若 `userIds` 计算出的"要移除的子集"中任何一个持有超管角色,拒绝。**不能用 `userId == 1` 判别**(不同租户的管理员 id 不一定是 1)。正确做法:注入 `PermissionService.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())` 或类似的现有工具;yudao 里可查 `AdminUserService.isSuperAdmin()` 的实现并复用。
|
||||
- *自踢守卫*:若当前登录人 id 在被移除列表 **且** 当前 `ProjectContextHolder.getProjectId()` 等于目标 `projectId`,拒绝。
|
||||
- _superadmin 守卫_:若 `userIds` 计算出的"要移除的子集"中任何一个持有超管角色,拒绝。**不能用 `userId == 1` 判别**(不同租户的管理员 id 不一定是 1)。正确做法:注入 `PermissionService.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())` 或类似的现有工具;yudao 里可查 `AdminUserService.isSuperAdmin()` 的实现并复用。
|
||||
- _自踢守卫_:若当前登录人 id 在被移除列表 **且** 当前 `ProjectContextHolder.getProjectId()` 等于目标 `projectId`,拒绝。
|
||||
- **项目删除级联**:
|
||||
- `ProjectService.deleteProject` 需在**同一个 `@Transactional` 方法内**:先 `userProjectMapper.delete(lambdaQuery.eq(projectId, id))`(走 MyBatis Plus 自动软删标记 `deleted=1`),再 `projectMapper.deleteById(id)`。
|
||||
- 项目 `disable` 不触发级联(用户关系保留,恢复启用后仍生效)。
|
||||
@@ -247,11 +249,15 @@ export namespace SystemUserProjectApi {
|
||||
}
|
||||
}
|
||||
|
||||
export function assignUserProjects(data: SystemUserProjectApi.AssignUserProjectsReq) {
|
||||
export function assignUserProjects(
|
||||
data: SystemUserProjectApi.AssignUserProjectsReq,
|
||||
) {
|
||||
return requestClient.post('/system/user-project/assign-user-projects', data);
|
||||
}
|
||||
|
||||
export function assignProjectUsers(data: SystemUserProjectApi.AssignProjectUsersReq) {
|
||||
export function assignProjectUsers(
|
||||
data: SystemUserProjectApi.AssignProjectUsersReq,
|
||||
) {
|
||||
return requestClient.post('/system/user-project/assign-project-users', data);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user