diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/config/RoleBootstrapRunner.java b/data-agent-backend/src/main/java/io/github/malonetalk/config/RoleBootstrapRunner.java
new file mode 100644
index 0000000..d024833
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/config/RoleBootstrapRunner.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.config;
+
+import io.github.malonetalk.entity.SysRole;
+import io.github.malonetalk.mapper.SysRoleMapper;
+import java.time.LocalDateTime;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * 启动引导:sys_role 为空时创建初始角色。
+ *
+ *
创建管理员(id=1)和普通用户(id=2)两个角色,
+ * 与 {@code @AdminOnly} 注解配合实现管理员权限判定。
+ */
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class RoleBootstrapRunner implements CommandLineRunner {
+
+ private final SysRoleMapper sysRoleMapper;
+
+ @Override
+ public void run(String... args) {
+ if (sysRoleMapper.countAll() > 0) {
+ return;
+ }
+ LocalDateTime now = LocalDateTime.now();
+
+ SysRole admin = new SysRole();
+ admin.setId(1);
+ admin.setName("管理员");
+ admin.setDescription("系统管理员,拥有所有权限");
+ admin.setCreateTime(now);
+ admin.setUpdateTime(now);
+ sysRoleMapper.insert(admin);
+
+ SysRole user = new SysRole();
+ user.setId(2);
+ user.setName("普通用户");
+ user.setDescription("普通用户,受限权限");
+ user.setCreateTime(now);
+ user.setUpdateTime(now);
+ sysRoleMapper.insert(user);
+
+ log.info("Bootstrapped initial roles: 管理员 (id=1), 普通用户 (id=2).");
+ }
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/DatasourceController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/DatasourceController.java
index da9b162..32629aa 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/DatasourceController.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/DatasourceController.java
@@ -24,12 +24,18 @@
import io.github.malonetalk.convertor.DatasourceConverter;
import io.github.malonetalk.dto.DatasourceRequest;
import io.github.malonetalk.dto.DatasourceResponse;
+import io.github.malonetalk.entity.ColumnInfo;
import io.github.malonetalk.entity.Datasource;
+import io.github.malonetalk.entity.TableInfo;
import io.github.malonetalk.enums.Status;
import io.github.malonetalk.exception.BusinessException;
+import io.github.malonetalk.mapper.ColumnSemanticInfoMapper;
+import io.github.malonetalk.mapper.TableInfoMapper;
import io.github.malonetalk.service.DatasourceService;
import jakarta.validation.Valid;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -48,6 +54,18 @@ public class DatasourceController {
private final DatasourceService dataSourceService;
private final DatasourceConverter datasourceConverter;
+ /**
+ * TableInfoMapper 和 ColumnSemanticInfoMapper 直接注入 Controller 而非通过 Service:
+ *
+ *
{@code GET /{id}/tables} 和 {@code GET /{id}/columns} 仅做"查 mapper → 返回"的纯透传,
+ * 零业务逻辑(无 join、无事务、无缓存、无校验)。引入中间 Service 只会产生一层无意义的委托代码。
+ *
+ *
如果后续需要按角色过滤返回结果(权限控制),届时再将两个查询收拢到 TableMetadataService。
+ */
+ private final TableInfoMapper tableInfoMapper;
+
+ private final ColumnSemanticInfoMapper columnSemanticInfoMapper;
+
@GetMapping
public Result> findAll() {
List list =
@@ -60,6 +78,33 @@ public Result findById(@PathVariable Integer id) {
return Result.success(datasourceConverter.toResponse(requireDatasource(id)));
}
+ /** 返回某数据源下的所有物理表名,供权限配置页使用。 */
+ @AdminOnly
+ @GetMapping("/{id}/tables")
+ public Result> listTableNames(@PathVariable Integer id) {
+ requireDatasource(id);
+ List tables =
+ tableInfoMapper.selectByDatasourceId(id).stream()
+ .map(TableInfo::getTableName)
+ .toList();
+ return Result.success(tables);
+ }
+
+ /** 返回某数据源下所有表的列名,一次查询,供列级权限配置使用。 */
+ @AdminOnly
+ @GetMapping("/{id}/columns")
+ public Result>> listAllColumns(@PathVariable Integer id) {
+ requireDatasource(id);
+ Map> map =
+ columnSemanticInfoMapper.selectByDatasourceId(id).stream()
+ .collect(
+ Collectors.groupingBy(
+ ColumnInfo::getTableName,
+ Collectors.mapping(
+ ColumnInfo::getColumnName, Collectors.toList())));
+ return Result.success(map);
+ }
+
@AdminOnly
@PostMapping
public Result save(@Valid @RequestBody DatasourceRequest request) {
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/MetricController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/MetricController.java
index a6fbd77..f1f3ccb 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/MetricController.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/MetricController.java
@@ -39,7 +39,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-@AdminOnly
@Slf4j
@RestController
@AllArgsConstructor
@@ -50,12 +49,14 @@ public class MetricController {
private final MetricService metricService;
private final MetricConverter metricConverter;
+ @AdminOnly
@PostMapping
public Result create(@Valid @RequestBody MetricRequest request) {
MetricInfo entity = metricConverter.toEntity(request);
return Result.success(metricConverter.toResponse(metricService.create(entity)));
}
+ @AdminOnly
@PutMapping("/{id}")
public Result update(
@PathVariable @Positive(message = "id 必须为正数") Integer id,
@@ -64,6 +65,7 @@ public Result update(
return Result.success(metricConverter.toResponse(metricService.update(id, entity)));
}
+ @AdminOnly
@DeleteMapping("/{id}")
public Result delete(@PathVariable @Positive(message = "id 必须为正数") Integer id) {
metricService.delete(id);
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/SysRoleController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/SysRoleController.java
new file mode 100644
index 0000000..b284357
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/SysRoleController.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.controller;
+
+import io.github.malonetalk.annotation.AdminOnly;
+import io.github.malonetalk.common.Result;
+import io.github.malonetalk.dto.ColumnPermissionResponse;
+import io.github.malonetalk.dto.RoleRequest;
+import io.github.malonetalk.dto.RoleResponse;
+import io.github.malonetalk.dto.SaveColumnPermissionRequest;
+import io.github.malonetalk.dto.SaveTablePermissionRequest;
+import io.github.malonetalk.dto.TablePermissionResponse;
+import io.github.malonetalk.service.SysRoleService;
+import jakarta.validation.Valid;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@AdminOnly
+@RestController
+@AllArgsConstructor
+@RequestMapping("/api/sys/role")
+@Validated
+public class SysRoleController {
+
+ private final SysRoleService sysRoleService;
+
+ @GetMapping
+ public Result> listAll() {
+ return Result.success(sysRoleService.listAll());
+ }
+
+ @PostMapping
+ public Result create(@Valid @RequestBody RoleRequest request) {
+ return Result.success(sysRoleService.create(request));
+ }
+
+ @PutMapping("/{id}")
+ public Result update(
+ @PathVariable Integer id, @Valid @RequestBody RoleRequest request) {
+ return Result.success(sysRoleService.update(id, request));
+ }
+
+ @DeleteMapping("/{id}")
+ public Result delete(@PathVariable Integer id) {
+ sysRoleService.delete(id);
+ return Result.success();
+ }
+
+ @GetMapping("/{roleId}/permissions")
+ public Result> getPermissions(@PathVariable Integer roleId) {
+ return Result.success(sysRoleService.getPermissions(roleId));
+ }
+
+ @PutMapping("/{roleId}/permissions")
+ public Result savePermissions(
+ @PathVariable Integer roleId, @Valid @RequestBody SaveTablePermissionRequest request) {
+ sysRoleService.savePermissions(roleId, request);
+ return Result.success();
+ }
+
+ @GetMapping("/{roleId}/columns")
+ public Result> getColumnPermissions(
+ @PathVariable Integer roleId, @RequestParam Integer datasourceId) {
+ return Result.success(sysRoleService.getColumnPermissions(roleId, datasourceId));
+ }
+
+ @PutMapping("/{roleId}/columns")
+ public Result saveColumnPermissions(
+ @PathVariable Integer roleId, @Valid @RequestBody SaveColumnPermissionRequest request) {
+ sysRoleService.saveColumnPermissions(roleId, request);
+ return Result.success();
+ }
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/convertor/RoleConverter.java b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/RoleConverter.java
new file mode 100644
index 0000000..afd97e3
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/RoleConverter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.convertor;
+
+import io.github.malonetalk.dto.RoleResponse;
+import io.github.malonetalk.entity.SysRole;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface RoleConverter {
+
+ RoleResponse toResponse(SysRole role);
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/ColumnPermissionResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/ColumnPermissionResponse.java
new file mode 100644
index 0000000..bb5e5f4
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/ColumnPermissionResponse.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import java.util.List;
+
+public record ColumnPermissionResponse(String tableName, List columnNames) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/RoleRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/RoleRequest.java
new file mode 100644
index 0000000..2995bee
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/RoleRequest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record RoleRequest(@NotBlank(message = "name 不能为空") String name, String description) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/RoleResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/RoleResponse.java
new file mode 100644
index 0000000..1f08dee
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/RoleResponse.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import java.time.LocalDateTime;
+
+public record RoleResponse(Integer id, String name, String description, LocalDateTime createTime) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/SaveColumnPermissionRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/SaveColumnPermissionRequest.java
new file mode 100644
index 0000000..d926934
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/SaveColumnPermissionRequest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+
+public record SaveColumnPermissionRequest(
+ @NotNull(message = "datasourceId 不能为空") Integer datasourceId,
+ @NotBlank(message = "tableName 不能为空") String tableName,
+ List columnNames) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/SaveTablePermissionRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/SaveTablePermissionRequest.java
new file mode 100644
index 0000000..b1a4956
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/SaveTablePermissionRequest.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+
+public record SaveTablePermissionRequest(
+ @NotNull(message = "datasourceId 不能为空") Integer datasourceId, List tableNames) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/TablePermissionResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/TablePermissionResponse.java
new file mode 100644
index 0000000..31ebde2
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/TablePermissionResponse.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import java.util.List;
+
+public record TablePermissionResponse(Integer datasourceId, List tableNames) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/entity/RoleHiddenColumn.java b/data-agent-backend/src/main/java/io/github/malonetalk/entity/RoleHiddenColumn.java
new file mode 100644
index 0000000..9ba8b0b
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/entity/RoleHiddenColumn.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.entity;
+
+import java.time.LocalDateTime;
+import lombok.Data;
+
+@Data
+public class RoleHiddenColumn {
+ private Integer id;
+ private Integer roleId;
+ private Integer datasourceId;
+ private String tableName;
+ private String columnName;
+ private LocalDateTime createTime;
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/entity/RoleTablePermission.java b/data-agent-backend/src/main/java/io/github/malonetalk/entity/RoleTablePermission.java
new file mode 100644
index 0000000..ae79233
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/entity/RoleTablePermission.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.entity;
+
+import java.time.LocalDateTime;
+import lombok.Data;
+
+@Data
+public class RoleTablePermission {
+ private Integer id;
+ private Integer roleId;
+ private Integer datasourceId;
+ private String tableName;
+ private LocalDateTime createTime;
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/entity/SysRole.java b/data-agent-backend/src/main/java/io/github/malonetalk/entity/SysRole.java
new file mode 100644
index 0000000..6f4e6f3
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/entity/SysRole.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.entity;
+
+import java.time.LocalDateTime;
+import lombok.Data;
+
+@Data
+public class SysRole {
+ private Integer id;
+ private String name;
+ private String description;
+ private LocalDateTime createTime;
+ private LocalDateTime updateTime;
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/RoleHiddenColumnMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/RoleHiddenColumnMapper.java
new file mode 100644
index 0000000..40897eb
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/RoleHiddenColumnMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.mapper;
+
+import io.github.malonetalk.entity.RoleHiddenColumn;
+import java.util.List;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface RoleHiddenColumnMapper {
+
+ List selectByRoleAndDatasource(
+ @Param("roleId") Integer roleId, @Param("datasourceId") Integer datasourceId);
+
+ int insert(RoleHiddenColumn perm);
+
+ int deleteByRoleDatasourceAndTable(
+ @Param("roleId") Integer roleId,
+ @Param("datasourceId") Integer datasourceId,
+ @Param("tableName") String tableName);
+
+ int deleteByRoleId(@Param("roleId") Integer roleId);
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/RoleTablePermissionMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/RoleTablePermissionMapper.java
new file mode 100644
index 0000000..da92fcf
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/RoleTablePermissionMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.mapper;
+
+import io.github.malonetalk.entity.RoleTablePermission;
+import java.util.List;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface RoleTablePermissionMapper {
+
+ List selectByRoleId(@Param("roleId") Integer roleId);
+
+ List selectByRoleAndDatasource(
+ @Param("roleId") Integer roleId, @Param("datasourceId") Integer datasourceId);
+
+ int insert(RoleTablePermission perm);
+
+ int deleteByRoleAndDatasource(
+ @Param("roleId") Integer roleId, @Param("datasourceId") Integer datasourceId);
+
+ int deleteByRoleId(@Param("roleId") Integer roleId);
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysRoleMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysRoleMapper.java
new file mode 100644
index 0000000..b04c593
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysRoleMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.mapper;
+
+import io.github.malonetalk.entity.SysRole;
+import java.util.List;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface SysRoleMapper {
+
+ List selectAll();
+
+ SysRole selectById(@Param("id") Integer id);
+
+ int insert(SysRole role);
+
+ int update(SysRole role);
+
+ int deleteById(@Param("id") Integer id);
+
+ int countAll();
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/SysRoleService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysRoleService.java
new file mode 100644
index 0000000..1d08980
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysRoleService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.service;
+
+import io.github.malonetalk.dto.ColumnPermissionResponse;
+import io.github.malonetalk.dto.RoleRequest;
+import io.github.malonetalk.dto.RoleResponse;
+import io.github.malonetalk.dto.SaveColumnPermissionRequest;
+import io.github.malonetalk.dto.SaveTablePermissionRequest;
+import io.github.malonetalk.dto.TablePermissionResponse;
+import java.util.List;
+
+public interface SysRoleService {
+
+ List listAll();
+
+ RoleResponse create(RoleRequest request);
+
+ RoleResponse update(Integer id, RoleRequest request);
+
+ void delete(Integer id);
+
+ List getPermissions(Integer roleId);
+
+ void savePermissions(Integer roleId, SaveTablePermissionRequest request);
+
+ List getColumnPermissions(Integer roleId, Integer datasourceId);
+
+ void saveColumnPermissions(Integer roleId, SaveColumnPermissionRequest request);
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/SysRoleServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysRoleServiceImpl.java
new file mode 100644
index 0000000..dea1ba7
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysRoleServiceImpl.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.service;
+
+import io.github.malonetalk.common.ErrorCode;
+import io.github.malonetalk.convertor.RoleConverter;
+import io.github.malonetalk.dto.ColumnPermissionResponse;
+import io.github.malonetalk.dto.RoleRequest;
+import io.github.malonetalk.dto.RoleResponse;
+import io.github.malonetalk.dto.SaveColumnPermissionRequest;
+import io.github.malonetalk.dto.SaveTablePermissionRequest;
+import io.github.malonetalk.dto.TablePermissionResponse;
+import io.github.malonetalk.entity.RoleHiddenColumn;
+import io.github.malonetalk.entity.RoleTablePermission;
+import io.github.malonetalk.entity.SysRole;
+import io.github.malonetalk.exception.BusinessException;
+import io.github.malonetalk.mapper.RoleHiddenColumnMapper;
+import io.github.malonetalk.mapper.RoleTablePermissionMapper;
+import io.github.malonetalk.mapper.SysRoleMapper;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@AllArgsConstructor
+public class SysRoleServiceImpl implements SysRoleService {
+
+ private final SysRoleMapper sysRoleMapper;
+ private final RoleTablePermissionMapper roleTablePermissionMapper;
+ private final RoleHiddenColumnMapper roleHiddenColumnMapper;
+ private final RoleConverter roleConverter;
+
+ /** 管理员角色 id:AuthInterceptor 靠 role_id==1 判 @AdminOnly,删掉即永久锁死管理员。 */
+ private static final int ADMIN_ROLE_ID = 1;
+
+ @Override
+ public List listAll() {
+ return sysRoleMapper.selectAll().stream().map(roleConverter::toResponse).toList();
+ }
+
+ @Override
+ public RoleResponse create(RoleRequest request) {
+ checkNameConflict(request.name(), null);
+ LocalDateTime now = LocalDateTime.now();
+ SysRole role = new SysRole();
+ role.setName(request.name());
+ role.setDescription(request.description());
+ role.setCreateTime(now);
+ role.setUpdateTime(now);
+ sysRoleMapper.insert(role);
+ return roleConverter.toResponse(role);
+ }
+
+ @Override
+ public RoleResponse update(Integer id, RoleRequest request) {
+ SysRole role = requireRole(id);
+ checkNameConflict(request.name(), id);
+ role.setName(request.name());
+ role.setDescription(request.description());
+ sysRoleMapper.update(role);
+ return roleConverter.toResponse(role);
+ }
+
+ @Override
+ @Transactional
+ public void delete(Integer id) {
+ requireRole(id);
+ if (id == ADMIN_ROLE_ID) {
+ throw BusinessException.of(ErrorCode.FORBIDDEN, "不能删除管理员角色");
+ }
+ roleTablePermissionMapper.deleteByRoleId(id);
+ roleHiddenColumnMapper.deleteByRoleId(id);
+ sysRoleMapper.deleteById(id);
+ }
+
+ @Override
+ public List getPermissions(Integer roleId) {
+ List perms = roleTablePermissionMapper.selectByRoleId(roleId);
+ return perms.stream()
+ .collect(
+ Collectors.groupingBy(
+ RoleTablePermission::getDatasourceId,
+ Collectors.mapping(
+ RoleTablePermission::getTableName, Collectors.toList())))
+ .entrySet()
+ .stream()
+ .map(e -> new TablePermissionResponse(e.getKey(), e.getValue()))
+ .toList();
+ }
+
+ @Override
+ @Transactional
+ public void savePermissions(Integer roleId, SaveTablePermissionRequest request) {
+ requireRole(roleId);
+ roleTablePermissionMapper.deleteByRoleAndDatasource(roleId, request.datasourceId());
+ if (request.tableNames() != null) {
+ LocalDateTime now = LocalDateTime.now();
+ for (String tableName : request.tableNames()) {
+ RoleTablePermission perm = new RoleTablePermission();
+ perm.setRoleId(roleId);
+ perm.setDatasourceId(request.datasourceId());
+ perm.setTableName(tableName);
+ perm.setCreateTime(now);
+ roleTablePermissionMapper.insert(perm);
+ }
+ }
+ }
+
+ @Override
+ public List getColumnPermissions(
+ Integer roleId, Integer datasourceId) {
+ List perms =
+ roleHiddenColumnMapper.selectByRoleAndDatasource(roleId, datasourceId);
+ return perms.stream()
+ .collect(
+ Collectors.groupingBy(
+ RoleHiddenColumn::getTableName,
+ Collectors.mapping(
+ RoleHiddenColumn::getColumnName, Collectors.toList())))
+ .entrySet()
+ .stream()
+ .map(e -> new ColumnPermissionResponse(e.getKey(), e.getValue()))
+ .toList();
+ }
+
+ @Override
+ @Transactional
+ public void saveColumnPermissions(Integer roleId, SaveColumnPermissionRequest request) {
+ requireRole(roleId);
+ roleHiddenColumnMapper.deleteByRoleDatasourceAndTable(
+ roleId, request.datasourceId(), request.tableName());
+ if (request.columnNames() != null && !request.columnNames().isEmpty()) {
+ LocalDateTime now = LocalDateTime.now();
+ for (String columnName : request.columnNames()) {
+ RoleHiddenColumn perm = new RoleHiddenColumn();
+ perm.setRoleId(roleId);
+ perm.setDatasourceId(request.datasourceId());
+ perm.setTableName(request.tableName());
+ perm.setColumnName(columnName);
+ perm.setCreateTime(now);
+ roleHiddenColumnMapper.insert(perm);
+ }
+ }
+ }
+
+ private void checkNameConflict(String name, Integer excludeId) {
+ List all = sysRoleMapper.selectAll();
+ for (SysRole role : all) {
+ if (role.getName().equals(name) && !role.getId().equals(excludeId)) {
+ throw BusinessException.of(ErrorCode.DATA_CONFLICT, "角色名 '" + name + "' 已存在");
+ }
+ }
+ }
+
+ private SysRole requireRole(Integer id) {
+ SysRole role = sysRoleMapper.selectById(id);
+ if (role == null) {
+ throw BusinessException.of(ErrorCode.RESOURCE_NOT_FOUND, "角色不存在");
+ }
+ return role;
+ }
+}
diff --git a/data-agent-backend/src/main/resources/mapper/RoleHiddenColumnMapper.xml b/data-agent-backend/src/main/resources/mapper/RoleHiddenColumnMapper.xml
new file mode 100644
index 0000000..de3fa37
--- /dev/null
+++ b/data-agent-backend/src/main/resources/mapper/RoleHiddenColumnMapper.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT * FROM role_hidden_column
+ WHERE role_id = #{roleId} AND datasource_id = #{datasourceId}
+ ORDER BY table_name, column_name
+
+
+
+ INSERT INTO role_hidden_column (role_id, datasource_id, table_name, column_name, create_time)
+ VALUES (#{roleId}, #{datasourceId}, #{tableName}, #{columnName}, #{createTime})
+
+
+
+ DELETE FROM role_hidden_column
+ WHERE role_id = #{roleId}
+ AND datasource_id = #{datasourceId}
+ AND table_name = #{tableName}
+
+
+
+ DELETE FROM role_hidden_column WHERE role_id = #{roleId}
+
+
+
diff --git a/data-agent-backend/src/main/resources/mapper/RoleTablePermissionMapper.xml b/data-agent-backend/src/main/resources/mapper/RoleTablePermissionMapper.xml
new file mode 100644
index 0000000..57f574c
--- /dev/null
+++ b/data-agent-backend/src/main/resources/mapper/RoleTablePermissionMapper.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT * FROM role_table_permission WHERE role_id = #{roleId} ORDER BY datasource_id, table_name
+
+
+
+ SELECT * FROM role_table_permission WHERE role_id = #{roleId} AND datasource_id = #{datasourceId}
+ ORDER BY table_name
+
+
+
+ INSERT INTO role_table_permission (role_id, datasource_id, table_name, create_time)
+ VALUES (#{roleId}, #{datasourceId}, #{tableName}, #{createTime})
+
+
+
+ DELETE FROM role_table_permission WHERE role_id = #{roleId} AND datasource_id = #{datasourceId}
+
+
+
+ DELETE FROM role_table_permission WHERE role_id = #{roleId}
+
+
+
diff --git a/data-agent-backend/src/main/resources/mapper/SysRoleMapper.xml b/data-agent-backend/src/main/resources/mapper/SysRoleMapper.xml
new file mode 100644
index 0000000..6cc3893
--- /dev/null
+++ b/data-agent-backend/src/main/resources/mapper/SysRoleMapper.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT * FROM sys_role ORDER BY id DESC
+
+
+
+ SELECT * FROM sys_role WHERE id = #{id}
+
+
+
+ INSERT INTO sys_role (name, description, create_time, update_time)
+ VALUES (#{name}, #{description}, #{createTime}, #{updateTime})
+
+
+
+ UPDATE sys_role
+
+ name = #{name},
+ description = #{description},
+
+ WHERE id = #{id}
+
+
+
+ DELETE FROM sys_role WHERE id = #{id}
+
+
+
+ SELECT COUNT(*) FROM sys_role
+
+
+
diff --git a/data-agent-frontend/src/api/sysRole.ts b/data-agent-frontend/src/api/sysRole.ts
new file mode 100644
index 0000000..a1d4d5a
--- /dev/null
+++ b/data-agent-frontend/src/api/sysRole.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+
+import request from './request';
+
+export interface RoleResponse {
+ id: number;
+ name: string;
+ description: string;
+ createTime: string;
+}
+
+export interface RoleRequest {
+ name: string;
+ description: string;
+}
+
+export interface TablePermissionResponse {
+ datasourceId: number;
+ tableNames: string[];
+}
+
+export interface SaveTablePermissionRequest {
+ datasourceId: number;
+ tableNames: string[];
+}
+
+export interface ColumnPermissionResponse {
+ tableName: string;
+ columnNames: string[];
+}
+
+export interface SaveColumnPermissionRequest {
+ datasourceId: number;
+ tableName: string;
+ columnNames: string[];
+}
+
+type ApiResult = { code: number; message: string; data: T };
+
+export function listRoles() {
+ return request.get>('/sys/role').then(res => res.data.data);
+}
+
+export function createRole(payload: RoleRequest) {
+ return request.post>('/sys/role', payload).then(res => res.data.data);
+}
+
+export function updateRole(id: number, payload: RoleRequest) {
+ return request
+ .put>(`/sys/role/${id}`, payload)
+ .then(res => res.data.data);
+}
+
+export function deleteRole(id: number) {
+ return request.delete>(`/sys/role/${id}`).then(res => res.data.data);
+}
+
+export function getPermissions(roleId: number) {
+ return request
+ .get>(`/sys/role/${roleId}/permissions`)
+ .then(res => res.data.data);
+}
+
+export function savePermissions(roleId: number, payload: SaveTablePermissionRequest) {
+ return request
+ .put>(`/sys/role/${roleId}/permissions`, payload)
+ .then(res => res.data.data);
+}
+
+export function getColumnPermissions(roleId: number, datasourceId: number) {
+ return request
+ .get>(`/sys/role/${roleId}/columns`, {
+ params: { datasourceId },
+ })
+ .then(res => res.data.data);
+}
+
+export function saveColumnPermissions(roleId: number, payload: SaveColumnPermissionRequest) {
+ return request
+ .put>(`/sys/role/${roleId}/columns`, payload)
+ .then(res => res.data.data);
+}
+
+/** 一次性获取数据源下所有表的列名,避免 N 次请求。 */
+export function getAllTableColumns(datasourceId: number) {
+ return request
+ .get>>(`/datasource/${datasourceId}/columns`)
+ .then(res => res.data.data);
+}
diff --git a/data-agent-frontend/src/components/layout/AppSidebar.vue b/data-agent-frontend/src/components/layout/AppSidebar.vue
index 8f5d6b2..7b10d20 100644
--- a/data-agent-frontend/src/components/layout/AppSidebar.vue
+++ b/data-agent-frontend/src/components/layout/AppSidebar.vue
@@ -31,6 +31,7 @@
{ path: '/report', title: '报告管理', icon: 'Document' },
{ path: '/metric', title: '指标口径管理', icon: 'DataLine' },
{ path: '/sys-user', title: '用户管理', icon: 'User' },
+ { path: '/sys-role', title: '角色管理', icon: 'Avatar' },
];
const activeMenu = computed(() => route.path);
diff --git a/data-agent-frontend/src/router/index.ts b/data-agent-frontend/src/router/index.ts
index 7264f69..acf89ee 100644
--- a/data-agent-frontend/src/router/index.ts
+++ b/data-agent-frontend/src/router/index.ts
@@ -73,6 +73,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/sys-user/UserManage.vue'),
meta: { title: '用户管理' },
},
+ {
+ path: '/sys-role',
+ name: 'RoleManage',
+ component: () => import('@/views/sys-role/RoleManage.vue'),
+ meta: { title: '角色管理' },
+ },
];
const router = createRouter({
diff --git a/data-agent-frontend/src/views/sys-role/RoleManage.vue b/data-agent-frontend/src/views/sys-role/RoleManage.vue
new file mode 100644
index 0000000..8badda9
--- /dev/null
+++ b/data-agent-frontend/src/views/sys-role/RoleManage.vue
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 权限
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 该数据源下暂无物理表
+
+
+
toggleTable(tableName, val)"
+ style="font-weight: 600"
+ >
+ {{ tableName }}
+
+
+
+ 隐藏列(勾选即对该角色不可见):
+
+
onColumnBlacklistChange(tableName, vals)"
+ >
+
+ {{ col }}
+
+
+
+
+ 该表无列信息
+
+
+
+ 保存权限
+
+
+ 请先选择一个数据源
+
+
+
+
+
diff --git a/data-agent-frontend/src/views/sys-user/UserManage.vue b/data-agent-frontend/src/views/sys-user/UserManage.vue
index f44979b..5b5804b 100644
--- a/data-agent-frontend/src/views/sys-user/UserManage.vue
+++ b/data-agent-frontend/src/views/sys-user/UserManage.vue
@@ -20,10 +20,15 @@
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
import * as sysUserApi from '@/api/sysUser';
+ import { listRoles, type RoleResponse } from '@/api/sysRole';
import type { UserResponse } from '@/api/sysUser';
const loading = ref(false);
const users = ref([]);
+ const roleOptions = ref([]);
+ function roleName(roleId: number) {
+ return roleOptions.value.find(r => r.id === roleId)?.name || String(roleId);
+ }
async function reload() {
loading.value = true;
@@ -148,7 +153,14 @@
}
}
- onMounted(reload);
+ onMounted(() => {
+ reload();
+ listRoles()
+ .then(list => {
+ roleOptions.value = list;
+ })
+ .catch(() => {});
+ });
@@ -165,7 +177,7 @@
- {{ row.roleId === 1 ? '管理员' : '普通用户' }}
+ {{ roleName(row.roleId) }}
@@ -212,8 +224,7 @@
-
-
+
diff --git a/sql/sys_role.sql b/sql/sys_role.sql
new file mode 100644
index 0000000..4b17061
--- /dev/null
+++ b/sql/sys_role.sql
@@ -0,0 +1,36 @@
+-- 权限管理:角色表 + 表级白名单 + 列级黑名单。
+CREATE TABLE IF NOT EXISTS `sys_role` (
+ `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `name` VARCHAR(64) NOT NULL COMMENT '角色名称',
+ `description` VARCHAR(255) NULL COMMENT '角色描述',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色';
+
+-- 角色-表白名单:角色可见的表;缺省不可见(新表不会自动泄露)。
+CREATE TABLE IF NOT EXISTS `role_table_permission` (
+ `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `role_id` INT NOT NULL COMMENT '角色ID',
+ `datasource_id` INT NOT NULL COMMENT '数据源ID',
+ `table_name` VARCHAR(128) NOT NULL COMMENT '物理表名(与 table_info.table_name 一致)',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_role_ds_table` (`role_id`, `datasource_id`, `table_name`),
+ KEY `idx_role_id` (`role_id`),
+ KEY `idx_datasource_id` (`datasource_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色-表 白名单';
+
+-- 角色-隐藏列(黑名单):记录存在 = 该列对角色不可见。缺省不隐藏。
+CREATE TABLE IF NOT EXISTS `role_hidden_column` (
+ `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `role_id` INT NOT NULL COMMENT '角色ID',
+ `datasource_id` INT NOT NULL COMMENT '数据源ID',
+ `table_name` VARCHAR(128) NOT NULL COMMENT '物理表名',
+ `column_name` VARCHAR(128) NOT NULL COMMENT '列名(记录存在即对角色不可见)',
+ `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_role_ds_table_col` (`role_id`, `datasource_id`, `table_name`, `column_name`),
+ KEY `idx_role_id` (`role_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色-隐藏列(黑名单)';