refactor(X-Pack): 对权限体系进行重构-2

This commit is contained in:
fit2cloud-chenyw
2026-05-12 15:52:56 +08:00
parent ae72d021be
commit ac96225e2d
29 changed files with 559 additions and 21 deletions

14
.mcp.json Normal file
View File

@@ -0,0 +1,14 @@
{
"mcpServers": {
"dbhub": {
"type": "stdio",
"command": "npx",
"args": [
"@bytebase/dbhub@latest",
"--config",
"./dbhub.toml"
],
"env": {}
}
}
}

143
CLAUDE.md Normal file
View File

@@ -0,0 +1,143 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
DataEase is an open-source BI tool by FIT2Cloud for data analysis and visualization via drag-and-drop chart creation. Version 3.0.0, GPL v3 license.
**Branch:** `dev-v3` (current), PRs target `dev-v2`
## Tech Stack
- **Backend:** Java 21, Spring Boot 3.3.x, JPA (Hibernate) + QueryDSL, JDK 21
- **Frontend:** Vue 3, TypeScript, Vite, Element Plus, ECharts, AntV (G2, L7, S2)
- **DB:** MySQL 8 (primary), also PostgreSQL, Oracle, SQL Server, DM, Kingbase, GreatSQL, H2 (desktop)
- **Other:** Knife4j (API docs), EhCache, Quartz
## Module Structure
```
dataease/
├── core/
│ ├── core-backend/ # Spring Boot app (main artifact: CoreApplication.jar)
│ └── core-frontend/ # Vue 3 SPA
├── sdk/ # Shared SDK (common, api, extensions, distributed)
├── de-xpack/ # Enterprise/premium features (separate git repo)
├── drivers/ # JDBC drivers
└── installer/ # Docker Compose, deployment scripts
```
## Backend Domain Structure
Each business domain under `core/core-backend/src/main/java/io/dataease/<domain>/`:
```
<domain>/
├── dao/auto/entity/ # JPA @Entity classes
├── auto/repository/ # JpaRepository interfaces
├── dao/ext/po/ # QueryDSL projection POJOs
├── dao/ext/mapper/ # Custom repository interfaces
├── dto/ # Data Transfer Objects
├── bo/ # Business Objects
├── manage/ # Business logic (@Component)
├── server/ # API implementations (implements sdk interfaces)
├── request/ # Request body classes
└── utils/ # Domain utilities
```
## Key Commands
### Build
```bash
# Full build (all modules)
mvn clean install
# Build community standalone JAR
cd core && mvn clean package -Pstandalone -U -Dmaven.test.skip=true
# Output: core/core-backend/target/CoreApplication.jar
# Build desktop edition
cd core && mvn clean package -Pdesktop -U -Dmaven.test.skip=true
# Build enterprise/distributed edition
cd core && mvn clean package -Pdistributed -U -Dmaven.test.skip=true
```
### Run
```bash
# Direct JAR execution
java -jar core/core-backend/target/CoreApplication.jar
# Docker (recommended for testing)
curl -sSL https://dataease.oss-cn-hangzhou.aliyuncs.com/quick_start_v2.sh | bash
# Default credentials: admin / DataEase@123456
# Local installer
cd installer && /bin/bash install.sh
```
### Frontend Dev
```bash
cd core/core-frontend
npm run dev # Dev server
npm run build:distributed # Production build
```
### QueryDSL Regeneration
```bash
mvn compile # Regenerates Q-classes in target/generated-sources/java/
```
## Build Profiles
| Profile | Database | Description |
|---------|----------|-------------|
| `standalone` (default) | MySQL | Community/single-server |
| `desktop` | H2 | Desktop/lightweight |
| `distributed` | MySQL | Enterprise/multi-node |
Database-specific profiles: `standalone-oracle`, `standalone-pg`, `standalone-dm`, `standalone-kingbase`, `standalone-sqlserver`, `standalone-GreatSQL`
## Critical Dev Conventions
### JPA > MyBatis-Plus
MyBatis-Plus has been replaced by JPA. **Do not use MyBatis-Plus patterns.**
- Use `jakarta.persistence.*` (not `javax.persistence.*`)
- Entities: `@Getter`/`@Setter` via Lombok (not `@Data`), `@Comment` on table and every field
- IDs: `Long`, no `@GeneratedValue` — IDs generated manually via `SnowflakeUtil`
- Repositories: `JpaRepository<Xxx, Long>`, `JpaSpecificationExecutor<Xxx>`
### JpaUpdateNonNullAspect
Intercepts all `save`/`saveAndFlush` calls:
- If entity exists (non-null ID), **only non-null fields are copied** before saving — mimics MyBatis-Plus `updateById`
- To intentionally set a field to null: use QueryDSL `@Modifying` + JPQL, `save()` cannot do this
### Multi-DB Compatibility
All QueryDSL/JPQL queries **must work across all supported databases** (MySQL, Oracle, PostgreSQL, SQL Server, DM, Kingbase, GreatSQL). Avoid database-specific dialect functions; branch per-dialect when necessary (see `DynamicCaseNamingStrategy`).
### Layering
- **server/** = API endpoint implementation, implements interfaces from `sdk` module
- **manage/** = business logic, `@Component`, called by server
- **dao/auto/** = JPA entities + repositories
- **dao/ext/** = custom query POJOs and non-standard repositories
### ID Generation
Use `SnowflakeUtil` for all new entity IDs.
## Architecture Notes
- **Profile-based feature toggling:** Three Maven profiles control which modules are included (community vs enterprise)
- **Frontend bundled into JAR:** `maven-antrun-plugin` copies frontend dist into backend `static/`
- **Schema management:** Flyway disabled, `ddl-auto: update` manages schema in dev and production
- **xpack independence:** de-xpack is a separate git repo; core-backend optionally depends on it for debugging

View File

@@ -97,6 +97,9 @@ public class MenuManage {
if (coreMenu.getId().equals(21L)) return false;
return coreMenu.getId().equals(7L)
|| coreMenu.getPid().equals(7L)
|| coreMenu.getId().equals(9L)
|| coreMenu.getId().equals(111L)
|| coreMenu.getId().equals(112L)
|| coreMenu.getId().equals(14L)
|| coreMenu.getId().equals(17L)
|| coreMenu.getId().equals(18L)

View File

@@ -18,6 +18,8 @@ i18n_menu.screen=Data Screen
i18n_menu.dataset=Dataset
i18n_menu.datasource=Data Source
i18n_menu.user=User Management
i18n_menu.sysuser=User Management
i18n_menu.sysauth=Permission Configuration
i18n_menu.org=Organization Management
i18n_menu.auth=Permission Configuration
i18n_menu.report=Scheduled Report
@@ -149,6 +151,7 @@ i18n_template_recent=Recently Used
i18n_default_org=Default Organization
i18n_org_admin=Organization Admin
i18n_ordinary_role=Ordinary User
i18n_org_analyst=Data Analyst
i18n_sys_admin=System Admin
i18n_threshold_logic_eq=Equal to

View File

@@ -17,6 +17,8 @@ i18n_menu.screen=\u6570\u636E\u5927\u5C4F
i18n_menu.dataset=\u6570\u636E\u96C6
i18n_menu.datasource=\u6570\u636E\u6E90
i18n_menu.user=\u7528\u6237\u7BA1\u7406
i18n_menu.sysuser=\u7528\u6237\u7BA1\u7406
i18n_menu.sysauth=\u6743\u9650\u914D\u7F6E
i18n_menu.org=\u7EC4\u7EC7\u7BA1\u7406
i18n_menu.auth=\u6743\u9650\u914D\u7F6E
i18n_menu.report=\u5B9A\u65F6\u62A5\u544A
@@ -148,6 +150,7 @@ i18n_template_recent=\u6700\u8FD1\u4F7F\u7528
i18n_default_org=\u9ED8\u8BA4\u7EC4\u7EC7
i18n_org_admin=\u7EC4\u7EC7\u7BA1\u7406\u5458
i18n_ordinary_role=\u666E\u901A\u7528\u6237
i18n_org_analyst=\u6570\u636E\u5206\u6790\u5E08
i18n_sys_admin=\u7CFB\u7EDF\u7BA1\u7406\u5458
i18n_threshold_logic_eq=\u7B49\u4E8E

View File

@@ -17,6 +17,8 @@ i18n_menu.screen=\u6578\u64DA\u5927\u5C4F
i18n_menu.dataset=\u6578\u64DA\u96C6
i18n_menu.datasource=\u6578\u64DA\u6E90
i18n_menu.user=\u7528\u6236\u7BA1\u7406
i18n_menu.sysuser=\u7528\u6236\u7BA1\u7406
i18n_menu.sysauth=\u6B0A\u9650\u914D\u7F6E
i18n_menu.org=\u7D44\u7E54\u7BA1\u7406
i18n_menu.auth=\u6B0A\u9650\u914D\u7F6E
i18n_menu.report=\u5B9A\u6642\u5831\u544A
@@ -148,6 +150,7 @@ i18n_template_recent=\u6700\u8FD1\u4F7F\u7528
i18n_default_org=\u9ED8\u8A8D\u7D44\u7E54
i18n_org_admin=\u7D44\u7E54\u7BA1\u7406\u54E1
i18n_ordinary_role=\u666E\u901A\u7528\u6236
i18n_org_analyst=\u6578\u64DA\u5206\u6790\u5E2B
i18n_sys_admin=\u7CFB\u7D71\u7BA1\u7406\u54E1
i18n_threshold_logic_eq=\u7B49\u65BC

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99992 0.333252C3.76574 0.333252 0.333252 3.76574 0.333252 7.99992C0.333252 12.2341 3.76574 15.6666 7.99992 15.6666C12.2341 15.6666 15.6666 12.2341 15.6666 7.99992C15.6666 3.76574 12.2341 0.333252 7.99992 0.333252ZM12.6514 11.2374L4.76246 3.34847C5.68019 2.70844 6.79587 2.33325 7.99992 2.33325C11.1295 2.33325 13.6666 4.87031 13.6666 7.99992C13.6666 9.20397 13.2914 10.3196 12.6514 11.2374ZM2.33325 7.99992C2.33325 6.79599 2.70837 5.68041 3.34829 4.76272L11.2371 12.6516C10.3194 13.2915 9.20385 13.6666 7.99992 13.6666C4.87031 13.6666 2.33325 11.1295 2.33325 7.99992Z" fill="#8F959E"/>
</svg>

After

Width:  |  Height:  |  Size: 701 B

View File

@@ -9,6 +9,8 @@ interface TreeConfig {
showCheckbox: boolean
checkOnClickNode: boolean
placeholder: string
multiple: boolean
clearable: boolean
}
const props = defineProps({
optionList: propTypes.arrayOf(
@@ -20,7 +22,11 @@ const props = defineProps({
})
),
title: propTypes.string,
property: Object as PropType<TreeConfig>
property: Object as PropType<TreeConfig>,
disabled: {
type: Boolean,
default: false
}
})
const { property } = toRefs(props)
@@ -30,7 +36,9 @@ const treeConfig = computed(() => {
checkStrictly: false,
showCheckbox: true,
checkOnClickNode: true,
placeholder: t('user.role')
placeholder: t('user.role'),
multiple: true,
clearable: false
},
property.value
)
@@ -39,12 +47,17 @@ const treeConfig = computed(() => {
const state = reactive({
currentStatus: [],
currentStatusSingle: null as any,
activeStatus: []
})
const emits = defineEmits(['filter-change'])
const filterTree = ref()
const treeChange = () => {
if (!treeConfig.value.multiple) {
emits('filter-change', state.currentStatusSingle ? [state.currentStatusSingle] : [])
return
}
const nodes = state.currentStatus.map(id => {
return filterTree.value?.getNode(id).data
})
@@ -59,6 +72,7 @@ const optionListNotSelect = computed(() => {
})
const clear = () => {
state.currentStatus = []
state.currentStatusSingle = null
}
watch(
() => state.currentStatus,
@@ -69,6 +83,12 @@ watch(
immediate: true
}
)
watch(
() => state.currentStatusSingle,
() => {
treeChange()
}
)
defineExpose({
clear
})
@@ -79,6 +99,7 @@ defineExpose({
<span>{{ title }}</span>
<div class="filter-item">
<el-tree-select
v-if="treeConfig.multiple"
node-key="value"
ref="filterTree"
:teleported="false"
@@ -86,13 +107,31 @@ defineExpose({
v-model="state.currentStatus"
:data="optionListNotSelect"
:highlight-current="true"
:disabled="disabled"
multiple
clearable
:render-after-expand="false"
:placeholder="$t('common.please_select') + treeConfig.placeholder"
:show-checkbox="treeConfig.showCheckbox"
:check-strictly="treeConfig.checkStrictly"
:check-on-click-node="treeConfig.checkOnClickNode"
/>
<el-tree-select
v-else
node-key="value"
ref="filterTree"
:teleported="false"
style="width: 100%"
v-model="state.currentStatusSingle"
:data="optionListNotSelect"
:highlight-current="true"
:disabled="disabled"
:clearable="treeConfig.clearable"
:render-after-expand="false"
:placeholder="$t('common.please_select') + treeConfig.placeholder"
:check-strictly="treeConfig.checkStrictly"
:check-on-click-node="treeConfig.checkOnClickNode"
/>
</div>
</div>
</template>

View File

@@ -44,6 +44,13 @@ const cleanrInnerValue = (index: number) => {
state.conditions[i].value = []
}
}
// For tree-select in single-select mode, also clear single value
if (componentList.value[index]?.type === 'tree-select') {
const prop = componentList.value[index]?.property
if (prop && prop.multiple === false) {
state.conditions = state.conditions.filter(c => c.field !== field)
}
}
}
const clearInnerTag = (index?: number) => {
if (isNaN(index)) {
@@ -79,11 +86,16 @@ const filterChange = (value, field, operator) => {
exits = true
condition['value'] = value
}
if (!condition?.value?.length) {
const val = condition?.value
const isEmpty = Array.isArray(val)
? !val.length
: val === null || val === undefined || val === ''
if (isEmpty) {
state.conditions.splice(len, 1)
}
}
if (!exits && value?.length) {
const hasValue = Array.isArray(value) ? value.length : value != null && value !== ''
if (!exits && hasValue) {
state.conditions.push({ field, value, operator })
}
treeFilterChange(value, field, operator)
@@ -129,6 +141,7 @@ defineExpose({
:option-list="component.option"
:title="component.title"
:property="component.property"
:disabled="component.disabled"
@filter-change="v => filterChange(v, component.field, 'in')"
/>
<drawer-filter

View File

@@ -125,7 +125,7 @@ defineExpose({
</script>
<template>
<div class="flex-table" :class="tableData && !tableData.length && 'no-data'">
<div class="flex-table" :class="!tableData?.length && 'no-data'">
<el-table
ref="table"
:border="border"

View File

@@ -478,6 +478,7 @@ export default {
user: 'User',
role: 'Role',
addUser: '@:common.add @:system.user',
selected_count: 'Selected {0} members',
click_to_show: 'Click to show',
click_to_hide: 'Click to hide',
basic_settings: 'Basic settings',
@@ -820,6 +821,17 @@ export default {
data_import_failed: 'Some data import failed',
data_import_failed_de: 'Data import failed'
},
sysuser: {
sys_user_management: 'System User Management',
search_placeholder: 'Search name, account, email',
org: 'Organization',
user_source: 'Source',
sys_variable: 'System Variable',
add_sys_user: 'Add System User',
edit_sys_user: 'Edit System User',
account_placeholder: 'Please enter account',
name_placeholder: 'Please enter name'
},
userimport: {
buttonText: 'Batch import',
dialogTitle: 'Batch upload',
@@ -909,7 +921,18 @@ export default {
move_resource_first: 'Migrate resources first',
default_parent_tips: '(Default current organization)',
admin_parent_tips: '(Default root organization)',
please_login_per_changed: 'Current user permissions have changed, please log in again'
please_login_per_changed: 'Current user permissions have changed, please log in again',
add_member: 'Add Member',
select_member: 'Select Members',
remove_member: 'Remove Member',
remove_member_confirm: 'Are you sure you want to remove this member from the organization?',
member_role: 'Member Role',
members: 'members',
selected_count: 'Selected {0} members',
select_org_first: 'Please select an organization from the left panel',
search_name_account: 'Search name or account',
select_member_role: 'Please select member role',
no_selected_member: 'No members selected'
},
auth: {
permission_configuration: 'Permission Configuration',

View File

@@ -464,6 +464,7 @@ export default {
user: '使用者',
role: '角色',
addUser: '@:common.add@:system.user',
selected_count: '已選 {0} ',
click_to_show: '點選顯示',
click_to_hide: '點選隱藏',
basic_settings: '基礎設定',
@@ -879,7 +880,18 @@ export default {
move_resource_first: '先遷移資源',
default_parent_tips: '(預設目前組織)',
admin_parent_tips: '(預設根組織)',
please_login_per_changed: '目前使用者權限已變更,請重新登入'
please_login_per_changed: '目前使用者權限已變更,請重新登入',
add_member: '新增成員',
select_member: '選擇成員',
remove_member: '移除成員',
remove_member_confirm: '確定將該成員移出目前組織嗎?',
member_role: '成員角色',
members: '人',
selected_count: '已選 {0} 人',
select_org_first: '請先在左側選擇組織',
search_name_account: '搜尋姓名、帳號',
select_member_role: '請選擇成員角色',
no_selected_member: '暫無已選成員'
},
auth: {
permission_configuration: '權限配置',

View File

@@ -465,6 +465,7 @@ export default {
user: '用户',
role: '角色',
addUser: '@:common.add@:system.user',
selected_count: '已选 {0} ',
click_to_show: '点击显示',
click_to_hide: '点击隐藏',
basic_settings: '基础设置',
@@ -795,6 +796,17 @@ export default {
data_import_failed: '部分数据导入失败',
data_import_failed_de: '数据导入失败'
},
sysuser: {
sys_user_management: '系统用户管理',
search_placeholder: '搜索姓名账号邮箱',
org: '所在组织',
user_source: '用户来源',
sys_variable: '系统变量',
add_sys_user: '添加系统用户',
edit_sys_user: '编辑系统用户',
account_placeholder: '请输入账号',
name_placeholder: '请输入姓名'
},
userimport: {
buttonText: '批量导入',
dialogTitle: '批量上传',
@@ -881,7 +893,18 @@ export default {
move_resource_first: '先迁移资源',
default_parent_tips: '(默认当前组织)',
admin_parent_tips: '(默认根组织)',
please_login_per_changed: '当前用户权限已变更,请重新登录'
please_login_per_changed: '当前用户权限已变更,请重新登录',
add_member: '添加成员',
select_member: '选择成员',
remove_member: '移除成员',
remove_member_confirm: '确定将该成员移出当前组织吗?',
member_role: '成员角色',
members: '人',
selected_count: '已选 {0} 人',
select_org_first: '请先在左侧选择组织',
search_name_account: '搜索姓名、账号',
select_member_role: '请选择成员角色',
no_selected_member: '暂无已选成员'
},
auth: {
permission_configuration: '权限配置',

View File

@@ -121,7 +121,6 @@ router.beforeEach(async (to, from, next) => {
next()
return
}
let roleRouters = (await getRoleRouters()) || []
if (isDesktop) {
roleRouters = roleRouters.filter(item => item.name !== 'system')

View File

@@ -1,5 +1,4 @@
<template>
<p class="router-title">{{ t('commons.system_parameter_setting') }}</p>
<el-tabs v-model="activeName">
<el-tab-pane v-for="item in tabArray" :key="item.name" :label="item.label" :name="item.name" />
</el-tabs>
@@ -51,20 +50,10 @@ const addTable = tab => {
}
</script>
<style lang="less">
.router-title {
color: #1f2329;
font-feature-settings: 'clig' off, 'liga' off;
font-family: var(--de-custom_font, 'PingFang');
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 28px;
}
.sys-setting-p {
width: 100%;
height: calc(100vh - 176px);
height: calc(100vh - 148px);
box-sizing: border-box;
margin-top: 12px;
}
.container-sys-param {

8
dbhub.toml Normal file
View File

@@ -0,0 +1,8 @@
[[sources]]
id = "dashboard_mysql"
type = "mysql"
host = "localhost"
port = 3306
user = "root"
password = "Password123@mysql"
database = "dataease_v3"

View File

@@ -0,0 +1,51 @@
package io.dataease.api.permissions.org.api;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.api.permissions.org.dto.SysOrgMemberAddRequest;
import io.dataease.api.permissions.org.dto.SysOrgMemberRequest;
import io.dataease.api.permissions.org.dto.SysOrgMemberRoleSwitchRequest;
import io.dataease.api.permissions.org.dto.SysOrgRoleOptionRequest;
import io.dataease.api.permissions.org.vo.SysOrgMemberVO;
import io.dataease.api.permissions.org.vo.SysOrgRoleOptionVO;
import io.dataease.auth.DeApiPath;
import io.dataease.model.KeywordRequest;
import io.dataease.result.PageResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
import static io.dataease.constant.AuthResourceEnum.ORG;
@Tag(name = "系统组织管理")
@ApiSupport(order = 887, author = "fit2cloud-someone")
@DeApiPath(value = "/sys/org", rt = ORG)
public interface SysOrgApi {
@Operation(summary = "分页查询组织成员")
@PostMapping("/member/page")
PageResult<SysOrgMemberVO> memberPage(@RequestBody SysOrgMemberRequest request);
@Operation(summary = "查询候选用户(不属于任何组织的用户)")
@PostMapping("/member/candidates")
List<SysOrgMemberVO> memberCandidates(@RequestBody KeywordRequest request);
@Operation(summary = "查询组织可选角色列表")
@PostMapping("/member/roleOptions")
List<SysOrgRoleOptionVO> memberRoleOptions(@RequestBody SysOrgRoleOptionRequest request);
@Operation(summary = "添加组织成员")
@PostMapping("/member/add")
void addMembers(@RequestBody SysOrgMemberAddRequest request);
@Operation(summary = "移除组织成员")
@PostMapping("/member/remove/{userId}/{orgId}")
void removeMember(@PathVariable("userId") Long userId, @PathVariable("orgId") Long orgId);
@Operation(summary = "切换成员角色")
@PostMapping("/member/switchRole")
void switchRole(@RequestBody SysOrgMemberRoleSwitchRequest request);
}

View File

@@ -5,6 +5,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Schema(description = "组织构造器")
@Data
@@ -18,4 +19,6 @@ public class OrgCreator implements Serializable {
private String name;
@Schema(description = "上级ID")
private Long pid;
@Schema(description = "组织管理员用户ID列表")
private List<Long> adminIds;
}

View File

@@ -0,0 +1,25 @@
package io.dataease.api.permissions.org.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Schema(description = "组织成员添加请求")
@Data
public class SysOrgMemberAddRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "组织ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long orgId;
@Schema(description = "用户ID列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Long> userIds;
@Schema(description = "角色ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long roleId;
}

View File

@@ -0,0 +1,27 @@
package io.dataease.api.permissions.org.dto;
import io.dataease.model.KeywordRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
@Schema(description = "组织成员查询请求")
@EqualsAndHashCode(callSuper = true)
@Data
public class SysOrgMemberRequest extends KeywordRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "组织ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long orgId;
@Schema(description = "当前页码")
private Long currentPage = 1L;
@Schema(description = "每页大小")
private Long pageSize = 10L;
}

View File

@@ -0,0 +1,25 @@
package io.dataease.api.permissions.org.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Schema(description = "组织成员角色切换请求")
@Data
public class SysOrgMemberRoleSwitchRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long userId;
@Schema(description = "组织ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long orgId;
@Schema(description = "角色ID列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Long> roleIds;
}

View File

@@ -0,0 +1,24 @@
package io.dataease.api.permissions.org.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* Intermediate DTO for raw query rows in memberPage (one row = user + one role)
*/
@Data
public class SysOrgMemberRowDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
private String account;
private String name;
private String email;
private Long roleId;
private String roleName;
private Boolean enable;
}

