feat(cli): 添加命令行敏感操作门禁和数据库管理功能
- 添加 migrate、seed、reset-all 命令行子命令 - 实现管理员密码门禁机制保护敏感操作 - 添加数据库表结构迁移和种子数据初始化功能 - 实现清空并重建全部表的 reset-all 功能 - 重构种子数据只包含系统必需基础数据,移除演示数据 - 添加 RBAC 权限角色初始化的命令行支持 - 配置命令行密码验证和操作日志记录功能
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/subtle"
|
||||||
"embed"
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -13,6 +16,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"bj_power_wms/internal/config"
|
"bj_power_wms/internal/config"
|
||||||
|
"bj_power_wms/internal/db"
|
||||||
|
"bj_power_wms/internal/eventlog"
|
||||||
"bj_power_wms/internal/handler"
|
"bj_power_wms/internal/handler"
|
||||||
"bj_power_wms/internal/logdaily"
|
"bj_power_wms/internal/logdaily"
|
||||||
"bj_power_wms/internal/svc"
|
"bj_power_wms/internal/svc"
|
||||||
@@ -69,6 +74,102 @@ func openBrowser(url string) {
|
|||||||
_ = cmd.Start()
|
_ = cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// promptPassword 从终端读取一行输入(不隐藏回显,用于运维终端操作)
|
||||||
|
func promptPassword(msg string) string {
|
||||||
|
fmt.Print(msg)
|
||||||
|
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||||
|
return strings.TrimRight(line, "\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminGate 管理员密码门禁:敏感命令行操作(migrate/seed/reset-all)必须输入与配置
|
||||||
|
// Cli.AdminPassword 一致的密码才放行;校验失败记录日志并终止,绝不继续执行(对齐 MES)。
|
||||||
|
func adminGate(c config.Config) bool {
|
||||||
|
pwd := promptPassword("请输入管理员密码以执行 [" + flag.Arg(0) + "]: ")
|
||||||
|
if subtle.ConstantTimeCompare([]byte(pwd), []byte(c.Cli.AdminPassword)) != 1 {
|
||||||
|
logx.Errorf("敏感命令 [%s] 因管理员密码校验失败被终止", flag.Arg(0))
|
||||||
|
fmt.Println("管理员密码不正确,已终止该操作。")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
logx.Infof("敏感命令 [%s] 已通过管理员密码校验", flag.Arg(0))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbConf 从服务配置构造数据库连接配置
|
||||||
|
func dbConf(c config.Config) db.DatabaseConf {
|
||||||
|
return db.DatabaseConf{
|
||||||
|
Host: c.Postgres.Host,
|
||||||
|
Port: c.Postgres.Port,
|
||||||
|
User: c.Postgres.User,
|
||||||
|
Password: c.Postgres.Password,
|
||||||
|
Dbname: c.Postgres.DBName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runMigrate 仅建表/补列(只增,不删任何数据)
|
||||||
|
func runMigrate(c config.Config) {
|
||||||
|
dbc := dbConf(c)
|
||||||
|
if err := db.EnsureDB(dbc); err != nil {
|
||||||
|
logx.Errorf("ensure database failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
client := db.MustNewDB(dbc)
|
||||||
|
defer client.Close()
|
||||||
|
if err := db.AutoMigrate(client); err != nil {
|
||||||
|
logx.Errorf("migrate failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("migrate: 数据库结构迁移成功(只增/补列,未删除任何数据)。")
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSeed 对齐基础主数据 + 权限角色(不删数据;表不存在则先建)
|
||||||
|
func runSeed(c config.Config) {
|
||||||
|
dbc := dbConf(c)
|
||||||
|
if err := db.EnsureDB(dbc); err != nil {
|
||||||
|
logx.Errorf("ensure database failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
client := db.MustNewDB(dbc)
|
||||||
|
defer client.Close()
|
||||||
|
if err := db.AutoMigrate(client); err != nil {
|
||||||
|
logx.Errorf("seed migrate failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := db.SeedIfEmpty(client); err != nil {
|
||||||
|
logx.Errorf("seed failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
svcCtx := &svc.ServiceContext{EntClient: client, EventLog: eventlog.NewWriter(client)}
|
||||||
|
p, r, b := handler.SeedRBAC(svcCtx, "system")
|
||||||
|
fmt.Printf("seed: 基础数据与权限已对齐(新增权限 %d、角色 %d、绑定用户 %d)。\n", p, r, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runResetAll 清空并重建全部表 + 重灌全部种子(基础主数据 + 权限角色)。此操作不可恢复!
|
||||||
|
func runResetAll(c config.Config) {
|
||||||
|
dbc := dbConf(c)
|
||||||
|
if err := db.EnsureDB(dbc); err != nil {
|
||||||
|
logx.Errorf("ensure database failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := db.DropAllTables(dbc); err != nil {
|
||||||
|
logx.Errorf("reset-all drop failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
client := db.MustNewDB(dbc)
|
||||||
|
defer client.Close()
|
||||||
|
if err := db.AutoMigrate(client); err != nil {
|
||||||
|
logx.Errorf("reset-all migrate failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := db.SeedIfEmpty(client); err != nil {
|
||||||
|
logx.Errorf("reset-all seed failed: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
svcCtx := &svc.ServiceContext{EntClient: client, EventLog: eventlog.NewWriter(client)}
|
||||||
|
p, r, b := handler.SeedRBAC(svcCtx, "system")
|
||||||
|
logx.Infof("reset-all 完成:新增权限 %d、角色 %d、绑定用户 %d", p, r, b)
|
||||||
|
fmt.Println("reset-all: 已清空并重建全部表,写入基础主数据与权限(管理员 admin/123456)。")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -83,6 +184,35 @@ func main() {
|
|||||||
logx.SetWriter(logx.NewWriter(w))
|
logx.SetWriter(logx.NewWriter(w))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 命令行子命令:DDL/删库/种子等敏感操作一律显式触发 + 管理员密码门禁,
|
||||||
|
// 绝不随服务启动自动执行(对齐 MES:migrate / seed / reset-all / serve)。
|
||||||
|
switch cmd := flag.Arg(0); cmd {
|
||||||
|
case "reset-all":
|
||||||
|
fmt.Println("reset-all: 即将清空并重建 WMS 全部表,此操作不可恢复!")
|
||||||
|
if !adminGate(c) {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
runResetAll(c)
|
||||||
|
return
|
||||||
|
case "seed":
|
||||||
|
if !adminGate(c) {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
runSeed(c)
|
||||||
|
return
|
||||||
|
case "migrate":
|
||||||
|
if !adminGate(c) {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
runMigrate(c)
|
||||||
|
return
|
||||||
|
case "", "serve":
|
||||||
|
// 正常启动服务:落到下方启动逻辑
|
||||||
|
default:
|
||||||
|
fmt.Println("未知命令: " + cmd + ";可用命令: migrate / seed / reset-all / serve")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
staticHandler := newStaticHandler(webFS)
|
staticHandler := newStaticHandler(webFS)
|
||||||
|
|
||||||
// 兜底路由:已注册的 /api/* 走正常匹配;未注册的非 /api 路径走嵌入式静态资源
|
// 兜底路由:已注册的 /api/* 走正常匹配;未注册的非 /api 路径走嵌入式静态资源
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ Agv:
|
|||||||
Web:
|
Web:
|
||||||
OpenBrowser: true
|
OpenBrowser: true
|
||||||
|
|
||||||
|
# 命令行敏感操作门禁密码(migrate/reset-all/seed 需输入,独立于后台登录账号密码)
|
||||||
|
Cli:
|
||||||
|
AdminPassword: "Hardman_2026"
|
||||||
|
|
||||||
# 附件存储(年/月/日/文件类型/uuid.ext,库内只存相对路径,不存绝对路径)
|
# 附件存储(年/月/日/文件类型/uuid.ext,库内只存相对路径,不存绝对路径)
|
||||||
# 生产环境改 RootDir: /data/bj_power/attachments
|
# 生产环境改 RootDir: /data/bj_power/attachments
|
||||||
Attachment:
|
Attachment:
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ type WebConfig struct {
|
|||||||
OpenBrowser bool `json:",default=true"` // 本机开发时启动后自动打开浏览器;服务器部署设为 false
|
OpenBrowser bool `json:",default=true"` // 本机开发时启动后自动打开浏览器;服务器部署设为 false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CliConfig 命令行敏感操作门禁密码(migrate/reset-all/seed 需输入,独立于后台登录账号密码)。
|
||||||
|
// 与 MES 对齐:DDL/删库等高危操作一律显式触发 + 密码校验,绝不随服务启动自动执行。
|
||||||
|
type CliConfig struct {
|
||||||
|
AdminPassword string `json:",default=Hardman_2026"`
|
||||||
|
}
|
||||||
|
|
||||||
// AttachmentConfig 附件存储 / 归档 / 磁盘监控配置(etc/*.yaml 的 Attachment 段)。
|
// AttachmentConfig 附件存储 / 归档 / 磁盘监控配置(etc/*.yaml 的 Attachment 段)。
|
||||||
// 落盘约定:<RootDir>/年/月/日/<文件类型大写>/<uuid>.<ext>,库内只存相对路径。
|
// 落盘约定:<RootDir>/年/月/日/<文件类型大写>/<uuid>.<ext>,库内只存相对路径。
|
||||||
type AttachmentConfig struct {
|
type AttachmentConfig struct {
|
||||||
@@ -73,6 +79,7 @@ type Config struct {
|
|||||||
Agv AgvConfig
|
Agv AgvConfig
|
||||||
Web WebConfig
|
Web WebConfig
|
||||||
Attachment AttachmentConfig
|
Attachment AttachmentConfig
|
||||||
|
Cli CliConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedisConf Redis 连接配置
|
// RedisConf Redis 连接配置
|
||||||
|
|||||||
@@ -268,3 +268,39 @@ func applyColumnPatches(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DropAllTables 删除 public schema 下全部表(CASCADE 连带索引/序列/外键约束)。
|
||||||
|
// 仅供命令行 reset-all 使用:清空后由 AutoMigrate 重建结构、SeedIfEmpty + handler.SeedRBAC 重灌种子。
|
||||||
|
// 与 MES 的 db.DropAllTables 对齐(逐表 DROP 而非 DROP SCHEMA,保留 schema 权限归属)。
|
||||||
|
func DropAllTables(c DatabaseConf) error {
|
||||||
|
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
|
||||||
|
c.User, c.Password, c.Host, c.Port, c.Dbname)
|
||||||
|
sqlDB, err := sql.Open("pgx", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sqlDB.Close()
|
||||||
|
|
||||||
|
rows, err := sqlDB.Query(`SELECT tablename FROM pg_tables WHERE schemaname='public'`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
for rows.Next() {
|
||||||
|
var t string
|
||||||
|
if err := rows.Scan(&t); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
names = append(names, t)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
for _, t := range names {
|
||||||
|
if _, err := sqlDB.Exec(`DROP TABLE IF EXISTS "` + t + `" CASCADE`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info(fmt.Sprintf("已删除全部表 %d 张", len(names)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,14 +4,21 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
|
||||||
|
|
||||||
"bj_power_wms/ent"
|
"bj_power_wms/ent"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SeedIfEmpty 首次启动时预置基础数据(区域/账号/演示物料与库存)
|
// SeedIfEmpty 首次启动/重置后预置「系统运行必需的基础主数据」,不含任何演示业务数据。
|
||||||
|
//
|
||||||
|
// 生产环境口径(用户裁决 2026-09-20):种子只允许写入系统跑起来所必需的固定主数据,
|
||||||
|
// 严禁预置演示物料/演示库存/演示账号等假业务数据。具体:
|
||||||
|
// - 区域:库房四大基础分区 Z01~Z04(库存落位的根节点,level=1);
|
||||||
|
// - 管理员账号:admin(初始密码 123456,登录后应立即修改);其余账号由管理员在「账号管理」按需创建、分配角色;
|
||||||
|
// - 接驳台:DOCK01~20(产线,DOCKn↔工位n)+ DOCK21(库房收货),系统预置只读主数据(dock.go 不提供增删改)。
|
||||||
|
//
|
||||||
|
// 权限与角色不在此:由 handler.SeedRBAC 幂等初始化(reset-all/seed 命令与 POST /api/rbac/seed 共用同一函数)。
|
||||||
func SeedIfEmpty(client *ent.Client) error {
|
func SeedIfEmpty(client *ent.Client) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -36,106 +43,20 @@ func SeedIfEmpty(client *ent.Client) error {
|
|||||||
slog.Info("seed: zones created")
|
slog.Info("seed: zones created")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 账号(全部统一初始密码 123456)
|
// 2. 管理员账号(唯一预置账号;初始密码 123456,登录后应立即修改)
|
||||||
users, _ := client.User.Query().Count(ctx)
|
users, _ := client.User.Query().Count(ctx)
|
||||||
if users == 0 {
|
if users == 0 {
|
||||||
adminHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
adminHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
||||||
opHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
|
|
||||||
client.User.Create().
|
client.User.Create().
|
||||||
SetUsername("admin").
|
SetUsername("admin").
|
||||||
SetPassword(string(adminHash)).
|
SetPassword(string(adminHash)).
|
||||||
SetRealName("系统管理员").
|
SetRealName("系统管理员").
|
||||||
SetRole("admin").
|
SetRole("admin").
|
||||||
SaveX(ctx)
|
SaveX(ctx)
|
||||||
client.User.Create().
|
slog.Info("seed: admin user created (admin/123456)")
|
||||||
SetUsername("store1").
|
|
||||||
SetPassword(string(opHash)).
|
|
||||||
SetRealName("一号库房保管员").
|
|
||||||
SetRole("operator").
|
|
||||||
SetDept("库房").
|
|
||||||
SaveX(ctx)
|
|
||||||
client.User.Create().
|
|
||||||
SetUsername("insp1").
|
|
||||||
SetPassword(string(opHash)).
|
|
||||||
SetRealName("质检员一号").
|
|
||||||
SetRole("inspector").
|
|
||||||
SetDept("质检").
|
|
||||||
SaveX(ctx)
|
|
||||||
slog.Info("seed: users created (admin/123456, store1/123456, insp1/123456)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 演示物料与库存
|
// 3. 接驳台主数据 DOCK01~20(产线, DOCKn↔工位n) + DOCK21(库房)
|
||||||
materials, _ := client.Material.Query().Count(ctx)
|
|
||||||
if materials == 0 {
|
|
||||||
mats := []struct {
|
|
||||||
code, name, spec, unit string
|
|
||||||
mode int
|
|
||||||
isBatch, isSerial bool
|
|
||||||
}{
|
|
||||||
{"GJ-BAN-001", "10mm钢板(结构件)", "2000x1000x10", "张", 1, true, false},
|
|
||||||
{"GJ-GANGGUAN-002", "无缝钢管114x8", "114x8x6000", "根", 1, true, false},
|
|
||||||
{"LJ-LUOSI-M16", "M16高强度螺栓", "M16x80 10.9级", "件", 1, true, false},
|
|
||||||
{"JM-ZHOUCHENG-6020", "精密轴承6020", "6020-2RS", "套", 2, false, true},
|
|
||||||
{"JM-DIANJI-750W", "伺服电机750W", "ECMA-C21307", "台", 2, false, true},
|
|
||||||
{"BCP-JIEKOUTI", "接驳体半成品", "-", "件", 2, false, true},
|
|
||||||
}
|
|
||||||
for _, m := range mats {
|
|
||||||
client.Material.Create().
|
|
||||||
SetCode(m.code).
|
|
||||||
SetName(m.name).
|
|
||||||
SetNillableSpec(strPtrNil(m.spec)).
|
|
||||||
SetNillableUnit(strPtrNil(m.unit)).
|
|
||||||
SetManageMode(m.mode).
|
|
||||||
SetIsBatchManaged(m.isBatch).
|
|
||||||
SetIsSerialManaged(m.isSerial).
|
|
||||||
SaveX(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 演示库存(统一 inventory 表,manage_mode 区分结构件/电气件)
|
|
||||||
today := time.Now().Format("2006-01-02")
|
|
||||||
// 结构件批次(已检合格)
|
|
||||||
client.Inventory.Create().
|
|
||||||
SetManageMode(1).
|
|
||||||
SetMaterialCode("GJ-BAN-001").
|
|
||||||
SetNillableMaterialName(strPtrNil("10mm钢板(结构件)")).
|
|
||||||
SetBatchNo("B2026060100001").
|
|
||||||
SetQuantity(120).SetLockedQty(0).
|
|
||||||
SetNillableProductionDate(strPtrNil("2026-06-01")).
|
|
||||||
SetNillableSupplier(strPtrNil("宝钢供应商")).
|
|
||||||
SetQualityStatus("合格").
|
|
||||||
SetNillableZoneCode(strPtrNil("Z02")).
|
|
||||||
SetStatus("在库").
|
|
||||||
SaveX(ctx)
|
|
||||||
client.Inventory.Create().
|
|
||||||
SetManageMode(1).
|
|
||||||
SetMaterialCode("GJ-GANGGUAN-002").
|
|
||||||
SetNillableMaterialName(strPtrNil("无缝钢管114x8")).
|
|
||||||
SetBatchNo("B2026060200034").
|
|
||||||
SetQuantity(50).SetLockedQty(0).
|
|
||||||
SetNillableProductionDate(strPtrNil(today)).
|
|
||||||
SetNillableSupplier(strPtrNil("天津钢管")).
|
|
||||||
SetQualityStatus("未检").
|
|
||||||
SetNillableZoneCode(strPtrNil("Z01")).
|
|
||||||
SetStatus("在库").
|
|
||||||
SaveX(ctx)
|
|
||||||
|
|
||||||
// 电气件 SN(在库,合格)
|
|
||||||
for i := 1; i <= 5; i++ {
|
|
||||||
client.Inventory.Create().
|
|
||||||
SetManageMode(2).
|
|
||||||
SetMaterialCode("JM-ZHOUCHENG-6020").
|
|
||||||
SetNillableMaterialName(strPtrNil("精密轴承6020")).
|
|
||||||
SetSnCode("SN20260601001" + itoaPad(i)).
|
|
||||||
SetQuantity(1).SetLockedQty(0).
|
|
||||||
SetQualityStatus("合格").
|
|
||||||
SetNillableZoneCode(strPtrNil("Z02")).
|
|
||||||
SetStatus("在库").
|
|
||||||
SaveX(ctx)
|
|
||||||
}
|
|
||||||
slog.Info("seed: demo materials & stock created")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 接驳台主数据 DOCK01~20(产线, DOCKn↔工位n) + DOCK21(库房)
|
|
||||||
docks, _ := client.Dock.Query().Count(ctx)
|
docks, _ := client.Dock.Query().Count(ctx)
|
||||||
if docks == 0 {
|
if docks == 0 {
|
||||||
for i := 1; i <= 20; i++ {
|
for i := 1; i <= 20; i++ {
|
||||||
@@ -162,31 +83,3 @@ func SeedIfEmpty(client *ent.Client) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func strPtrNil(s string) *string {
|
|
||||||
if s == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func itoaPad(n int) string {
|
|
||||||
base := "000"
|
|
||||||
return base[:len(base)-lenOfInt(n)] + intToStr(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lenOfInt(n int) int {
|
|
||||||
c := 1
|
|
||||||
for n >= 10 {
|
|
||||||
n /= 10
|
|
||||||
c++
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func intToStr(n int) string {
|
|
||||||
if n < 10 {
|
|
||||||
return string(rune('0' + n))
|
|
||||||
}
|
|
||||||
return intToStr(n/10) + string(rune('0'+n%10))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -115,10 +115,10 @@ var seedRoleMeta = []struct {
|
|||||||
{"inspector", "质检员", "质量检验相关"},
|
{"inspector", "质检员", "质量检验相关"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// seedRbacHandler POST /api/rbac/seed 幂等初始化权限与角色(已存在按 code 跳过)
|
// SeedRBAC 幂等初始化权限与角色(已存在按 code 跳过/补齐),返回(新增权限数, 新增角色数, 绑定历史用户数)。
|
||||||
func seedRbacHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
// 接收最小 ServiceContext(命令行 reset-all/seed 仅需 EntClient + EventLog),与 HTTP seedRbacHandler 共用同一逻辑,
|
||||||
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
|
// 保证两条路径灌入完全一致的权限模型。operator 为操作人(HTTP 传登录名,命令行传 "system")。
|
||||||
createdP, createdR := 0, 0
|
func SeedRBAC(ctx *svc.ServiceContext, operator string) (createdP, createdR, bound int) {
|
||||||
for _, p := range seedPermissions {
|
for _, p := range seedPermissions {
|
||||||
exist, _ := ctx.EntClient.Permission.Query().
|
exist, _ := ctx.EntClient.Permission.Query().
|
||||||
Where(permission.CodeEQ(p.Code)).Exist(ctx0())
|
Where(permission.CodeEQ(p.Code)).Exist(ctx0())
|
||||||
@@ -140,7 +140,6 @@ func seedRbacHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
codes := seedRoleCodes()
|
codes := seedRoleCodes()
|
||||||
bound := 0
|
|
||||||
for _, m := range seedRoleMeta {
|
for _, m := range seedRoleMeta {
|
||||||
rl, err := ctx.EntClient.Role.Query().
|
rl, err := ctx.EntClient.Role.Query().
|
||||||
Where(role.CodeEQ(m.Code)).Only(ctx0())
|
Where(role.CodeEQ(m.Code)).Only(ctx0())
|
||||||
@@ -245,10 +244,17 @@ func seedRbacHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
|
|
||||||
// 幂等 seed 只在发生真实变更(新增权限/角色/绑定历史用户)时记录日志,避免每次页面加载刷屏
|
// 幂等 seed 只在发生真实变更(新增权限/角色/绑定历史用户)时记录日志,避免每次页面加载刷屏
|
||||||
if createdP > 0 || createdR > 0 || bound > 0 {
|
if createdP > 0 || createdR > 0 || bound > 0 {
|
||||||
ctx.EventLog.Write(ctx0(), "rbac.seed", r.Header.Get("X-Username"), "rbac", "",
|
ctx.EventLog.Write(ctx0(), "rbac.seed", operator, "rbac", "",
|
||||||
"RBAC 种子初始化", map[string]any{"permissions": createdP, "roles": createdR, "bound": bound})
|
"RBAC 种子初始化", map[string]any{"permissions": createdP, "roles": createdR, "bound": bound})
|
||||||
}
|
}
|
||||||
logx.Infof("rbac seed: 新增权限 %d 条、角色 %d 个、绑定历史用户 %d 个", createdP, createdR, bound)
|
logx.Infof("rbac seed: 新增权限 %d 条、角色 %d 个、绑定历史用户 %d 个", createdP, createdR, bound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// seedRbacHandler POST /api/rbac/seed 幂等初始化权限与角色(复用 SeedRBAC,与命令行 reset-all/seed 同源)
|
||||||
|
func seedRbacHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return requireAdmin(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
createdP, createdR, bound := SeedRBAC(ctx, r.Header.Get("X-Username"))
|
||||||
ok(w, map[string]any{"createdPermissions": createdP, "createdRoles": createdR, "boundUsers": bound})
|
ok(w, map[string]any{"createdPermissions": createdP, "createdRoles": createdR, "boundUsers": bound})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
WMS系统
|
|
||||||
1.仓储作业-入库管理
|
|
||||||
物料和检测单号为必输项,但是前端显示规格不一致,物料未填写时,输入框会爆红,提示请选择物料,检测单号未填写时,输入框不会爆红,但是会有弹窗提示。建议如果必填项未填写的话,输入框既爆红,又会有弹窗提示。
|
|
||||||
2.仓储作业-入库管理
|
|
||||||
结构件入库,电气件 SN 入库,其他入库,这三个都可以添加一个附件上传的功能,就是入库记录页面的附件上传。入库记录页面的附件上传现在存在bug,上传附件后点击查看显示{"code":401,"message":"未登录或 token 缺失"}
|
|
||||||
3.区域维护设计的不合理,我说一下当前的展示效果,每次新建一个区域编码的时候,需要输入区域编码和区域名称,这时候问题就来了,一个区域不止一个货架号吧,但是当前页面只能新建一个货架号,然后选择第几层,选择位置号,一个货架中不止一层,每一次不止一个位置号,这样的话,现在这个区域维护页面需要显示好多重复的区域编码和区域名称,你需要思考一下这个问题,这个地方应该怎么进行优化。然后还存在一个联动问题,区域维护新建好,或者编辑一个区域之后,入库管理页面,在选择区域的时候不会显示刚刚新建的内容,需要刷新一下页面才能显示
|
|
||||||
4.仓储作业-入库管理
|
|
||||||
电气件SN入库把这个名称修改为电气件入库
|
|
||||||
5.仓储作业-入库管理
|
|
||||||
结构类入库:这部分现在存在问题,在选择物料的时候,会出现不止结构件类型的,还出现了,应该在其他入库类型的选项
|
|
||||||
6.wms工单备料台账,存在问题,mes未实现把数据推送到wms工单备料台账,导致wms工单备料台账中没有数据,然后间接导致不能进行出库操作,不能闭环跑完整个流程
|
|
||||||
7.仓储作业-出库管理
|
|
||||||
出库区域选择的时候全是undefined,点击出库会有错误,参数错误: json: unknown field "outboundType"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user