feat(identity): 支持用户和用户组批量管理

增加原子批量启用、禁用和删除接口及管理端多选操作,目标缺失时整批回滚。\n\n拆分管理端与 API Key 权限缓存并在弹窗保存后刷新候选;补齐失效规则一键清理样式、固定右侧操作列和 OpenAPI 契约。
This commit is contained in:
2026-08-03 15:43:49 +08:00
parent 7376d6fab6
commit ad8cdd525b
19 changed files with 1167 additions and 74 deletions
@@ -2,12 +2,83 @@ package httpapi
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// batchGatewayUsers godoc
// @Summary 批量操作用户
// @Description 管理端原子批量启用、禁用或软删除最多 500 个用户;任一目标不存在时整批不变更。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.IdentityBatchInput true "用户批量操作"
// @Success 200 {object} IdentityBatchResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users/batch [post]
func (s *Server) batchGatewayUsers(w http.ResponseWriter, r *http.Request) {
var input store.IdentityBatchInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
result, err := s.store.BatchGatewayUsers(r.Context(), input)
if err != nil {
writeIdentityBatchError(w, s, err, "users")
return
}
writeJSON(w, http.StatusOK, result)
}
// batchUserGroups godoc
// @Summary 批量操作用户组
// @Description 管理端原子批量启用、禁用或删除最多 500 个用户组;删除时同步删除其访问规则,关联默认用户组外键按数据库约束置空。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.IdentityBatchInput true "用户组批量操作"
// @Success 200 {object} IdentityBatchResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/user-groups/batch [post]
func (s *Server) batchUserGroups(w http.ResponseWriter, r *http.Request) {
var input store.IdentityBatchInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
result, err := s.store.BatchUserGroups(r.Context(), input)
if err != nil {
writeIdentityBatchError(w, s, err, "user groups")
return
}
writeJSON(w, http.StatusOK, result)
}
func writeIdentityBatchError(w http.ResponseWriter, s *Server, err error, target string) {
switch {
case errors.Is(err, store.ErrInvalidIdentityBatch):
writeError(w, http.StatusBadRequest, "action must be enable, disable or delete and ids must contain 1 to 500 UUIDs")
case errors.Is(err, store.ErrIdentityBatchTargetNotFound):
writeError(w, http.StatusNotFound, "one or more "+target+" were not found")
default:
s.logger.Error("batch identity operation failed", "target", target, "error", err)
writeError(w, http.StatusInternalServerError, "batch "+target+" operation failed")
}
}
// createTenant godoc
// @Summary 创建租户
// @Description 管理端创建网关租户,tenantKey 和 name 必填。
@@ -144,6 +144,13 @@ type UserGroupListResponse struct {
Items []store.UserGroup `json:"items"`
}
type IdentityBatchResponse struct {
Action string `json:"action" example:"disable"`
RequestedCount int `json:"requestedCount" example:"2"`
AffectedCount int `json:"affectedCount" example:"2"`
IDs []string `json:"ids"`
}
type AccessRuleListResponse struct {
Items []store.AccessRule `json:"items"`
}
+2
View File
@@ -197,6 +197,7 @@ func NewServerWithStores(
mux.Handle("DELETE /api/admin/tenants/{tenantID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.deleteTenant)))
mux.Handle("GET /api/admin/users", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listUsers)))
mux.Handle("POST /api/admin/users", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.createGatewayUser)))
mux.Handle("POST /api/admin/users/batch", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.batchGatewayUsers)))
mux.Handle("PATCH /api/admin/users/{userID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateGatewayUser)))
mux.Handle("PATCH /api/admin/users/{userID}/wallet", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.setUserWalletBalance)))
mux.Handle("POST /api/admin/users/{userID}/wallet/recharge", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.rechargeUserWalletBalance)))
@@ -204,6 +205,7 @@ func NewServerWithStores(
mux.Handle("GET /api/admin/audit-logs", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listAuditLogs)))
mux.Handle("GET /api/admin/user-groups", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listUserGroups)))
mux.Handle("POST /api/admin/user-groups", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.createUserGroup)))
mux.Handle("POST /api/admin/user-groups/batch", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.batchUserGroups)))
mux.Handle("PATCH /api/admin/user-groups/{groupID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.updateUserGroup)))
mux.Handle("DELETE /api/admin/user-groups/{groupID}", server.requireAdmin(auth.PermissionManager, http.HandlerFunc(server.deleteUserGroup)))
mux.Handle("GET /api/admin/access-rules", server.requireAdmin(auth.PermissionPower, http.HandlerFunc(server.listAccessRules)))