View File

@@ -0,0 +1,18 @@
package io.dataease.api.permissions.org.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Schema(description = "组织角色选项请求")
@Data
public class SysOrgRoleOptionRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "组织ID", requiredMode = Schema.RequiredMode.REQUIRED)
private Long orgId;
}

View File

@@ -0,0 +1,50 @@
package io.dataease.api.permissions.org.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Schema(description = "组织成员VO")
@Data
public class SysOrgMemberVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "用户ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@Schema(description = "账号")
private String account;
@Schema(description = "姓名")
private String name;
@Schema(description = "邮箱")
private String email;
@Schema(description = "角色列表")
private List<RoleItem> roles;
@Schema(description = "用户状态")
private Boolean enable;
@Data
public static class RoleItem implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "角色ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long roleId;
@Schema(description = "角色名称")
private String roleName;
}
}

View File

@@ -0,0 +1,24 @@
package io.dataease.api.permissions.org.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Schema(description = "组织角色选项VO")
@Data
public class SysOrgRoleOptionVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "角色ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@Schema(description = "角色名称")
private String name;
}

View File

@@ -6,6 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.util.List;
@Schema(description = "角色过滤器")
@EqualsAndHashCode(callSuper = true)
@@ -17,4 +18,8 @@ public class RoleRequest extends KeywordRequest {
private static final long serialVersionUID = 7354856549096378406L;
@Schema(description = "用户ID")
private Long uid;
@Schema(description = "组织ID")
private Long oid;
@Schema(description = "组织ID列表(支持多选)")
private List<Long> oidList;
}

View File

@@ -35,4 +35,6 @@ public class UserCreator implements Serializable {
private Boolean mfaEnable = false;
@Schema(description = "系统变量")
private List<SysVariableValueItem> variables;
@Schema(description = "所属组织ID")
private Long defaultOid;
}

View File

@@ -16,5 +16,7 @@ public class UserGridRequest extends KeywordRequest implements Serializable {
private List<Long> roleIdList;
private Long oid;
private Boolean timeDesc;
}

View File

@@ -17,6 +17,10 @@ public class UserFormVO implements Serializable {
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
@Schema(description = "所属组织ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long defaultOid;
@Schema(description = "账号")
private String account;