From 9ec2350a2d5de8f40a0ed59e9cc63c67f9b5b783 Mon Sep 17 00:00:00 2001
From: lzq986 <2719916844@qq.com>
Date: Fri, 7 Aug 2026 19:27:48 +0800
Subject: [PATCH 1/2] feat(auth): enforce admin role via @AdminOnly annotation
Add an @AdminOnly annotation and enforce it in AuthInterceptor: when a handler (method overrides class) is annotated and the current user's role_id != 1, return 403. Carry role_id through UserContext, default the bootstrapped admin's role, and gate existing management controllers (datasource, domain, metric, report, semantic, mcp, table-*) with @AdminOnly. Add the env-driven auth config to application.properties.
---
.../malonetalk/annotation/AdminOnly.java | 33 +++++++++++++++++++
.../github/malonetalk/common/UserContext.java | 4 ++-
.../config/AdminBootstrapRunner.java | 2 +-
.../controller/DatasourceController.java | 6 ++++
.../controller/DomainController.java | 2 ++
.../controller/McpServerController.java | 2 ++
.../controller/MetricController.java | 2 ++
.../controller/ReportController.java | 2 ++
.../TableColumnSemanticController.java | 2 ++
.../TableRelationSemanticController.java | 2 ++
.../TableRelationWorkspaceController.java | 2 ++
.../controller/TableSemanticController.java | 2 ++
.../TableSemanticSyncController.java | 2 ++
.../interceptor/AuthInterceptor.java | 22 ++++++++++++-
.../src/main/resources/application.properties | 7 ++++
15 files changed, 89 insertions(+), 3 deletions(-)
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/annotation/AdminOnly.java
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/annotation/AdminOnly.java b/data-agent-backend/src/main/java/io/github/malonetalk/annotation/AdminOnly.java
new file mode 100644
index 0000000..2af3280
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/annotation/AdminOnly.java
@@ -0,0 +1,33 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 标记需要管理员权限的 Controller 方法或类。
+ *
+ *
方法/类上有 {@link AdminOnly} 且当前用户 role_id != 1 时返回
+ * {@link ErrorCode#FORBIDDEN} (403)。表/列拦截、会话隔离 = 后续轮次。
*/
@Component
@AllArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
+ private static final int ADMIN_ROLE_ID = 1;
+
private final JwtUtil jwtUtil;
private final SysUserMapper sysUserMapper;
@@ -58,6 +63,12 @@ public boolean preHandle(
ErrorCode.UNAUTHORIZED, "Account is disabled or does not exist.");
}
UserContext.set(context);
+
+ if (handler instanceof HandlerMethod handlerMethod && isAdminRequired(handlerMethod)) {
+ if (context.roleId() == null || context.roleId() != ADMIN_ROLE_ID) {
+ throw BusinessException.of(ErrorCode.FORBIDDEN, "需要管理员权限");
+ }
+ }
return true;
}
@@ -70,6 +81,15 @@ public void afterCompletion(
UserContext.clear();
}
+ /** 方法或所在类上有 @AdminOnly 注解时要求管理员权限;方法级注解覆盖类级。 */
+ private boolean isAdminRequired(HandlerMethod handlerMethod) {
+ AdminOnly methodAnnotation = handlerMethod.getMethodAnnotation(AdminOnly.class);
+ if (methodAnnotation != null) {
+ return true;
+ }
+ return handlerMethod.getBeanType().isAnnotationPresent(AdminOnly.class);
+ }
+
private String extractBearer(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header == null || header.isBlank()) {
diff --git a/data-agent-backend/src/main/resources/application.properties b/data-agent-backend/src/main/resources/application.properties
index dccb8e1..084e531 100644
--- a/data-agent-backend/src/main/resources/application.properties
+++ b/data-agent-backend/src/main/resources/application.properties
@@ -27,3 +27,10 @@ io.github.malonetalk.model.base-url=
io.github.malonetalk.model.api-key=${IO_GITHUB_MALONETALK_MODEL_API_KEY:}
spring.config.import=classpath:skill.properties
+
+# Auth Configuration (login round): all env-driven, no secrets committed.
+# JWT secret must be >= 32 bytes in production; blank => in-memory random key (dev only, tokens invalidated on restart).
+jwt.secret=${JWT_SECRET:}
+jwt.expiration-hours=${JWT_EXPIRATION_HOURS:24}
+# Initial admin password for first startup when sys_user is empty; fail-closed if unset.
+admin.init-password=${ADMIN_INIT_PASSWORD:}
From 0adb040507862e9b61156455f3254b6851764b2a Mon Sep 17 00:00:00 2001
From: lzq986 <2719916844@qq.com>
Date: Fri, 7 Aug 2026 19:29:01 +0800
Subject: [PATCH 2/2] feat(user): add user management CRUD and admin UI
Add SysUser CRUD (controller/service/mapper/DTOs) with create, update
(displayName only), admin password reset, and enable/disable. Username
uniqueness and external-idp password-reset guards live in the service
layer. Ship the user-management UI: sidebar entry, /sys-user route, and a
UserManage page with list/create/edit/reset-password/toggle-status.
---
.../controller/SysUserController.java | 89 ++++++
.../malonetalk/dto/ResetPasswordRequest.java | 27 ++
.../malonetalk/dto/UserCreateRequest.java | 32 ++
.../github/malonetalk/dto/UserResponse.java | 28 ++
.../malonetalk/dto/UserUpdateRequest.java | 27 ++
.../malonetalk/mapper/SysUserMapper.java | 7 +
.../malonetalk/service/SysUserService.java | 36 +++
.../service/SysUserServiceImpl.java | 109 +++++++
.../main/resources/mapper/SysUserMapper.xml | 21 +-
data-agent-frontend/src/api/sysUser.ts | 70 +++++
.../src/components/layout/AppSidebar.vue | 1 +
data-agent-frontend/src/router/index.ts | 6 +
.../src/views/sys-user/UserManage.vue | 291 ++++++++++++++++++
13 files changed, 743 insertions(+), 1 deletion(-)
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/controller/SysUserController.java
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/ResetPasswordRequest.java
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/UserCreateRequest.java
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/UserResponse.java
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/UserUpdateRequest.java
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserService.java
create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserServiceImpl.java
create mode 100644 data-agent-frontend/src/api/sysUser.ts
create mode 100644 data-agent-frontend/src/views/sys-user/UserManage.vue
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/SysUserController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/SysUserController.java
new file mode 100644
index 0000000..d61f8e6
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/SysUserController.java
@@ -0,0 +1,89 @@
+/*
+ * 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.ResetPasswordRequest;
+import io.github.malonetalk.dto.UserCreateRequest;
+import io.github.malonetalk.dto.UserResponse;
+import io.github.malonetalk.dto.UserUpdateRequest;
+import io.github.malonetalk.service.SysUserService;
+import jakarta.validation.Valid;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+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;
+
+/**
+ * 用户管理 CRUD(权限轮次再加 @AdminOnly;本轮登录后即可用)。
+ *
+ *
username 唯一性由 Service 层保证;password 不允许通过 update 接口修改(需调用重置密码)。
+ */
+@AdminOnly
+@RestController
+@AllArgsConstructor
+@RequestMapping("/api/sys/user")
+@Validated
+public class SysUserController {
+
+ private final SysUserService sysUserService;
+
+ @GetMapping
+ public Result> listAll() {
+ return Result.success(sysUserService.listAll());
+ }
+
+ @PostMapping
+ public Result create(@Valid @RequestBody UserCreateRequest request) {
+ return Result.success(sysUserService.create(request));
+ }
+
+ @PutMapping("/{id}")
+ public Result update(
+ @PathVariable Integer id, @Valid @RequestBody UserUpdateRequest request) {
+ return Result.success(sysUserService.update(id, request));
+ }
+
+ /** 管理员重置用户密码(不需旧密码)。 */
+ @PutMapping("/{id}/password")
+ public Result resetPassword(
+ @PathVariable Integer id, @Valid @RequestBody ResetPasswordRequest request) {
+ sysUserService.resetPassword(id, request.newPassword());
+ return Result.success(true);
+ }
+
+ /** 启 / 停用户。 */
+ @PutMapping("/{id}/status")
+ public Result updateStatus(
+ @PathVariable Integer id,
+ @RequestParam
+ @jakarta.validation.constraints.Min(0)
+ @jakarta.validation.constraints.Max(1)
+ Integer status) {
+ sysUserService.updateStatus(id, status);
+ return Result.success(true);
+ }
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/ResetPasswordRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/ResetPasswordRequest.java
new file mode 100644
index 0000000..3af0a82
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/ResetPasswordRequest.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.Size;
+
+/** 管理员重置用户密码(不需旧密码);区别于 {@link ChangePasswordRequest}(用户自己改,需验旧密码)。 */
+public record ResetPasswordRequest(
+ @NotBlank(message = "newPassword 不能为空")
+ @Size(min = 6, max = 64, message = "newPassword 长度需在 6-64 之间")
+ String newPassword) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserCreateRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserCreateRequest.java
new file mode 100644
index 0000000..a9f08bb
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserCreateRequest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+
+public record UserCreateRequest(
+ @NotBlank(message = "username 不能为空") String username,
+ @NotBlank(message = "password 不能为空")
+ @Size(min = 6, max = 64, message = "password 长度需在 6-64 之间")
+ String password,
+ @NotBlank(message = "displayName 不能为空") String displayName,
+ @NotNull @Min(0) @Max(1) Integer roleId) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserResponse.java
new file mode 100644
index 0000000..300bdd7
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserResponse.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.dto;
+
+import java.time.LocalDateTime;
+
+public record UserResponse(
+ Integer id,
+ String username,
+ String displayName,
+ Integer roleId,
+ Integer status,
+ LocalDateTime createTime) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserUpdateRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserUpdateRequest.java
new file mode 100644
index 0000000..4bb3412
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/UserUpdateRequest.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.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+
+/** 更新用户。roleId 为 null 表示不修改角色。 */
+public record UserUpdateRequest(
+ @NotBlank(message = "displayName 不能为空") String displayName,
+ @Min(0) @Max(1) Integer roleId) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysUserMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysUserMapper.java
index ceefded..77f03f8 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysUserMapper.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/SysUserMapper.java
@@ -19,6 +19,7 @@
import io.github.malonetalk.common.UserContext;
import io.github.malonetalk.entity.SysUser;
+import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -41,4 +42,10 @@ int updatePassword(
@Param("updateTime") java.time.LocalDateTime updateTime);
int countAll();
+
+ List selectAll();
+
+ int update(SysUser user);
+
+ int updateStatus(@Param("id") Integer id, @Param("status") Integer status);
}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserService.java
new file mode 100644
index 0000000..4b2fe4e
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.UserCreateRequest;
+import io.github.malonetalk.dto.UserResponse;
+import io.github.malonetalk.dto.UserUpdateRequest;
+import java.util.List;
+
+public interface SysUserService {
+
+ List listAll();
+
+ UserResponse create(UserCreateRequest request);
+
+ UserResponse update(Integer id, UserUpdateRequest request);
+
+ void resetPassword(Integer id, String newPassword);
+
+ void updateStatus(Integer id, Integer status);
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserServiceImpl.java
new file mode 100644
index 0000000..c2aff40
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/SysUserServiceImpl.java
@@ -0,0 +1,109 @@
+/*
+ * 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.dto.UserCreateRequest;
+import io.github.malonetalk.dto.UserResponse;
+import io.github.malonetalk.dto.UserUpdateRequest;
+import io.github.malonetalk.entity.SysUser;
+import io.github.malonetalk.exception.BusinessException;
+import io.github.malonetalk.mapper.SysUserMapper;
+import io.github.malonetalk.util.PasswordUtil;
+import java.time.LocalDateTime;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class SysUserServiceImpl implements SysUserService {
+
+ private final SysUserMapper sysUserMapper;
+
+ @Override
+ public List listAll() {
+ return sysUserMapper.selectAll().stream().map(this::toResponse).toList();
+ }
+
+ @Override
+ public UserResponse create(UserCreateRequest request) {
+ SysUser existing = sysUserMapper.selectByUsername(request.username());
+ if (existing != null) {
+ throw BusinessException.of(
+ ErrorCode.DATA_CONFLICT, "用户名 '" + request.username() + "' 已存在");
+ }
+ LocalDateTime now = LocalDateTime.now();
+ SysUser user = new SysUser();
+ user.setUsername(request.username());
+ user.setPasswordHash(PasswordUtil.hash(request.password()));
+ user.setDisplayName(request.displayName());
+ user.setRoleId(request.roleId());
+ user.setIdpType("LOCAL");
+ user.setStatus(1);
+ user.setCreateTime(now);
+ user.setUpdateTime(now);
+ sysUserMapper.insert(user);
+ return toResponse(user);
+ }
+
+ @Override
+ public UserResponse update(Integer id, UserUpdateRequest request) {
+ SysUser user = requireUser(id);
+ user.setDisplayName(request.displayName());
+ if (request.roleId() != null) {
+ user.setRoleId(request.roleId());
+ }
+ user.setUpdateTime(LocalDateTime.now());
+ sysUserMapper.update(user);
+ return toResponse(user);
+ }
+
+ @Override
+ public void resetPassword(Integer id, String newPassword) {
+ SysUser user = requireUser(id);
+ if (user.getPasswordHash() == null) {
+ throw BusinessException.of(ErrorCode.BAD_REQUEST, "外部身份源用户无法重置密码");
+ }
+ sysUserMapper.updatePassword(id, PasswordUtil.hash(newPassword), LocalDateTime.now());
+ }
+
+ @Override
+ public void updateStatus(Integer id, Integer status) {
+ requireUser(id);
+ sysUserMapper.updateStatus(id, status);
+ }
+
+ private SysUser requireUser(Integer id) {
+ SysUser user = sysUserMapper.selectById(id);
+ if (user == null) {
+ throw BusinessException.of(ErrorCode.RESOURCE_NOT_FOUND, "用户不存在");
+ }
+ return user;
+ }
+
+ private UserResponse toResponse(SysUser user) {
+ return new UserResponse(
+ user.getId(),
+ user.getUsername(),
+ user.getDisplayName(),
+ user.getRoleId(),
+ user.getStatus(),
+ user.getCreateTime());
+ }
+}
diff --git a/data-agent-backend/src/main/resources/mapper/SysUserMapper.xml b/data-agent-backend/src/main/resources/mapper/SysUserMapper.xml
index f43caa3..58c25d1 100644
--- a/data-agent-backend/src/main/resources/mapper/SysUserMapper.xml
+++ b/data-agent-backend/src/main/resources/mapper/SysUserMapper.xml
@@ -20,6 +20,7 @@
+
@@ -33,7 +34,7 @@
@@ -57,4 +58,22 @@
SELECT COUNT(*) FROM sys_user
+
+
+
+ UPDATE sys_user
+
+ display_name = #{displayName},
+ role_id = #{roleId},
+ update_time = #{updateTime},
+
+ WHERE id = #{id}
+
+
+
+ UPDATE sys_user SET status = #{status}, update_time = NOW() WHERE id = #{id}
+
+
diff --git a/data-agent-frontend/src/api/sysUser.ts b/data-agent-frontend/src/api/sysUser.ts
new file mode 100644
index 0000000..17e9b5f
--- /dev/null
+++ b/data-agent-frontend/src/api/sysUser.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 UserResponse {
+ id: number;
+ username: string;
+ displayName: string;
+ roleId: number; // 1=管理员 0=普通用户
+ status: number; // 1=启用 0=禁用
+ createTime: string;
+}
+
+export interface UserCreateRequest {
+ username: string;
+ password: string;
+ displayName: string;
+ roleId: number;
+}
+
+export interface UserUpdateRequest {
+ displayName: string;
+ roleId: number | null;
+}
+
+type ApiResult = { code: number; message: string; data: T };
+
+export function listUsers() {
+ return request.get>('/sys/user').then(res => res.data.data);
+}
+
+export function createUser(payload: UserCreateRequest) {
+ return request.post>('/sys/user', payload).then(res => res.data.data);
+}
+
+export function updateUser(id: number, payload: UserUpdateRequest) {
+ return request
+ .put>(`/sys/user/${id}`, payload)
+ .then(res => res.data.data);
+}
+
+export function resetPassword(id: number, newPassword: string) {
+ return request
+ .put>(`/sys/user/${id}/password`, { newPassword })
+ .then(res => res.data.data);
+}
+
+export function updateStatus(id: number, status: number) {
+ return request
+ .put>(`/sys/user/${id}/status`, null, {
+ params: { status },
+ })
+ .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 5bf2dec..8f5d6b2 100644
--- a/data-agent-frontend/src/components/layout/AppSidebar.vue
+++ b/data-agent-frontend/src/components/layout/AppSidebar.vue
@@ -30,6 +30,7 @@
{ path: '/semantic', title: '语义管理', icon: 'Collection' },
{ path: '/report', title: '报告管理', icon: 'Document' },
{ path: '/metric', title: '指标口径管理', icon: 'DataLine' },
+ { path: '/sys-user', title: '用户管理', icon: 'User' },
];
const activeMenu = computed(() => route.path);
diff --git a/data-agent-frontend/src/router/index.ts b/data-agent-frontend/src/router/index.ts
index fe2b92d..7264f69 100644
--- a/data-agent-frontend/src/router/index.ts
+++ b/data-agent-frontend/src/router/index.ts
@@ -67,6 +67,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/metric/MetricManage.vue'),
meta: { title: '指标口径管理' },
},
+ {
+ path: '/sys-user',
+ name: 'UserManage',
+ component: () => import('@/views/sys-user/UserManage.vue'),
+ meta: { title: '用户管理' },
+ },
];
const router = createRouter({
diff --git a/data-agent-frontend/src/views/sys-user/UserManage.vue b/data-agent-frontend/src/views/sys-user/UserManage.vue
new file mode 100644
index 0000000..f44979b
--- /dev/null
+++ b/data-agent-frontend/src/views/sys-user/UserManage.vue
@@ -0,0 +1,291 @@
+
+
+
+
+
+