2026-09-03 14:20:37 +08:00
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
2026-09-04 14:18:53 +08:00
|
|
|
"strconv"
|
2026-09-03 18:13:59 +08:00
|
|
|
"strings"
|
2026-09-03 14:20:37 +08:00
|
|
|
|
|
|
|
|
"bj_power_wms/ent"
|
|
|
|
|
"bj_power_wms/ent/permission"
|
|
|
|
|
"bj_power_wms/ent/role"
|
|
|
|
|
"bj_power_wms/ent/user"
|
|
|
|
|
"bj_power_wms/internal/svc"
|
|
|
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 种子权限定义:MENU=菜单权限(对应侧栏,path 为路由);BUTTON=按钮级权限(页面内操作)
|
|
|
|
|
type seedPerm struct {
|
|
|
|
|
Code string
|
|
|
|
|
Name string
|
|
|
|
|
Type string
|
|
|
|
|
Path string
|
|
|
|
|
Icon string
|
|
|
|
|
Sort int
|
2026-09-03 18:13:59 +08:00
|
|
|
// Parent 按钮所属菜单编码(BUTTON 填其所属 MENU code;导出等按钮已按菜单拆分,不再用全局 *)
|
|
|
|
|
Parent string
|
2026-09-03 14:20:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var seedPermissions = []seedPerm{
|
|
|
|
|
// ===== 菜单权限 =====
|
2026-09-03 18:13:59 +08:00
|
|
|
{"dashboard:view", "工作台", "MENU", "/", "HomeFilled", 10, ""},
|
|
|
|
|
{"inbound:view", "入库管理", "MENU", "/inbound", "Download", 20, ""},
|
|
|
|
|
{"outbound:view", "出库管理", "MENU", "/outbound", "Upload", 30, ""},
|
|
|
|
|
{"inventory:view", "库存查询", "MENU", "/inventory", "Search", 40, ""},
|
|
|
|
|
{"inspection:view", "质量检验", "MENU", "/inspection", "CircleCheck", 50, ""},
|
|
|
|
|
{"stocktake:view", "库存盘点", "MENU", "/stocktake", "List", 60, ""},
|
|
|
|
|
{"semi:view", "半成品/成品", "MENU", "/semi", "Box", 70, ""},
|
|
|
|
|
{"ledger:view", "备料台账", "MENU", "/ledger", "Tickets", 80, ""},
|
|
|
|
|
{"zone:view", "区域维护", "MENU", "/zone", "Location", 90, ""},
|
|
|
|
|
{"material:view", "物料档案", "MENU", "/material", "Goods", 100, ""},
|
|
|
|
|
{"user:manage", "账号管理", "MENU", "/users", "EditPen", 110, ""},
|
|
|
|
|
{"role:manage", "角色管理", "MENU", "/roles", "Key", 120, ""},
|
2026-09-04 14:18:53 +08:00
|
|
|
{"eventlog:manage", "操作日志", "MENU", "/event-log", "Clock", 130, ""},
|
|
|
|
|
{"agv:manage", "AGV配送", "MENU", "/agv", "Van", 135, ""},
|
2026-09-03 14:20:37 +08:00
|
|
|
// ===== 按钮权限 =====
|
2026-09-03 18:13:59 +08:00
|
|
|
{"inbound:create", "批量入库", "BUTTON", "", "", 200, "inbound:view"},
|
|
|
|
|
{"inbound:import", "Excel导入", "BUTTON", "", "", 201, "inbound:view"},
|
|
|
|
|
{"inbound:export", "导出", "BUTTON", "", "", 202, "inbound:view"},
|
|
|
|
|
{"outbound:create", "发起出库", "BUTTON", "", "", 210, "outbound:view"},
|
|
|
|
|
{"outbound:export", "导出", "BUTTON", "", "", 211, "outbound:view"},
|
|
|
|
|
{"inspection:create", "录入检验", "BUTTON", "", "", 220, "inspection:view"},
|
|
|
|
|
{"inspection:export", "导出", "BUTTON", "", "", 221, "inspection:view"},
|
|
|
|
|
{"inventory:export", "导出", "BUTTON", "", "", 204, "inventory:view"},
|
|
|
|
|
{"stocktake:start", "发起盘点", "BUTTON", "", "", 230, "stocktake:view"},
|
|
|
|
|
{"stocktake:writeback", "差异写回", "BUTTON", "", "", 231, "stocktake:view"},
|
|
|
|
|
{"material:create", "新增物料", "BUTTON", "", "", 240, "material:view"},
|
|
|
|
|
{"material:edit", "编辑物料", "BUTTON", "", "", 241, "material:view"},
|
|
|
|
|
{"material:delete", "删除物料", "BUTTON", "", "", 242, "material:view"},
|
|
|
|
|
{"material:import", "导入", "BUTTON", "", "", 243, "material:view"},
|
|
|
|
|
{"material:export", "导出", "BUTTON", "", "", 244, "material:view"},
|
|
|
|
|
{"zone:create", "新增区域", "BUTTON", "", "", 250, "zone:view"},
|
|
|
|
|
{"zone:edit", "编辑区域", "BUTTON", "", "", 251, "zone:view"},
|
|
|
|
|
{"zone:delete", "删除区域", "BUTTON", "", "", 252, "zone:view"},
|
|
|
|
|
{"user:create", "新增账号", "BUTTON", "", "", 260, "user:manage"},
|
|
|
|
|
{"user:edit", "编辑账号", "BUTTON", "", "", 261, "user:manage"},
|
|
|
|
|
{"user:delete", "删除账号", "BUTTON", "", "", 262, "user:manage"},
|
|
|
|
|
{"role:create", "新增角色", "BUTTON", "", "", 270, "role:manage"},
|
|
|
|
|
{"role:edit", "编辑角色", "BUTTON", "", "", 271, "role:manage"},
|
|
|
|
|
{"role:delete", "删除角色", "BUTTON", "", "", 272, "role:manage"},
|
2026-09-04 14:18:53 +08:00
|
|
|
{"agv:dispatch", "下发AGV", "BUTTON", "", "", 280, "agv:manage"},
|
2026-09-03 18:13:59 +08:00
|
|
|
// 注:导出权限已按菜单拆分(inbound/outbound/inspection/inventory:export),
|
|
|
|
|
// 不再使用全局 *:export,以更精细地控制“谁能在哪个菜单导出”。
|
2026-09-03 14:20:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 三角色预置权限(对齐原硬编码 rolePermissions,并补充按钮权限)
|
|
|
|
|
// admin 全量;operator 除账号/角色管理外全部;inspector 仅质检相关
|
|
|
|
|
func seedRoleCodes() map[string][]string {
|
|
|
|
|
all := make([]string, 0, len(seedPermissions))
|
|
|
|
|
for _, p := range seedPermissions {
|
|
|
|
|
all = append(all, p.Code)
|
|
|
|
|
}
|
|
|
|
|
oper := make([]string, 0, len(seedPermissions))
|
|
|
|
|
for _, p := range seedPermissions {
|
|
|
|
|
if p.Code == "user:manage" || p.Code == "role:manage" ||
|
|
|
|
|
p.Code == "user:create" || p.Code == "user:edit" || p.Code == "user:delete" ||
|
|
|
|
|
p.Code == "role:create" || p.Code == "role:edit" || p.Code == "role:delete" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
oper = append(oper, p.Code)
|
|
|
|
|
}
|
|
|
|
|
insp := []string{
|
|
|
|
|
"dashboard:view", "inventory:view", "inspection:view", "inspection:create",
|
2026-09-03 18:13:59 +08:00
|
|
|
"stocktake:view", "ledger:view", "zone:view", "material:view",
|
|
|
|
|
"inventory:export", "inspection:export", "material:export",
|
2026-09-03 14:20:37 +08:00
|
|
|
}
|
|
|
|
|
return map[string][]string{"admin": all, "operator": oper, "inspector": insp}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var seedRoleMeta = []struct {
|
|
|
|
|
Code string
|
|
|
|
|
Name string
|
|
|
|
|
Remark string
|
|
|
|
|
}{
|
|
|
|
|
{"admin", "管理员", "系统管理员,拥有全部权限"},
|
|
|
|
|
{"operator", "库房保管员", "入库/出库/库存/盘点等日常作业"},
|
|
|
|
|
{"inspector", "质检员", "质量检验相关"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// seedRbacHandler POST /api/rbac/seed 幂等初始化权限与角色(已存在按 code 跳过)
|
|
|
|
|
func seedRbacHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|
|
|
|
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
createdP, createdR := 0, 0
|
|
|
|
|
for _, p := range seedPermissions {
|
|
|
|
|
exist, _ := ctx.EntClient.Permission.Query().
|
|
|
|
|
Where(permission.CodeEQ(p.Code)).Exist(ctx0())
|
|
|
|
|
if exist {
|
2026-09-03 18:13:59 +08:00
|
|
|
// 已存在:幂等补齐 parent_code(新增字段不影响已建角色权限)
|
|
|
|
|
_, _ = ctx.EntClient.Permission.Update().
|
|
|
|
|
Where(permission.CodeEQ(p.Code)).
|
|
|
|
|
SetParentCode(p.Parent).
|
|
|
|
|
Save(ctx0())
|
2026-09-03 14:20:37 +08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
_, err := ctx.EntClient.Permission.Create().
|
|
|
|
|
SetCode(p.Code).SetName(p.Name).SetType(p.Type).
|
2026-09-03 18:13:59 +08:00
|
|
|
SetParentCode(p.Parent).
|
2026-09-03 14:20:37 +08:00
|
|
|
SetPath(p.Path).SetIcon(p.Icon).SetSort(p.Sort).
|
|
|
|
|
Save(ctx0())
|
|
|
|
|
if err == nil {
|
|
|
|
|
createdP++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
codes := seedRoleCodes()
|
|
|
|
|
bound := 0
|
|
|
|
|
for _, m := range seedRoleMeta {
|
|
|
|
|
rl, err := ctx.EntClient.Role.Query().
|
|
|
|
|
Where(role.CodeEQ(m.Code)).Only(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
// 内置角色不存在则创建
|
|
|
|
|
rl, err = ctx.EntClient.Role.Create().
|
|
|
|
|
SetCode(m.Code).SetName(m.Name).SetRemark(m.Remark).
|
|
|
|
|
SetPermissionCodes(codes[m.Code]).
|
|
|
|
|
Save(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
createdR++
|
|
|
|
|
}
|
|
|
|
|
// 把「role 字符串 == 该角色 code」的历史用户绑定到 role_id(幂等,仅绑定 role_id 为空者)
|
|
|
|
|
users, _ := ctx.EntClient.User.Query().
|
|
|
|
|
Where(user.RoleEQ(m.Code), user.RoleIDIsNil()).All(ctx0())
|
|
|
|
|
for _, u := range users {
|
|
|
|
|
if _, err := ctx.EntClient.User.UpdateOneID(u.ID).SetRoleID(rl.ID).Save(ctx0()); err == nil {
|
|
|
|
|
bound++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-03 18:13:59 +08:00
|
|
|
// 迁移历史角色:把“可查看的菜单”自动补上对应导出码(精细化:原全局 *:export 等价于“可见菜单均可导出”)
|
|
|
|
|
// 同时清理可能残留的废弃 *:export。已有角色 seed 不会覆盖,故在此补齐导出能力。
|
|
|
|
|
menuExport := map[string]string{}
|
|
|
|
|
for _, p := range seedPermissions {
|
|
|
|
|
if p.Type == "BUTTON" && p.Parent != "" && p.Name == "导出" {
|
|
|
|
|
menuExport[p.Parent] = p.Code
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
allRoles, _ := ctx.EntClient.Role.Query().All(ctx0())
|
|
|
|
|
for _, rl := range allRoles {
|
|
|
|
|
codes := rl.PermissionCodes
|
|
|
|
|
changed := false
|
|
|
|
|
set := map[string]bool{}
|
|
|
|
|
for _, c := range codes {
|
|
|
|
|
if c == "*:export" {
|
|
|
|
|
changed = true // 移除废弃全局码
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
set[c] = true
|
|
|
|
|
}
|
|
|
|
|
for c := range set {
|
|
|
|
|
if strings.HasSuffix(c, ":view") {
|
|
|
|
|
// c 本身就是菜单权限码(如 inbound:view),与 menuExport 的 key 一致
|
|
|
|
|
if exp, ok := menuExport[c]; ok && !set[exp] {
|
|
|
|
|
set[exp] = true
|
|
|
|
|
changed = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !changed {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
newCodes := make([]string, 0, len(set))
|
|
|
|
|
for c := range set {
|
|
|
|
|
newCodes = append(newCodes, c)
|
|
|
|
|
}
|
|
|
|
|
_, _ = ctx.EntClient.Role.UpdateOneID(rl.ID).SetPermissionCodes(newCodes).Save(ctx0())
|
|
|
|
|
}
|
|
|
|
|
// 强制管理员角色始终拥有全部权限(不允许被缩减),与“admin 拥有所有权限”一致
|
|
|
|
|
if adminRole, aerr := ctx.EntClient.Role.Query().Where(role.CodeEQ("admin")).Only(ctx0()); aerr == nil {
|
|
|
|
|
_ = ctx.EntClient.Role.UpdateOneID(adminRole.ID).SetPermissionCodes(codes["admin"]).Exec(ctx0())
|
|
|
|
|
}
|
|
|
|
|
// 清理已废弃的全局 *:export 权限行(迁移后无角色引用,幂等)
|
|
|
|
|
_, _ = ctx.EntClient.Permission.Delete().Where(permission.CodeEQ("*:export")).Exec(ctx0())
|
|
|
|
|
|
2026-09-04 14:18:53 +08:00
|
|
|
// 幂等 seed 只在发生真实变更(新增权限/角色/绑定历史用户)时记录日志,避免每次页面加载刷屏
|
|
|
|
|
if createdP > 0 || createdR > 0 || bound > 0 {
|
|
|
|
|
ctx.EventLog.Write(ctx0(), "rbac.seed", r.Header.Get("X-Username"), "rbac", "",
|
|
|
|
|
"RBAC 种子初始化", map[string]any{"permissions": createdP, "roles": createdR, "bound": bound})
|
|
|
|
|
}
|
2026-09-03 14:20:37 +08:00
|
|
|
logx.Infof("rbac seed: 新增权限 %d 条、角色 %d 个、绑定历史用户 %d 个", createdP, createdR, bound)
|
|
|
|
|
ok(w, map[string]any{"createdPermissions": createdP, "createdRoles": createdR, "boundUsers": bound})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// listRolesHandler GET /api/roles 角色列表(含权限码)
|
|
|
|
|
func listRolesHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
2026-09-03 18:13:59 +08:00
|
|
|
return requirePerm(ctx, "role:manage", func(w http.ResponseWriter, r *http.Request) {
|
2026-09-03 14:20:37 +08:00
|
|
|
list, err := ctx.EntClient.Role.Query().Order(ent.Asc("id")).All(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ok(w, map[string]any{"list": list})
|
2026-09-03 18:13:59 +08:00
|
|
|
})
|
2026-09-03 14:20:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-04 14:18:53 +08:00
|
|
|
// roleOptionsHandler GET /api/roles/options 角色轻量选项(供账号管理页下拉选择角色)
|
|
|
|
|
// 与角色管理页的 /api/roles 解耦:仅需 user:manage 即可读,不要求 role:manage
|
|
|
|
|
func roleOptionsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|
|
|
|
return requirePerm(ctx, "user:manage", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
list, err := ctx.EntClient.Role.Query().Order(ent.Asc("id")).All(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
out := make([]map[string]any, 0, len(list))
|
|
|
|
|
for _, rl := range list {
|
|
|
|
|
out = append(out, map[string]any{
|
|
|
|
|
"id": rl.ID, "code": rl.Code, "name": rl.Name,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
ok(w, map[string]any{"list": out})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
// createRoleHandler POST /api/roles 新增角色
|
|
|
|
|
func createRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
2026-09-03 18:13:59 +08:00
|
|
|
return requirePerm(ctx, "role:create", func(w http.ResponseWriter, r *http.Request) {
|
2026-09-03 14:20:37 +08:00
|
|
|
var req struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
Remark string `json:"remark"`
|
|
|
|
|
PermissionCodes []string `json:"permissionCodes"`
|
|
|
|
|
}
|
|
|
|
|
if err := parseJSON(r, &req); err != nil || req.Code == "" || req.Name == "" {
|
|
|
|
|
fail(w, http.StatusBadRequest, "角色名称与编码必填")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
exist, _ := ctx.EntClient.Role.Query().Where(role.CodeEQ(req.Code)).Exist(ctx0())
|
|
|
|
|
if exist {
|
|
|
|
|
fail(w, http.StatusConflict, "角色编码已存在: "+req.Code)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
codes := req.PermissionCodes
|
|
|
|
|
if codes == nil {
|
|
|
|
|
codes = []string{}
|
|
|
|
|
}
|
|
|
|
|
rl, err := ctx.EntClient.Role.Create().
|
|
|
|
|
SetName(req.Name).SetCode(req.Code).
|
|
|
|
|
SetNillableRemark(strPtr(req.Remark)).
|
|
|
|
|
SetPermissionCodes(codes).Save(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-04 14:18:53 +08:00
|
|
|
ctx.EventLog.Write(ctx0(), "role.create", r.Header.Get("X-Username"), "role", strconv.Itoa(rl.ID),
|
|
|
|
|
"新增角色 "+req.Name+"("+req.Code+")", map[string]any{"count": len(codes)})
|
2026-09-03 14:20:37 +08:00
|
|
|
ok(w, rl)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// updateRoleHandler POST /api/roles/update 编辑角色(含权限勾选)
|
|
|
|
|
func updateRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
2026-09-03 18:13:59 +08:00
|
|
|
return requirePerm(ctx, "role:edit", func(w http.ResponseWriter, r *http.Request) {
|
2026-09-03 14:20:37 +08:00
|
|
|
var req struct {
|
|
|
|
|
ID int `json:"id"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Remark string `json:"remark"`
|
|
|
|
|
PermissionCodes []string `json:"permissionCodes"`
|
|
|
|
|
}
|
|
|
|
|
if err := parseJSON(r, &req); err != nil || req.ID <= 0 {
|
|
|
|
|
fail(w, http.StatusBadRequest, "参数错误")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-03 18:13:59 +08:00
|
|
|
// 内置管理员角色不允许修改其权限(admin 拥有所有权限,且不可被缩减)
|
|
|
|
|
target, err := ctx.EntClient.Role.Get(ctx0(), req.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusNotFound, "角色不存在")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if target.Code == "admin" {
|
|
|
|
|
fail(w, http.StatusBadRequest, "内置管理员角色不允许修改其权限")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-03 14:20:37 +08:00
|
|
|
// 角色编码创建后不可修改(前端亦置灰),此处只改名称/备注/权限
|
|
|
|
|
upd := ctx.EntClient.Role.UpdateOneID(req.ID)
|
|
|
|
|
if req.Name != "" {
|
|
|
|
|
upd = upd.SetName(req.Name)
|
|
|
|
|
}
|
|
|
|
|
if req.Remark != "" {
|
|
|
|
|
upd = upd.SetRemark(req.Remark)
|
|
|
|
|
}
|
|
|
|
|
if req.PermissionCodes != nil {
|
|
|
|
|
upd = upd.SetPermissionCodes(req.PermissionCodes)
|
|
|
|
|
}
|
|
|
|
|
rl, err := upd.Save(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-04 14:18:53 +08:00
|
|
|
ctx.EventLog.Write(ctx0(), "role.update", r.Header.Get("X-Username"), "role", strconv.Itoa(req.ID),
|
|
|
|
|
"编辑角色 "+target.Name+"("+target.Code+")", map[string]any{"permissionCodes": rl.PermissionCodes})
|
2026-09-03 14:20:37 +08:00
|
|
|
ok(w, rl)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// deleteRoleHandler POST /api/roles/delete 删除角色(内置三角色禁止删除)
|
|
|
|
|
func deleteRoleHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
2026-09-03 18:13:59 +08:00
|
|
|
return requirePerm(ctx, "role:delete", func(w http.ResponseWriter, r *http.Request) {
|
2026-09-03 14:20:37 +08:00
|
|
|
var req struct {
|
|
|
|
|
ID int `json:"id"`
|
|
|
|
|
}
|
|
|
|
|
if err := parseJSON(r, &req); err != nil || req.ID <= 0 {
|
|
|
|
|
fail(w, http.StatusBadRequest, "参数错误")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
target, err := ctx.EntClient.Role.Get(ctx0(), req.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusNotFound, "角色不存在")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if target.Code == "admin" || target.Code == "operator" || target.Code == "inspector" {
|
|
|
|
|
fail(w, http.StatusBadRequest, "内置角色不可删除: "+target.Code)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-08 11:44:04 +08:00
|
|
|
// 删除保护:角色仍被用户引用时禁删(按 role_id 引用 + 旧 role 编码字段双口径)
|
|
|
|
|
used, _ := ctx.EntClient.User.Query().Where(user.Or(user.RoleIDEQ(req.ID), user.RoleEQ(target.Code))).Count(ctx0())
|
|
|
|
|
if used > 0 {
|
|
|
|
|
fail(w, http.StatusConflict, "该角色仍被 "+strconv.Itoa(used)+" 个账号使用,请先在账号管理中调整这些账号的角色")
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-03 14:20:37 +08:00
|
|
|
if err := ctx.EntClient.Role.DeleteOneID(req.ID).Exec(ctx0()); err != nil {
|
|
|
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-04 14:18:53 +08:00
|
|
|
ctx.EventLog.Write(ctx0(), "role.delete", r.Header.Get("X-Username"), "role", strconv.Itoa(req.ID),
|
|
|
|
|
"删除角色 "+target.Name+"("+target.Code+")", nil)
|
2026-09-03 14:20:37 +08:00
|
|
|
ok(w, nil)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// listPermissionsHandler GET /api/permissions 权限列表(菜单+按钮,按 sort 排序)
|
|
|
|
|
func listPermissionsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
list, err := ctx.EntClient.Permission.Query().
|
|
|
|
|
Order(ent.Asc("sort"), ent.Asc("id")).All(ctx0())
|
|
|
|
|
if err != nil {
|
|
|
|
|
fail(w, http.StatusInternalServerError, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ok(w, map[string]any{"list": list})
|
|
|
|
|
}
|
|
|
|
|
}
|