From 371b89ee8da604b94c12cec38d2461940091d4f8 Mon Sep 17 00:00:00 2001 From: kenneth <1185230223@qq.com> Date: Mon, 14 Apr 2025 15:28:51 +0800 Subject: [PATCH] 2 --- cmd/erp.go | 121 +++-- example/main.go | 190 ++++++++ go.mod | 9 +- go.sum | 8 + internal/db/query/sys_user_login_log.sql | 17 +- internal/db/sqlc/querier.go | 2 +- internal/db/sqlc/sys_user_login_log.sql.go | 38 +- internal/erpserver/biz/biz.go | 12 +- internal/erpserver/biz/v1/budget/budget.go | 4 +- .../erpserver/biz/v1/customer/customer.go | 4 +- internal/erpserver/biz/v1/expense/expsense.go | 4 +- internal/erpserver/biz/v1/income/income.go | 4 +- internal/erpserver/biz/v1/project/project.go | 4 +- internal/erpserver/biz/v1/system/category.go | 4 +- internal/erpserver/biz/v1/system/config.go | 4 +- .../erpserver/biz/v1/system/department.go | 4 +- internal/erpserver/biz/v1/system/login_log.go | 55 +-- internal/erpserver/biz/v1/system/menu.go | 4 +- internal/erpserver/biz/v1/system/role.go | 4 +- internal/erpserver/biz/v1/system/system.go | 14 +- internal/erpserver/biz/v1/system/user.go | 90 ++-- internal/erpserver/handler/app.go | 62 +++ internal/erpserver/handler/budget/budget.go | 4 +- internal/erpserver/handler/common/captcha.go | 18 +- internal/erpserver/handler/common/common.go | 36 -- internal/erpserver/handler/common/upload.go | 8 - .../erpserver/handler/customer/customer.go | 4 +- internal/erpserver/handler/expense/expense.go | 4 +- internal/erpserver/handler/handler.go | 90 ---- internal/erpserver/handler/income/income.go | 4 +- internal/erpserver/handler/project/project.go | 4 +- internal/erpserver/handler/router.go | 126 +++++ internal/erpserver/handler/system/audit.go | 16 +- internal/erpserver/handler/system/config.go | 91 ++-- .../erpserver/handler/system/department.go | 52 +- internal/erpserver/handler/system/home.go | 40 +- .../erpserver/handler/system/login_log.go | 17 +- internal/erpserver/handler/system/menu.go | 112 ++--- internal/erpserver/handler/system/role.go | 104 ++-- internal/erpserver/handler/system/system.go | 76 --- internal/erpserver/handler/system/user.go | 116 ++--- internal/erpserver/http.go | 342 ++++++------- internal/erpserver/model/form/user.go | 20 + internal/erpserver/model/system/audit_log.go | 71 +++ internal/erpserver/model/system/config.go | 28 ++ internal/erpserver/model/system/department.go | 32 ++ internal/erpserver/model/system/login_log.go | 56 +++ internal/erpserver/model/system/menu.go | 37 ++ internal/erpserver/model/system/role.go | 34 ++ internal/erpserver/model/system/role_menu.go | 18 + internal/erpserver/model/system/user.go | 55 ++- internal/erpserver/model/view/system.go | 36 ++ internal/erpserver/repository/store.go | 59 +++ .../erpserver/repository/system/audit_log.go | 52 ++ .../erpserver/repository/system/config.go | 51 ++ .../erpserver/repository/system/department.go | 100 ++++ .../erpserver/repository/system/login_log.go | 78 +++ internal/erpserver/repository/system/menu.go | 73 +++ internal/erpserver/repository/system/role.go | 100 ++++ .../erpserver/repository/system/role_menu.go | 37 ++ internal/erpserver/repository/system/user.go | 93 ++++ .../erpserver/service/v1/common/captcha.go | 30 ++ internal/erpserver/service/v1/service.go | 102 ++++ .../erpserver/service/v1/system/audit_log.go | 29 ++ .../erpserver/service/v1/system/config.go | 69 +++ .../erpserver/service/v1/system/department.go | 207 ++++++++ .../erpserver/service/v1/system/login_log.go | 46 ++ internal/erpserver/service/v1/system/menu.go | 457 ++++++++++++++++++ internal/erpserver/service/v1/system/role.go | 210 ++++++++ .../erpserver/service/v1/system/role_menu.go | 32 ++ internal/erpserver/service/v1/system/user.go | 203 ++++++++ internal/erpserver/store/expansion.go | 24 + internal/erpserver/store/store.go | 9 +- internal/erpserver/store/system/login_log.go | 58 +++ internal/erpserver/store/system/role.go | 44 ++ internal/erpserver/store/system/user.go | 47 ++ internal/pkg/database/postgresql.go | 2 +- internal/pkg/middleware/audit.go | 48 +- internal/pkg/middleware/authorize.go | 22 +- internal/pkg/middleware/middleware.go | 21 +- internal/pkg/redis/redis.go | 4 +- internal/pkg/session/session.go | 12 +- internal/pkg/tpl/render.go | 10 +- internal/pkg/tpl/util.go | 9 +- main.go | 15 - web/statics/admin/css/style.css | 33 ++ web/templates/manage/home/dashboard.tmpl | 14 +- .../manage/system/audit_log/list.tmpl | 2 +- .../manage/system/department/list.tmpl | 11 +- .../manage/system/login_log/list.tmpl | 13 +- web/templates/manage/system/menu/list.tmpl | 29 +- web/templates/manage/system/role/list.tmpl | 19 +- web/templates/manage/system/user/list.tmpl | 12 +- 93 files changed, 3757 insertions(+), 1038 deletions(-) create mode 100644 example/main.go create mode 100644 internal/erpserver/handler/app.go delete mode 100644 internal/erpserver/handler/common/common.go delete mode 100644 internal/erpserver/handler/handler.go create mode 100644 internal/erpserver/handler/router.go delete mode 100644 internal/erpserver/handler/system/system.go create mode 100644 internal/erpserver/model/system/audit_log.go create mode 100644 internal/erpserver/model/system/config.go create mode 100644 internal/erpserver/model/system/department.go create mode 100644 internal/erpserver/model/system/login_log.go create mode 100644 internal/erpserver/model/system/menu.go create mode 100644 internal/erpserver/model/system/role.go create mode 100644 internal/erpserver/model/system/role_menu.go create mode 100644 internal/erpserver/repository/store.go create mode 100644 internal/erpserver/repository/system/audit_log.go create mode 100644 internal/erpserver/repository/system/config.go create mode 100644 internal/erpserver/repository/system/department.go create mode 100644 internal/erpserver/repository/system/login_log.go create mode 100644 internal/erpserver/repository/system/menu.go create mode 100644 internal/erpserver/repository/system/role.go create mode 100644 internal/erpserver/repository/system/role_menu.go create mode 100644 internal/erpserver/repository/system/user.go create mode 100644 internal/erpserver/service/v1/common/captcha.go create mode 100644 internal/erpserver/service/v1/service.go create mode 100644 internal/erpserver/service/v1/system/audit_log.go create mode 100644 internal/erpserver/service/v1/system/config.go create mode 100644 internal/erpserver/service/v1/system/department.go create mode 100644 internal/erpserver/service/v1/system/login_log.go create mode 100644 internal/erpserver/service/v1/system/menu.go create mode 100644 internal/erpserver/service/v1/system/role.go create mode 100644 internal/erpserver/service/v1/system/role_menu.go create mode 100644 internal/erpserver/service/v1/system/user.go create mode 100644 internal/erpserver/store/expansion.go create mode 100644 internal/erpserver/store/system/login_log.go create mode 100644 internal/erpserver/store/system/role.go diff --git a/cmd/erp.go b/cmd/erp.go index 406edb3..8f24672 100644 --- a/cmd/erp.go +++ b/cmd/erp.go @@ -5,21 +5,20 @@ import ( "fmt" "log" - db "management/internal/db/sqlc" - "management/internal/erpserver" - "management/internal/erpserver/biz" "management/internal/erpserver/handler" - "management/internal/erpserver/store" + "management/internal/erpserver/repository" + systemrepo "management/internal/erpserver/repository/system" + commonservice "management/internal/erpserver/service/v1/common" + systemservice "management/internal/erpserver/service/v1/system" "management/internal/pkg/binding" "management/internal/pkg/config" "management/internal/pkg/database" - "management/internal/pkg/logger" "management/internal/pkg/middleware" "management/internal/pkg/redis" "management/internal/pkg/session" - "management/internal/pkg/snowflake" "management/internal/pkg/tpl" + "github.com/drhin/logger" "github.com/spf13/cobra" ) @@ -44,52 +43,110 @@ func runErp(ctx context.Context) error { conf, err := config.New(configPath) checkError(err) - logger.New(conf.App.Prod) + // 初始化数据 + // dbinit.InitSeed() - sdb, err := db.NewIStore(ctx, conf.DB) + // mustInit(redis.Init) + + err = binding.SetValidatorTrans("zh") checkError(err) + contextx, err := newAppContext(conf) + checkError(err) + + address := fmt.Sprintf("%s:%d", conf.App.Host, conf.App.Port) + log.Printf("Starting erp manage server on %s", address) + server := InitServer(address, contextx.Router()) + return server.ListenAndServe() +} + +func newAppContext(conf *config.Config) (*handler.AppContext, error) { + // initialize DB dbOptions := &database.PostgreSQLOptions{ Addr: conf.DB.Host, Username: conf.DB.Username, Password: conf.DB.Password, Database: conf.DB.DBName, } - - // 创建并返回数据库连接 gdb, err := database.NewPostgreSQL(dbOptions) - checkError(err) - - store := store.NewStore(gdb) - - // 初始化数据 - // dbinit.InitSeed() - - // mustInit(redis.Init) + if err != nil { + return nil, err + } + repo := repository.NewStore(gdb) + // initialize Redis redis, err := redis.New(conf.Redis) - checkError(err) + if err != nil { + return nil, err + } - session := session.New(sdb.Pool(), conf.App.Prod) + // initialize Session + db, err := gdb.DB() + if err != nil { + return nil, err + } + session := session.New(db, conf.App.Prod) - mustInit(snowflake.Init) + // initialize Logger + log, err := logger.NewProduction() + if err != nil { + return nil, err + } - biz := biz.NewBiz(store, sdb, redis, session) + ctx := &handler.AppContext{ + DB: gdb, + Store: repo, + Redis: redis, + Session: session, + Config: conf, + Log: log, + } - middleware := middleware.New(biz.SystemV1(), session) + setRepository(ctx) + setService(ctx) + setMiddleware(ctx) + if err := setRender(ctx); err != nil { + return nil, err + } - rander, err := tpl.New(session, biz.SystemV1().MenuBiz()) - checkError(err) + return ctx, nil +} - err = binding.SetValidatorTrans("zh") - checkError(err) +func setRepository(ctx *handler.AppContext) { + ctx.UserRepo = systemrepo.NewUserRepository(ctx.Store) + ctx.LoginLogRepo = systemrepo.NewLoginLogRepository(ctx.Store) + ctx.AuditLogRepo = systemrepo.NewAuditLogRepository(ctx.Store) + ctx.RoleRepo = systemrepo.NewRoleRepository(ctx.Store) + ctx.DepartmentRepo = systemrepo.NewDepartmentRepository(ctx.Store) + ctx.ConfigRepo = systemrepo.NewConfigRepository(ctx.Store) + ctx.RoleMenuRepo = systemrepo.NewRoleMenuRepository(ctx.Store) + ctx.MenuRepo = systemrepo.NewMenuRepository(ctx.Store) +} - handler := handler.NewHandler(conf, rander, redis, session, biz, middleware) +func setService(ctx *handler.AppContext) { + ctx.CaptchaService = commonservice.NewCaptchaService() - address := fmt.Sprintf("%s:%d", conf.App.Host, conf.App.Port) - log.Printf("Starting erp manage server on %s", address) - server := InitServer(address, erpserver.NewRouter(handler, middleware)) - return server.ListenAndServe() + ctx.ConfigService = systemservice.NewConfigService(ctx.ConfigRepo, ctx.Redis) + ctx.LoginLogService = systemservice.NewLoginLogService(ctx.LoginLogRepo) + ctx.AuditLogService = systemservice.NewAuditLogService(ctx.AuditLogRepo) + ctx.RoleService = systemservice.NewRoleService(ctx.RoleRepo) + ctx.UserService = systemservice.NewUserService(ctx.Session, ctx.Log, ctx.UserRepo, ctx.RoleService, ctx.LoginLogService) + ctx.DepartmentService = systemservice.NewDepartmentService(ctx.DepartmentRepo) + ctx.RoleMenuService = systemservice.NewRoleMenuService(ctx.RoleMenuRepo) + ctx.MenuService = systemservice.NewMenuService(ctx.Redis, ctx.Store, ctx.MenuRepo, ctx.RoleService, ctx.RoleMenuService) +} + +func setMiddleware(ctx *handler.AppContext) { + ctx.Middleware = middleware.New(ctx.Session, ctx.MenuService, ctx.AuditLogService) +} + +func setRender(ctx *handler.AppContext) error { + var err error + ctx.Render, err = tpl.New(ctx.Session, ctx.MenuService) + if err != nil { + return err + } + return nil } func checkError(err error) { diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..47c2182 --- /dev/null +++ b/example/main.go @@ -0,0 +1,190 @@ +package main + +import ( + "errors" + "fmt" + "sync" + "time" +) + +const ( + // 位数分配 (Bit Allocation) + timestampBits uint8 = 41 + businessIDBits uint8 = 3 + workerIDBits uint8 = 10 + sequenceBits uint8 = 9 // 64 - 1 (sign) - 41 (timestamp) - 3 (business) - 10 (worker) = 9 + + // 最大值 (Max Values) + maxBusinessID int64 = -1 ^ (-1 << businessIDBits) // 2^3 - 1 = 7 + maxWorkerID int64 = -1 ^ (-1 << workerIDBits) // 2^10 - 1 = 1023 + maxSequence int64 = -1 ^ (-1 << sequenceBits) // 2^9 - 1 = 511 + + // 位移量 (Bit Shifts) + workerIDShift = sequenceBits // 9 + businessIDShift = sequenceBits + workerIDBits // 9 + 10 = 19 + timestampShift = sequenceBits + workerIDBits + businessIDBits // 9 + 10 + 3 = 22 + + // 自定义纪元 (Epoch), 单位毫秒. (可以设置为项目上线的日期) + // 例如: 2025-01-01 00:00:00 UTC + customEpoch int64 = 1735689600000 // time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli() +) + +// ErrClockMovedBackwards indicates that the system clock moved backwards. +var ErrClockMovedBackwards = errors.New("clock moved backwards, refusing to generate id") + +// ErrInvalidBusinessID indicates the business ID is out of range. +var ErrInvalidBusinessID = fmt.Errorf("business ID must be between 0 and %d", maxBusinessID) + +// ErrInvalidWorkerID indicates the worker ID is out of range. +var ErrInvalidWorkerID = fmt.Errorf("worker ID must be between 0 and %d", maxWorkerID) + +// Generator is the core ID generator structure. +type Generator struct { + mu sync.Mutex + lastTimestamp int64 + workerID int64 + businessID int64 + sequence int64 +} + +// NewGenerator creates a new ID generator instance. +func NewGenerator(workerID, businessID int64) (*Generator, error) { + if workerID < 0 || workerID > maxWorkerID { + return nil, ErrInvalidWorkerID + } + if businessID < 0 || businessID > maxBusinessID { + return nil, ErrInvalidBusinessID + } + + return &Generator{ + workerID: workerID, + businessID: businessID, + }, nil +} + +// currentTimeMillis returns the current time in milliseconds since the custom epoch. +func currentTimeMillis() int64 { + return time.Now().UnixMilli() - customEpoch +} + +// tilNextMillis waits until the next millisecond. +func tilNextMillis(lastTimestamp int64) int64 { + timestamp := currentTimeMillis() + for timestamp <= lastTimestamp { + // Spin wait is generally discouraged, but for millisecond precision, + // time.Sleep(1 * time.Millisecond) might overshoot too much. + // A brief sleep can reduce CPU churn if clock skew is minor. + time.Sleep(time.Microsecond * 100) // Sleep briefly + timestamp = currentTimeMillis() + } + return timestamp +} + +// NextID generates the next unique ID. +func (g *Generator) NextID() (int64, error) { + g.mu.Lock() + defer g.mu.Unlock() + + timestamp := currentTimeMillis() + + // 时钟回拨检查 (Clock moved backwards check) + if timestamp < g.lastTimestamp { + // 可以选择: + // 1. 返回错误 (Recommended for safety) + return 0, fmt.Errorf("%w: current: %d, last: %d", ErrClockMovedBackwards, timestamp, g.lastTimestamp) + // 2. 等待时钟追上 (Potentially blocks, less safe if clock jump is large) + // timestamp = tilNextMillis(g.lastTimestamp) + } + + // 同一毫秒内 (Within the same millisecond) + if timestamp == g.lastTimestamp { + g.sequence = (g.sequence + 1) & maxSequence + // 序列号溢出,等待下一毫秒 (Sequence overflow, wait for next millisecond) + if g.sequence == 0 { + timestamp = tilNextMillis(g.lastTimestamp) + // 等待后重置序列号 (Reset sequence after waiting) + // g.sequence = 0 // Reset is implicit as it overflowed to 0 + } + } else { + // 新的毫秒,重置序列号 (New millisecond, reset sequence) + g.sequence = 0 + } + + // 更新最后时间戳 (Update last timestamp) + g.lastTimestamp = timestamp + + // 组合 ID (Assemble the ID) + id := (timestamp << timestampShift) | // 时间戳左移 + (g.businessID << businessIDShift) | // 业务ID左移 + (g.workerID << workerIDShift) | // Worker ID左移 + g.sequence // 序列号 + + return id, nil +} + +// ParseID decomposes an ID into its components. Useful for debugging or analysis. +func ParseID(id int64) (timestampMsSinceEpoch int64, businessID int64, workerID int64, sequence int64, genTimeUTC time.Time) { + timestampMsSinceEpoch = (id >> timestampShift) & (int64(-1) ^ (int64(-1) << timestampBits)) + businessID = (id >> businessIDShift) & maxBusinessID + workerID = (id >> workerIDShift) & maxWorkerID + sequence = id & maxSequence + + // Calculate generation time in UTC + genTimeUTC = time.UnixMilli(timestampMsSinceEpoch + customEpoch).UTC() + + return +} + +func main() { + // !!! IMPORTANT: Worker ID and Business ID MUST be unique per instance/purpose !!! + // These should typically come from configuration. + workerID := int64(1) // Example: Get from config/env + businessIDOrder := int64(1) // Example: 1 for Order + businessIDPayment := int64(2) // Example: 2 for Payment + + // Create generators for different business types + orderGenerator, err := NewGenerator(workerID, businessIDOrder) + if err != nil { + panic(err) + } + + paymentGenerator, err := NewGenerator(workerID, businessIDPayment) + if err != nil { + panic(err) + } + + // Generate some IDs + for i := 0; i < 5; i++ { + orderID, err := orderGenerator.NextID() + if err != nil { + fmt.Println("Error generating order ID:", err) + time.Sleep(1 * time.Millisecond) // Wait before retry on clock issues + continue + } + fmt.Printf("Generated Order ID: %d\n", orderID) + // You can optionally add a human-readable prefix when displaying/logging + fmt.Printf(" Readable Order ID: ORD-%d\n", orderID) + + // Parse it back (for demonstration) + ts, biz, wkr, seq, genTime := ParseID(orderID) + fmt.Printf(" Parsed: Timestamp=%d, Business=%d, Worker=%d, Sequence=%d, GenTime=%s\n", ts, biz, wkr, seq, genTime.Format(time.RFC3339Nano)) + + paymentID, err := paymentGenerator.NextID() + if err != nil { + fmt.Println("Error generating payment ID:", err) + continue + } + fmt.Printf("Generated Payment ID: %d\n", paymentID) + fmt.Printf(" Readable Payment ID: PAY-%d\n", paymentID) + + tsP, bizP, wkrP, seqP, genTimeP := ParseID(paymentID) + fmt.Printf(" Parsed: Timestamp=%d, Business=%d, Worker=%d, Sequence=%d, GenTime=%s\n\n", tsP, bizP, wkrP, seqP, genTimeP.Format(time.RFC3339Nano)) + + time.Sleep(5 * time.Millisecond) // Simulate time passing + } + + // Example of how to get shard key (assuming 64 shards) + orderIDForSharding, _ := orderGenerator.NextID() + shardIndex := orderIDForSharding % 64 + fmt.Printf("\nOrder ID %d would route to Shard Index %d\n", orderIDForSharding, shardIndex) +} diff --git a/go.mod b/go.mod index 63bfc8a..0956c0d 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/alexedwards/scs/pgxstore v0.0.0-20250212122300-421ef1d8611c github.com/alexedwards/scs/v2 v2.8.0 github.com/bwmarrin/snowflake v0.3.0 + github.com/drhin/logger v0.0.0-20250407025020-f3e0beed768b github.com/fsnotify/fsnotify v1.8.0 github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 github.com/gin-gonic/gin v1.10.0 @@ -32,21 +33,23 @@ require ( github.com/spf13/viper v1.20.1 github.com/sqids/sqids-go v0.4.1 github.com/zhang2092/browser v0.0.2 + go.uber.org/zap v1.27.0 golang.org/x/crypto v0.36.0 + gorm.io/driver/postgres v1.5.11 gorm.io/gorm v1.25.12 ) require ( - github.com/BurntSushi/toml v1.4.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect + github.com/alexedwards/scs/postgresstore v0.0.0-20250212122300-421ef1d8611c // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bytedance/sonic v1.12.9 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/drhin/logger v0.0.0-20250407005450-5bbde7825366 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/gin-contrib/sse v1.0.0 // indirect @@ -80,7 +83,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.14.0 // indirect golang.org/x/image v0.25.0 // indirect golang.org/x/net v0.38.0 // indirect @@ -91,5 +93,4 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.5.11 // indirect ) diff --git a/go.sum b/go.sum index 90be268..9c85783 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= @@ -9,6 +10,8 @@ github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyY github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= github.com/alexedwards/scs/pgxstore v0.0.0-20250212122300-421ef1d8611c h1:Y33ELOUUjGGV7p99OU8MXrmSKhOayEPtQ26qDr0LcRg= github.com/alexedwards/scs/pgxstore v0.0.0-20250212122300-421ef1d8611c/go.mod h1:hwveArYcjyOK66EViVgVU5Iqj7zyEsWjKXMQhDJrTLI= +github.com/alexedwards/scs/postgresstore v0.0.0-20250212122300-421ef1d8611c h1:VNg1Uj7ICuqGP7AH6lQwLfzpLRe0VAesYOhwBt7D8uQ= +github.com/alexedwards/scs/postgresstore v0.0.0-20250212122300-421ef1d8611c/go.mod h1:TDDdV/xnjj+/4zBQ9a2k+i2AbuAdY7SQjPUh5zoTZ3M= github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw= github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -39,6 +42,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/drhin/logger v0.0.0-20250407005450-5bbde7825366 h1:HcCQ0kAGAiprUVYDw54vMcC9n9TSX3co0ro+l44TcrE= github.com/drhin/logger v0.0.0-20250407005450-5bbde7825366/go.mod h1:XfunbMqKGMTCr3jsu5Vwe7kVUAINPOi/4z+e8qvVJDE= +github.com/drhin/logger v0.0.0-20250407025020-f3e0beed768b h1:VkXK9/bFutQXrFYcHcbxzkvMHAP9dybMJRMLEMPtKro= +github.com/drhin/logger v0.0.0-20250407025020-f3e0beed768b/go.mod h1:QUg+qnn7zvYONlsRRGWVKI4zArm4vZN2e5JFTydJICM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= @@ -134,6 +139,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -213,6 +219,8 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zhang2092/browser v0.0.2 h1:4jyWWkSCabGxqn74lCDvNnA4o9TlmsUXuwQHPZFsflQ= github.com/zhang2092/browser v0.0.2/go.mod h1:k/HIdVgWmpi9WvGuIU8pu8aK4rMCI4vr0ICCsk5H8T8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= diff --git a/internal/db/query/sys_user_login_log.sql b/internal/db/query/sys_user_login_log.sql index 01a210d..4172735 100644 --- a/internal/db/query/sys_user_login_log.sql +++ b/internal/db/query/sys_user_login_log.sql @@ -19,8 +19,21 @@ WHERE created_at BETWEEN @start_at AND @end_at AND (@username::text = '' OR username ILIKE '%' || @username || '%'); -- name: ListSysUserLoginLogCondition :many -SELECT * FROM sys_user_login_log -WHERE created_at BETWEEN @start_at AND @end_at +SELECT + id, + created_at, + email, + COALESCE((SELECT username FROM sys_user WHERE email = sys_user_login_log.email LIMIT 1), '') AS username, + user_uuid, + is_success, + message, + referer_url, + url, + os, + ip, + browser +FROM sys_user_login_log +WHERE sys_user_login_log.created_at BETWEEN @start_at AND @end_at AND (@email::text = '' OR email ILIKE '%' || @email || '%') AND (@username::text = '' OR username ILIKE '%' || @username || '%') ORDER BY created_at DESC diff --git a/internal/db/sqlc/querier.go b/internal/db/sqlc/querier.go index ea061d5..4a71264 100644 --- a/internal/db/sqlc/querier.go +++ b/internal/db/sqlc/querier.go @@ -87,7 +87,7 @@ type Querier interface { ListSysUser(ctx context.Context) ([]*SysUser, error) ListSysUserByIds(ctx context.Context, dollar_1 []int32) ([]*SysUser, error) ListSysUserCondition(ctx context.Context, arg *ListSysUserConditionParams) ([]*ListSysUserConditionRow, error) - ListSysUserLoginLogCondition(ctx context.Context, arg *ListSysUserLoginLogConditionParams) ([]*SysUserLoginLog, error) + ListSysUserLoginLogCondition(ctx context.Context, arg *ListSysUserLoginLogConditionParams) ([]*ListSysUserLoginLogConditionRow, error) RecursiveSysMenus(ctx context.Context) ([]*RecursiveSysMenusRow, error) RecursiveSysMenusByRoleID(ctx context.Context, roleID int32) ([]*RecursiveSysMenusByRoleIDRow, error) StatisticsExpense(ctx context.Context) ([]*StatisticsExpenseRow, error) diff --git a/internal/db/sqlc/sys_user_login_log.sql.go b/internal/db/sqlc/sys_user_login_log.sql.go index 46237cf..9cd8cc6 100644 --- a/internal/db/sqlc/sys_user_login_log.sql.go +++ b/internal/db/sqlc/sys_user_login_log.sql.go @@ -85,8 +85,21 @@ func (q *Queries) CreateSysUserLoginLog(ctx context.Context, arg *CreateSysUserL } const listSysUserLoginLogCondition = `-- name: ListSysUserLoginLogCondition :many -SELECT id, created_at, email, username, user_uuid, is_success, message, referer_url, url, os, ip, browser FROM sys_user_login_log -WHERE created_at BETWEEN $1 AND $2 +SELECT + id, + created_at, + email, + COALESCE((SELECT username FROM sys_user WHERE email = sys_user_login_log.email LIMIT 1), '') AS username, + user_uuid, + is_success, + message, + referer_url, + url, + os, + ip, + browser +FROM sys_user_login_log +WHERE sys_user_login_log.created_at BETWEEN $1 AND $2 AND ($3::text = '' OR email ILIKE '%' || $3 || '%') AND ($4::text = '' OR username ILIKE '%' || $4 || '%') ORDER BY created_at DESC @@ -103,7 +116,22 @@ type ListSysUserLoginLogConditionParams struct { Size int32 `json:"size"` } -func (q *Queries) ListSysUserLoginLogCondition(ctx context.Context, arg *ListSysUserLoginLogConditionParams) ([]*SysUserLoginLog, error) { +type ListSysUserLoginLogConditionRow struct { + ID int64 `json:"id"` + CreatedAt time.Time `json:"created_at"` + Email string `json:"email"` + Username interface{} `json:"username"` + UserUuid uuid.UUID `json:"user_uuid"` + IsSuccess bool `json:"is_success"` + Message string `json:"message"` + RefererUrl string `json:"referer_url"` + Url string `json:"url"` + Os string `json:"os"` + Ip string `json:"ip"` + Browser string `json:"browser"` +} + +func (q *Queries) ListSysUserLoginLogCondition(ctx context.Context, arg *ListSysUserLoginLogConditionParams) ([]*ListSysUserLoginLogConditionRow, error) { rows, err := q.db.Query(ctx, listSysUserLoginLogCondition, arg.StartAt, arg.EndAt, @@ -116,9 +144,9 @@ func (q *Queries) ListSysUserLoginLogCondition(ctx context.Context, arg *ListSys return nil, err } defer rows.Close() - items := []*SysUserLoginLog{} + items := []*ListSysUserLoginLogConditionRow{} for rows.Next() { - var i SysUserLoginLog + var i ListSysUserLoginLogConditionRow if err := rows.Scan( &i.ID, &i.CreatedAt, diff --git a/internal/erpserver/biz/biz.go b/internal/erpserver/biz/biz.go index aad3fab..ba4cd02 100644 --- a/internal/erpserver/biz/biz.go +++ b/internal/erpserver/biz/biz.go @@ -12,6 +12,8 @@ import ( "management/internal/erpserver/store" "management/internal/pkg/redis" "management/internal/pkg/session" + + "github.com/drhin/logger" ) // IBiz 定义了业务层需要实现的方法. @@ -36,20 +38,22 @@ type IBiz interface { type biz struct { database store.IStore store db.Store - redis redis.IRedis - session session.ISession + redis redis.RedisCache + session session.Session + logger *logger.Logger } // 确保 biz 实现了 IBiz 接口. var _ IBiz = (*biz)(nil) // NewBiz 创建一个 IBiz 类型的实例. -func NewBiz(database store.IStore, store db.Store, redis redis.IRedis, session session.ISession) *biz { +func NewBiz(database store.IStore, store db.Store, redis redis.RedisCache, session session.Session, logger *logger.Logger) *biz { return &biz{ database: database, store: store, redis: redis, session: session, + logger: logger, } } @@ -60,7 +64,7 @@ func (b *biz) CommonV1() commonv1.CommonBiz { // SystemV1 返回一个实现了 SystemBiz 接口的实例. func (b *biz) SystemV1() systemv1.SystemBiz { - return systemv1.New(b.database, b.store, b.redis, b.session) + return systemv1.New(b.database, b.store, b.redis, b.session, b.logger) } func (b *biz) ProjectV1() projectv1.ProjectBiz { diff --git a/internal/erpserver/biz/v1/budget/budget.go b/internal/erpserver/biz/v1/budget/budget.go index c26982a..5d91dbe 100644 --- a/internal/erpserver/biz/v1/budget/budget.go +++ b/internal/erpserver/biz/v1/budget/budget.go @@ -16,12 +16,12 @@ type BudgetBiz interface { type budgetBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ BudgetBiz = (*budgetBiz)(nil) -func New(store db.Store, redis redis.IRedis) *budgetBiz { +func New(store db.Store, redis redis.RedisCache) *budgetBiz { return &budgetBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/customer/customer.go b/internal/erpserver/biz/v1/customer/customer.go index bf0962e..aead9eb 100644 --- a/internal/erpserver/biz/v1/customer/customer.go +++ b/internal/erpserver/biz/v1/customer/customer.go @@ -16,12 +16,12 @@ type CustomerBiz interface { type customerBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ CustomerBiz = (*customerBiz)(nil) -func New(store db.Store, redis redis.IRedis) *customerBiz { +func New(store db.Store, redis redis.RedisCache) *customerBiz { return &customerBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/expense/expsense.go b/internal/erpserver/biz/v1/expense/expsense.go index cfa97a3..25a48b6 100644 --- a/internal/erpserver/biz/v1/expense/expsense.go +++ b/internal/erpserver/biz/v1/expense/expsense.go @@ -22,12 +22,12 @@ type ExpenseBiz interface { type expenseBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ ExpenseBiz = (*expenseBiz)(nil) -func New(store db.Store, redis redis.IRedis) *expenseBiz { +func New(store db.Store, redis redis.RedisCache) *expenseBiz { return &expenseBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/income/income.go b/internal/erpserver/biz/v1/income/income.go index fac15be..4288609 100644 --- a/internal/erpserver/biz/v1/income/income.go +++ b/internal/erpserver/biz/v1/income/income.go @@ -22,12 +22,12 @@ type IncomeBiz interface { type incomeBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ IncomeBiz = (*incomeBiz)(nil) -func New(store db.Store, redis redis.IRedis) *incomeBiz { +func New(store db.Store, redis redis.RedisCache) *incomeBiz { return &incomeBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/project/project.go b/internal/erpserver/biz/v1/project/project.go index fd475df..a18f989 100644 --- a/internal/erpserver/biz/v1/project/project.go +++ b/internal/erpserver/biz/v1/project/project.go @@ -31,12 +31,12 @@ type ProjectExpansion interface { type projectBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ ProjectBiz = (*projectBiz)(nil) -func New(store db.Store, redis redis.IRedis) *projectBiz { +func New(store db.Store, redis redis.RedisCache) *projectBiz { return &projectBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/system/category.go b/internal/erpserver/biz/v1/system/category.go index 090d56c..fbd0a8a 100644 --- a/internal/erpserver/biz/v1/system/category.go +++ b/internal/erpserver/biz/v1/system/category.go @@ -40,12 +40,12 @@ type CategoryBiz interface { type categoryBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ CategoryBiz = (*categoryBiz)(nil) -func NewCategory(store db.Store, redis redis.IRedis) *categoryBiz { +func NewCategory(store db.Store, redis redis.RedisCache) *categoryBiz { return &categoryBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/system/config.go b/internal/erpserver/biz/v1/system/config.go index b38a140..e15b224 100644 --- a/internal/erpserver/biz/v1/system/config.go +++ b/internal/erpserver/biz/v1/system/config.go @@ -27,12 +27,12 @@ type ConfigExpansion interface { type configBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ ConfigBiz = (*configBiz)(nil) -func NewConfig(store db.Store, redis redis.IRedis) *configBiz { +func NewConfig(store db.Store, redis redis.RedisCache) *configBiz { return &configBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/system/department.go b/internal/erpserver/biz/v1/system/department.go index 04e139c..510dc5e 100644 --- a/internal/erpserver/biz/v1/system/department.go +++ b/internal/erpserver/biz/v1/system/department.go @@ -36,12 +36,12 @@ type DepartmentExpansion interface{} type departmentBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ DepartmentBiz = (*departmentBiz)(nil) -func NewDepartment(store db.Store, redis redis.IRedis) *departmentBiz { +func NewDepartment(store db.Store, redis redis.RedisCache) *departmentBiz { return &departmentBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/system/login_log.go b/internal/erpserver/biz/v1/system/login_log.go index 9abe50a..c1c040b 100644 --- a/internal/erpserver/biz/v1/system/login_log.go +++ b/internal/erpserver/biz/v1/system/login_log.go @@ -2,68 +2,41 @@ package system import ( "context" - "time" "management/internal/db/model/dto" db "management/internal/db/sqlc" + systemmodel "management/internal/erpserver/model/system" + "management/internal/erpserver/store" ) type LoginLogBiz interface { - Create(ctx context.Context, arg *db.CreateSysUserLoginLogParams) error - List(ctx context.Context, q dto.SearchDto) ([]*db.SysUserLoginLog, int64, error) + Create(ctx context.Context, obj *systemmodel.LoginLog) error + List(ctx context.Context, q dto.SearchDto) ([]*systemmodel.LoginLog, int64, error) } type loginLogBiz struct { - store db.Store + database store.IStore + store db.Store } var _ LoginLogBiz = (*loginLogBiz)(nil) -func NewLoginLog(store db.Store) *loginLogBiz { +func NewLoginLog(database store.IStore, store db.Store) *loginLogBiz { return &loginLogBiz{ - store: store, + database: database, + store: store, } } -func (b *loginLogBiz) Create(ctx context.Context, arg *db.CreateSysUserLoginLogParams) error { - return b.store.CreateSysUserLoginLog(ctx, arg) +func (b *loginLogBiz) Create(ctx context.Context, obj *systemmodel.LoginLog) error { + return b.database.LoginLog().Create(ctx, obj) } -func (b *loginLogBiz) List(ctx context.Context, q dto.SearchDto) ([]*db.SysUserLoginLog, int64, error) { - start, err := time.ParseInLocation(time.DateTime, q.SearchTimeBegin, time.Local) - if err != nil { - return nil, 0, err - } - end, err := time.ParseInLocation(time.DateTime, q.SearchTimeEnd, time.Local) +func (b *loginLogBiz) List(ctx context.Context, q dto.SearchDto) ([]*systemmodel.LoginLog, int64, error) { + res, count, err := b.database.LoginLog().List(ctx, q) if err != nil { return nil, 0, err } - countArg := &db.CountSysUserLoginLogConditionParams{ - StartAt: start, - EndAt: end, - Email: q.SearchEmail, - Username: q.SearchName, - } - - dataArg := &db.ListSysUserLoginLogConditionParams{ - StartAt: start, - EndAt: end, - Email: q.SearchEmail, - Username: q.SearchName, - Skip: (int32(q.Page) - 1) * int32(q.Rows), - Size: int32(q.Rows), - } - - count, err := b.store.CountSysUserLoginLogCondition(ctx, countArg) - if err != nil { - return nil, 0, err - } - - logs, err := b.store.ListSysUserLoginLogCondition(ctx, dataArg) - if err != nil { - return nil, 0, err - } - - return logs, count, nil + return res, count, nil } diff --git a/internal/erpserver/biz/v1/system/menu.go b/internal/erpserver/biz/v1/system/menu.go index 4d5fe58..28bd945 100644 --- a/internal/erpserver/biz/v1/system/menu.go +++ b/internal/erpserver/biz/v1/system/menu.go @@ -42,12 +42,12 @@ type MenuExpansion interface { type menuBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ MenuBiz = (*menuBiz)(nil) -func NewMenu(store db.Store, redis redis.IRedis) *menuBiz { +func NewMenu(store db.Store, redis redis.RedisCache) *menuBiz { return &menuBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/system/role.go b/internal/erpserver/biz/v1/system/role.go index a085702..fbfac4a 100644 --- a/internal/erpserver/biz/v1/system/role.go +++ b/internal/erpserver/biz/v1/system/role.go @@ -37,12 +37,12 @@ type RoleExpansion interface{} type roleBiz struct { store db.Store - redis redis.IRedis + redis redis.RedisCache } var _ RoleBiz = (*roleBiz)(nil) -func NewRole(store db.Store, redis redis.IRedis) *roleBiz { +func NewRole(store db.Store, redis redis.RedisCache) *roleBiz { return &roleBiz{ store: store, redis: redis, diff --git a/internal/erpserver/biz/v1/system/system.go b/internal/erpserver/biz/v1/system/system.go index 7f868d0..5b96a76 100644 --- a/internal/erpserver/biz/v1/system/system.go +++ b/internal/erpserver/biz/v1/system/system.go @@ -5,6 +5,8 @@ import ( "management/internal/erpserver/store" "management/internal/pkg/redis" "management/internal/pkg/session" + + "github.com/drhin/logger" ) type SystemBiz interface { @@ -21,23 +23,25 @@ type SystemBiz interface { type systemBiz struct { database store.IStore store db.Store - redis redis.IRedis - session session.ISession + redis redis.RedisCache + session session.Session + logger *logger.Logger } var _ SystemBiz = (*systemBiz)(nil) -func New(database store.IStore, store db.Store, redis redis.IRedis, session session.ISession) *systemBiz { +func New(database store.IStore, store db.Store, redis redis.RedisCache, session session.Session, logger *logger.Logger) *systemBiz { return &systemBiz{ database: database, store: store, redis: redis, session: session, + logger: logger, } } func (b *systemBiz) UserBiz() UserBiz { - return NewUser(b.database, b.store, b.session) + return NewUser(b.database, b.store, b.session, b.logger) } func (b *systemBiz) MenuBiz() MenuBiz { @@ -61,7 +65,7 @@ func (b *systemBiz) AuditBiz() AuditBiz { } func (b *systemBiz) LoginLogBiz() LoginLogBiz { - return NewLoginLog(b.store) + return NewLoginLog(b.database, b.store) } func (b *systemBiz) CategoryBiz() CategoryBiz { diff --git a/internal/erpserver/biz/v1/system/user.go b/internal/erpserver/biz/v1/system/user.go index fb8d3c9..613a32c 100644 --- a/internal/erpserver/biz/v1/system/user.go +++ b/internal/erpserver/biz/v1/system/user.go @@ -4,13 +4,13 @@ import ( "context" "encoding/json" "errors" - "fmt" "strconv" "time" "management/internal/db/model/dto" db "management/internal/db/sqlc" "management/internal/erpserver/model/form" + systemmodel "management/internal/erpserver/model/system" "management/internal/erpserver/model/view" "management/internal/erpserver/store" "management/internal/pkg/crypto" @@ -18,7 +18,9 @@ import ( "management/internal/pkg/rand" "management/internal/pkg/session" + "github.com/drhin/logger" "github.com/google/uuid" + "go.uber.org/zap" ) // UserBiz 定义处理用户请求所需的方法. @@ -43,17 +45,19 @@ type UserExpansion interface { type userBiz struct { database store.IStore store db.Store - session session.ISession + session session.Session + log *logger.Logger } // 确保 userBiz 实现了 UserBiz 接口. var _ UserBiz = (*userBiz)(nil) -func NewUser(database store.IStore, store db.Store, session session.ISession) *userBiz { +func NewUser(database store.IStore, store db.Store, session session.Session, log *logger.Logger) *userBiz { return &userBiz{ database: database, store: store, session: session, + log: log, } } @@ -183,73 +187,67 @@ func (b *userBiz) XmSelect(ctx context.Context) ([]*view.XmSelect, error) { } func (b *userBiz) Login(ctx context.Context, req *form.Login) error { - log := &db.CreateSysUserLoginLogParams{ - CreatedAt: time.Now(), - Email: req.Email, - IsSuccess: false, - RefererUrl: req.Referrer, - Url: req.Url, - Os: req.Os, - Ip: req.Ip, - Browser: req.Browser, - } - - user, err := b.database.User().GetByEmail(ctx, req.Email) - // user, err = b.store.GetSysUserByEmail(ctx, req.Email) + l := systemmodel.NewLoginLog(req.Email, req.Os, req.Ip, req.Browser, req.Url, req.Referrer) + err := b.login(ctx, req) + if err != nil { + if err := b.database.LoginLog().Create(ctx, l.SetMessage(err.Error())); err != nil { + b.log.Error(err.Error(), err, zap.Any("login_log", l)) + } + return err + } + + if err := b.database.LoginLog().Create(ctx, l.SetOk("登录成功")); err != nil { + b.log.Error(err.Error(), err, zap.Any("login_log", l)) + } + return nil +} + +func (b *userBiz) login(ctx context.Context, req *form.Login) error { + user, err := b.database.User().GetByEmail(ctx, req.Email) if err != nil { - log.Message = err.Error() - _ = b.store.CreateSysUserLoginLog(ctx, log) return err } - fmt.Printf("%+v", user) - log.UserUuid = user.Uuid - log.Username = user.Username err = crypto.BcryptComparePassword(user.HashedPassword, req.Password+user.Salt) if err != nil { - log.Message = "compare password failed" - _ = b.store.CreateSysUserLoginLog(ctx, log) - return errors.New(log.Message) + return errors.New("账号或密码错误") + } + + user.Role, err = b.database.Role().Get(ctx, user.RoleID) + if err != nil { + return err + } + if user.Role == nil || user.Role.ID == 0 { + return errors.New("账号没有配置角色, 请联系管理员") } // 登陆成功 - - if user.RoleID == 0 { - log.Message = "账号没有配置角色, 请联系管理员" - _ = b.store.CreateSysUserLoginLog(ctx, log) - return errors.New(log.Message) - } - - sysRole, err := b.store.GetSysRole(ctx, user.RoleID) + err = b.loginSuccess(ctx, user, req) if err != nil { - log.Message = "账号配置的角色错误, 请联系管理员" - _ = b.store.CreateSysUserLoginLog(ctx, log) - return errors.New(log.Message) + return err } + return nil +} + +func (b *userBiz) loginSuccess(ctx context.Context, user *systemmodel.User, req *form.Login) error { auth := dto.AuthorizeUser{ ID: user.ID, Uuid: user.Uuid, Email: user.Email, Username: user.Username, - RoleID: sysRole.ID, - RoleName: sysRole.Name, - OS: log.Os, - IP: log.Ip, - Browser: log.Browser, + RoleID: user.Role.ID, + RoleName: user.Role.Name, + OS: req.Os, + IP: req.Ip, + Browser: req.Browser, } gob, err := json.Marshal(auth) if err != nil { - log.Message = err.Error() - _ = b.store.CreateSysUserLoginLog(ctx, log) return err } b.session.Put(ctx, know.StoreName, gob) - - log.IsSuccess = true - log.Message = "登陆成功" - _ = b.store.CreateSysUserLoginLog(ctx, log) return nil } diff --git a/internal/erpserver/handler/app.go b/internal/erpserver/handler/app.go new file mode 100644 index 0000000..fcf9f22 --- /dev/null +++ b/internal/erpserver/handler/app.go @@ -0,0 +1,62 @@ +package handler + +import ( + systemrepo "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" + systemsvc "management/internal/erpserver/service/v1" + "management/internal/pkg/config" + "management/internal/pkg/middleware" + "management/internal/pkg/redis" + "management/internal/pkg/session" + "management/internal/pkg/tpl" + + "github.com/drhin/logger" + "gorm.io/gorm" +) + +type AppContext struct { + // DB + DB *gorm.DB + + // Store + Store repository.Store + + // Cache + Redis redis.RedisCache + + // session + Session session.Session + + // middleware + Middleware middleware.Middleware + + // render + Render tpl.Renderer + + // config + Config *config.Config + + // log + Log *logger.Logger + + // repository + UserRepo systemrepo.UserRepository + LoginLogRepo systemrepo.LoginLogRepository + AuditLogRepo systemrepo.AuditLogRepository + RoleRepo systemrepo.RoleRepository + DepartmentRepo systemrepo.DepartmentRepository + ConfigRepo systemrepo.ConfigRepository + MenuRepo systemrepo.MenuRepository + RoleMenuRepo systemrepo.RoleMenuRepository + + // service + CaptchaService systemsvc.CaptchaService + UserService systemsvc.UserService + LoginLogService systemsvc.LoginLogService + AuditLogService systemsvc.AuditLogService + RoleService systemsvc.RoleService + DepartmentService systemsvc.DepartmentService + ConfigService systemsvc.ConfigService + MenuService systemsvc.MenuService + RoleMenuService systemsvc.RoleMenuService +} diff --git a/internal/erpserver/handler/budget/budget.go b/internal/erpserver/handler/budget/budget.go index 8060245..78057e9 100644 --- a/internal/erpserver/handler/budget/budget.go +++ b/internal/erpserver/handler/budget/budget.go @@ -30,12 +30,12 @@ type BudgetHandler interface { type budgetHandler struct { render tpl.Renderer biz biz.IBiz - mi middleware.IMiddleware + mi middleware.Middleware } var _ BudgetHandler = (*budgetHandler)(nil) -func NewBudgetHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.IMiddleware) *budgetHandler { +func NewBudgetHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.Middleware) *budgetHandler { return &budgetHandler{ render: render, biz: biz, diff --git a/internal/erpserver/handler/common/captcha.go b/internal/erpserver/handler/common/captcha.go index 43f2893..2bee1a3 100644 --- a/internal/erpserver/handler/common/captcha.go +++ b/internal/erpserver/handler/common/captcha.go @@ -3,30 +3,22 @@ package common import ( "net/http" - commonv1 "management/internal/erpserver/biz/v1/common" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/config" "management/internal/pkg/tpl" ) -type CaptchaHandler interface { - Captcha(w http.ResponseWriter, r *http.Request) -} - -// captchaHandler 是 CaptchaHandler 接口的实现. type captchaHandler struct { conf *config.Captcha render tpl.Renderer - biz commonv1.CaptchaBiz + svc v1.CaptchaService } -// 确保 captchaHandler 实现了 CaptchaHandler 接口. -var _ CaptchaHandler = (*captchaHandler)(nil) - -func NewCaptchaHandler(conf *config.Captcha, render tpl.Renderer, biz commonv1.CaptchaBiz) *captchaHandler { +func NewCaptchaHandler(conf *config.Captcha, render tpl.Renderer, svc v1.CaptchaService) *captchaHandler { return &captchaHandler{ conf: conf, render: render, - biz: biz, + svc: svc, } } @@ -40,7 +32,7 @@ type CaptchaResponse struct { func (h *captchaHandler) Captcha(w http.ResponseWriter, r *http.Request) { keyLong := h.conf.KeyLong oc := h.conf.OpenCaptcha - id, b64s, _, err := h.biz.Generate(h.conf.ImgHeight, h.conf.ImgWidth, keyLong, 0.7, 80) + id, b64s, _, err := h.svc.Generate(h.conf.ImgHeight, h.conf.ImgWidth, keyLong, 0.7, 80) if err != nil { h.render.JSON(w, tpl.Response{Success: false, Message: "获取验证码失败"}) return diff --git a/internal/erpserver/handler/common/common.go b/internal/erpserver/handler/common/common.go deleted file mode 100644 index 8be16ee..0000000 --- a/internal/erpserver/handler/common/common.go +++ /dev/null @@ -1,36 +0,0 @@ -package common - -import ( - commonv1 "management/internal/erpserver/biz/v1/common" - "management/internal/pkg/config" - "management/internal/pkg/tpl" -) - -type CommonHandler interface { - CaptchaHandler() CaptchaHandler - UploadHandler() UploadHandler -} - -type commonHandler struct { - conf *config.Config - render tpl.Renderer - biz commonv1.CommonBiz -} - -var _ CommonHandler = (*commonHandler)(nil) - -func NewCommonHandler(conf *config.Config, render tpl.Renderer, biz commonv1.CommonBiz) *commonHandler { - return &commonHandler{ - conf: conf, - render: render, - biz: biz, - } -} - -func (h *commonHandler) CaptchaHandler() CaptchaHandler { - return NewCaptchaHandler(&h.conf.Captcha, h.render, h.biz.CaptchaBiz()) -} - -func (h *commonHandler) UploadHandler() UploadHandler { - return NewUploadHandler(h.render) -} diff --git a/internal/erpserver/handler/common/upload.go b/internal/erpserver/handler/common/upload.go index db550ef..8f382c1 100644 --- a/internal/erpserver/handler/common/upload.go +++ b/internal/erpserver/handler/common/upload.go @@ -8,18 +8,10 @@ import ( "management/internal/pkg/tpl" ) -type UploadHandler interface { - Img(w http.ResponseWriter, r *http.Request) - File(w http.ResponseWriter, r *http.Request) - MutilFiles(w http.ResponseWriter, r *http.Request) -} - type uploadHandler struct { render tpl.Renderer } -var _ UploadHandler = (*uploadHandler)(nil) - func NewUploadHandler(render tpl.Renderer) *uploadHandler { return &uploadHandler{ render: render, diff --git a/internal/erpserver/handler/customer/customer.go b/internal/erpserver/handler/customer/customer.go index 35455e9..7935ee0 100644 --- a/internal/erpserver/handler/customer/customer.go +++ b/internal/erpserver/handler/customer/customer.go @@ -28,12 +28,12 @@ type CustomerHandler interface { type customerHandler struct { render tpl.Renderer biz biz.IBiz - mi middleware.IMiddleware + mi middleware.Middleware } var _ CustomerHandler = (*customerHandler)(nil) -func NewCustomerHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.IMiddleware) *customerHandler { +func NewCustomerHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.Middleware) *customerHandler { return &customerHandler{ render: render, biz: biz, diff --git a/internal/erpserver/handler/expense/expense.go b/internal/erpserver/handler/expense/expense.go index b14a046..795bdcc 100644 --- a/internal/erpserver/handler/expense/expense.go +++ b/internal/erpserver/handler/expense/expense.go @@ -29,12 +29,12 @@ type ExpenseHandler interface { type expenseHandler struct { render tpl.Renderer biz biz.IBiz - mi middleware.IMiddleware + mi middleware.Middleware } var _ ExpenseHandler = (*expenseHandler)(nil) -func NewExpenseHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.IMiddleware) *expenseHandler { +func NewExpenseHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.Middleware) *expenseHandler { return &expenseHandler{ render: render, biz: biz, diff --git a/internal/erpserver/handler/handler.go b/internal/erpserver/handler/handler.go deleted file mode 100644 index 525ca96..0000000 --- a/internal/erpserver/handler/handler.go +++ /dev/null @@ -1,90 +0,0 @@ -package handler - -import ( - "management/internal/erpserver/biz" - "management/internal/erpserver/handler/budget" - "management/internal/erpserver/handler/common" - "management/internal/erpserver/handler/customer" - "management/internal/erpserver/handler/expense" - "management/internal/erpserver/handler/income" - "management/internal/erpserver/handler/project" - "management/internal/erpserver/handler/system" - "management/internal/pkg/config" - "management/internal/pkg/middleware" - "management/internal/pkg/redis" - "management/internal/pkg/session" - "management/internal/pkg/tpl" -) - -// IHandler 定义了Handler需要实现的方法. -type IHandler interface { - // 获取 Common Handler 接口. - CommonHandler() common.CommonHandler - // 获取 System Handler 接口. - SystemHandler() system.SystemHandler - ProjectHandler() project.ProjectHandler - BudgetHandler() budget.BudgetHandler - CustomerHandler() customer.CustomerHandler - IncomeHandler() income.IncomeHandler - ExpenseHandler() expense.ExpenseHandler -} - -// handler 是 IHandler 的一个具体实现. -type handler struct { - conf *config.Config - render tpl.Renderer - redis redis.IRedis - session session.ISession - biz biz.IBiz - mi middleware.IMiddleware -} - -// 确保 handler 实现了 IHandler 接口. -var _ IHandler = (*handler)(nil) - -// NewHandler 创建一个 IHandler 类型的实例. -func NewHandler(conf *config.Config, render tpl.Renderer, redis redis.IRedis, session session.ISession, biz biz.IBiz, mi middleware.IMiddleware) *handler { - return &handler{ - conf: conf, - render: render, - redis: redis, - session: session, - biz: biz, - mi: mi, - } -} - -// CommonHandler 返回一个实现了 CommonHandler 接口的实例. -func (h *handler) CommonHandler() common.CommonHandler { - return common.NewCommonHandler(h.conf, h.render, h.biz.CommonV1()) -} - -// SystemHandler 返回一个实现了 SystemHandler 接口的实例. -func (h *handler) SystemHandler() system.SystemHandler { - return system.NewSystemHandler(h.render, h.redis, h.session, h.biz, h.mi) -} - -// ProjectHandler 返回一个实现了 ProjectHandler 接口的实例. -func (h *handler) ProjectHandler() project.ProjectHandler { - return project.NewProjectHandler(h.render, h.biz, h.mi) -} - -// BudgetHandler 返回一个实现了 BudgetHandler 接口的实例. -func (h *handler) BudgetHandler() budget.BudgetHandler { - return budget.NewBudgetHandler(h.render, h.biz, h.mi) -} - -// CustomerHandler 返回一个实现了 CustomerHandler 接口的实例. -func (h *handler) CustomerHandler() customer.CustomerHandler { - return customer.NewCustomerHandler(h.render, h.biz, h.mi) -} - -// IncomeHandler 返回一个实现了 IncomeHandler 接口的实例. -func (h *handler) IncomeHandler() income.IncomeHandler { - return income.NewIncomeHandler(h.render, h.biz, h.mi) -} - -// ExpenseHandler 返回一个实现了 ExpenseHandler 接口的实例. -func (h *handler) ExpenseHandler() expense.ExpenseHandler { - return expense.NewExpenseHandler(h.render, h.biz, h.mi) -} diff --git a/internal/erpserver/handler/income/income.go b/internal/erpserver/handler/income/income.go index 4723851..2e2dec1 100644 --- a/internal/erpserver/handler/income/income.go +++ b/internal/erpserver/handler/income/income.go @@ -29,12 +29,12 @@ type IncomeHandler interface { type incomeHandler struct { render tpl.Renderer biz biz.IBiz - mi middleware.IMiddleware + mi middleware.Middleware } var _ IncomeHandler = (*incomeHandler)(nil) -func NewIncomeHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.IMiddleware) *incomeHandler { +func NewIncomeHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.Middleware) *incomeHandler { return &incomeHandler{ render: render, biz: biz, diff --git a/internal/erpserver/handler/project/project.go b/internal/erpserver/handler/project/project.go index 2ab8ee8..d60119b 100644 --- a/internal/erpserver/handler/project/project.go +++ b/internal/erpserver/handler/project/project.go @@ -31,12 +31,12 @@ type ProjectHandler interface { type projectHandler struct { render tpl.Renderer biz biz.IBiz - mi middleware.IMiddleware + mi middleware.Middleware } var _ ProjectHandler = (*projectHandler)(nil) -func NewProjectHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.IMiddleware) *projectHandler { +func NewProjectHandler(render tpl.Renderer, biz biz.IBiz, mi middleware.Middleware) *projectHandler { return &projectHandler{ render: render, biz: biz, diff --git a/internal/erpserver/handler/router.go b/internal/erpserver/handler/router.go new file mode 100644 index 0000000..9d40819 --- /dev/null +++ b/internal/erpserver/handler/router.go @@ -0,0 +1,126 @@ +package handler + +import ( + "net/http" + + "management/internal/erpserver/handler/common" + "management/internal/erpserver/handler/system" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func (ctx *AppContext) Router() *chi.Mux { + r := chi.NewRouter() + + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + // r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + + staticServer := http.FileServer(http.Dir("./web/statics/")) + r.Handle("/statics/*", http.StripPrefix("/statics", staticServer)) + + uploadServer := http.FileServer(http.Dir("./upload/")) + r.Handle("/upload/*", http.StripPrefix("/upload", uploadServer)) + + r.Group(func(r chi.Router) { + r.Use(ctx.Middleware.NoSurf) // CSRF + r.Use(ctx.Middleware.LoadSession) // Session + + captchaHandler := common.NewCaptchaHandler(&ctx.Config.Captcha, ctx.Render, ctx.CaptchaService) + r.Get("/captcha", captchaHandler.Captcha) + + uploadHandler := common.NewUploadHandler(ctx.Render) + r.With(ctx.Middleware.Authorize, ctx.Middleware.Audit).Post("/upload/img", uploadHandler.Img) + r.With(ctx.Middleware.Authorize, ctx.Middleware.Audit).Post("/upload/file", uploadHandler.File) + r.With(ctx.Middleware.Authorize, ctx.Middleware.Audit).Post("/upload/mutilfile", uploadHandler.MutilFiles) + + userHandler := system.NewUserHandler(ctx.Render, ctx.Middleware, ctx.CaptchaService, ctx.UserService, ctx.RoleService, ctx.DepartmentService) + r.Get("/", userHandler.Login) + r.Post("/login", userHandler.Login) + r.Get("/logout", userHandler.Logout) + + homeHandler := system.NewHomeHandler(ctx.Render, ctx.Middleware, ctx.UserService, ctx.LoginLogService) + r.With(ctx.Middleware.Authorize).Get("/home.html", homeHandler.Home) + r.With(ctx.Middleware.Authorize).Get("/dashboard", homeHandler.Dashboard) + + configHandler := system.NewConfigHandler(ctx.Render, ctx.Redis, ctx.ConfigService) + r.With(ctx.Middleware.Authorize).Get("/pear.json", configHandler.Pear) + + r.Route("/system", func(r chi.Router) { + r.Use(ctx.Middleware.Authorize) + + menuHandler := system.NewMenuHandler(ctx.Render, ctx.Middleware, ctx.MenuService) + r.Get("/menus", menuHandler.Menus) + r.Route("/menu", func(r chi.Router) { + r.Use(ctx.Middleware.Audit) + r.Get("/list", menuHandler.List) + r.Post("/list", menuHandler.List) + r.Get("/add", menuHandler.Add) + r.Get("/add_children", menuHandler.AddChildren) + r.Get("/edit", menuHandler.Edit) + r.Post("/save", menuHandler.Save) + r.Post("/data", menuHandler.Data) + r.Post("/refresh_cache", menuHandler.RefreshCache) + }) + + r.Route("/department", func(r chi.Router) { + r.Use(ctx.Middleware.Audit) + + departHandler := system.NewDepartmentHandler(ctx.Render, ctx.DepartmentService) + r.Get("/list", departHandler.List) + r.Post("/list", departHandler.List) + r.Get("/add", departHandler.Add) + r.Get("/add_children", departHandler.AddChildren) + r.Get("/edit", departHandler.Edit) + r.Post("/save", departHandler.Save) + r.Post("/data", departHandler.Data) + r.Post("/refresh_cache", departHandler.RefreshCache) + r.Post("/rebuild_parent_path", departHandler.RebuildParentPath) + }) + + r.Route("/role", func(r chi.Router) { + r.Use(ctx.Middleware.Audit) + + roleHandler := system.NewRoleHandler(ctx.Render, ctx.RoleService, ctx.MenuService) + r.Get("/list", roleHandler.List) + r.Post("/list", roleHandler.List) + r.Get("/add", roleHandler.Add) + r.Get("/add_children", roleHandler.AddChildren) + r.Get("/edit", roleHandler.Edit) + r.Post("/save", roleHandler.Save) + r.Post("/data", roleHandler.Data) + r.Post("/refresh_cache", roleHandler.RefreshCache) + r.Post("/rebuild_parent_path", roleHandler.RebuildParentPath) + r.Post("/refresh_role_menus", roleHandler.RefreshRoleMenus) + r.Get("/set_menu", roleHandler.SetMenu) + r.Post("/set_menu", roleHandler.SetMenu) + }) + + r.Route("/user", func(r chi.Router) { + r.Get("/list", userHandler.List) + r.Post("/list", userHandler.List) + r.Get("/add", userHandler.Add) + r.Get("/edit", userHandler.Edit) + r.Post("/save", userHandler.Save) + r.Get("/profile", userHandler.Profile) + r.Post("/data", userHandler.Data) + }) + + loginLogHandler := system.NewLoginLogHandler(ctx.Render, ctx.LoginLogService) + r.Route("/login_log", func(r chi.Router) { + r.Get("/list", loginLogHandler.List) + r.Post("/list", loginLogHandler.List) + }) + + auditHandler := system.NewAuditHandler(ctx.Render, ctx.AuditLogService) + r.Route("/audit_log", func(r chi.Router) { + r.Get("/list", auditHandler.List) + r.Post("/list", auditHandler.List) + }) + }) + }) + + return r +} diff --git a/internal/erpserver/handler/system/audit.go b/internal/erpserver/handler/system/audit.go index d7196a5..03857e7 100644 --- a/internal/erpserver/handler/system/audit.go +++ b/internal/erpserver/handler/system/audit.go @@ -4,26 +4,20 @@ import ( "net/http" "management/internal/db/model/dto" - "management/internal/erpserver/biz" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/convertor" "management/internal/pkg/tpl" ) -type AuditHandler interface { - List(w http.ResponseWriter, r *http.Request) -} - type auditHandler struct { render tpl.Renderer - biz biz.IBiz + svc v1.AuditLogService } -var _ AuditHandler = (*auditHandler)(nil) - -func NewAuditHandler(render tpl.Renderer, biz biz.IBiz) *auditHandler { +func NewAuditHandler(render tpl.Renderer, svc v1.AuditLogService) *auditHandler { return &auditHandler{ render: render, - biz: biz, + svc: svc, } } @@ -38,7 +32,7 @@ func (h *auditHandler) List(w http.ResponseWriter, r *http.Request) { q.SearchEmail = r.PostFormValue("email") q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1) q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10) - res, count, err := h.biz.SystemV1().AuditBiz().List(r.Context(), q) + res, count, err := h.svc.List(r.Context(), q) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/erpserver/handler/system/config.go b/internal/erpserver/handler/system/config.go index 65c2e48..db8324b 100644 --- a/internal/erpserver/handler/system/config.go +++ b/internal/erpserver/handler/system/config.go @@ -1,61 +1,41 @@ package system import ( - "encoding/json" "net/http" "strings" "management/internal/db/model/dto" db "management/internal/db/sqlc" - "management/internal/erpserver/biz" + systemmodel "management/internal/erpserver/model/system" + systemsvc "management/internal/erpserver/service/v1" "management/internal/pkg/convertor" - "management/internal/pkg/know/pearadmin" "management/internal/pkg/redis" "management/internal/pkg/tpl" ) -type ConfigHandler interface { - Add(w http.ResponseWriter, r *http.Request) - Edit(w http.ResponseWriter, r *http.Request) - Save(w http.ResponseWriter, r *http.Request) - List(w http.ResponseWriter, r *http.Request) - Refresh(w http.ResponseWriter, r *http.Request) - ResetPear(w http.ResponseWriter, r *http.Request) - - ConfigExpansion -} - -type ConfigExpansion interface { - Pear(w http.ResponseWriter, r *http.Request) -} - -// configHandler 是 ConfigHandler 接口的实现. type configHandler struct { - render tpl.Renderer - redis redis.IRedis - biz biz.IBiz + render tpl.Renderer + redis redis.RedisCache + configsvc systemsvc.ConfigService } -// 确保 userHandler 实现了 ConfigHandler 接口. -var _ ConfigHandler = (*configHandler)(nil) - -func NewConfigHandler(render tpl.Renderer, redis redis.IRedis, biz biz.IBiz) *configHandler { +func NewConfigHandler(render tpl.Renderer, redis redis.RedisCache, configsvc systemsvc.ConfigService) *configHandler { return &configHandler{ - render: render, - redis: redis, - biz: biz, + render: render, + redis: redis, + configsvc: configsvc, } } func (h *configHandler) Add(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "config/edit.tmpl", map[string]any{ - "Item": &db.SysConfig{}, + "Item": &systemmodel.Config{}, "Result": "", }) } type EditSysConfig struct { - *db.SysConfig + *systemmodel.Config Result string } @@ -64,13 +44,13 @@ func (h *configHandler) Edit(w http.ResponseWriter, r *http.Request) { id := convertor.QueryInt[int32](vars, "id", 0) vm := &EditSysConfig{} if id > 0 { - if conf, err := h.biz.SystemV1().ConfigBiz().Get(r.Context(), id); err == nil { - vm.SysConfig = conf + if conf, err := h.configsvc.Get(r.Context(), id); err == nil { + vm.Config = conf vm.Result = string(conf.Value) } } h.render.HTML(w, r, "config/edit.tmpl", map[string]any{ - "Item": vm.SysConfig, + "Item": vm.Config, "Result": vm.Result, }) } @@ -90,11 +70,11 @@ func (h *configHandler) Save(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if id == 0 { - arg := &db.CreateSysConfigParams{ + arg := &systemmodel.Config{ Key: key, Value: []byte(value), } - err := h.biz.SystemV1().ConfigBiz().Create(ctx, arg) + err := h.configsvc.Create(ctx, arg) if err != nil { if db.IsUniqueViolation(err) { h.render.JSONERR(w, "数据已存在") @@ -106,17 +86,14 @@ func (h *configHandler) Save(w http.ResponseWriter, r *http.Request) { h.render.JSONOK(w, "添加成功") } else { - res, err := h.biz.SystemV1().ConfigBiz().Get(ctx, id) + res, err := h.configsvc.Get(ctx, id) if err != nil { h.render.JSONERR(w, err.Error()) return } - arg := &db.UpdateSysConfigByKeyParams{ - Key: res.Key, - Value: []byte(value), - } - err = h.biz.SystemV1().ConfigBiz().Update(ctx, arg) + res.Value = []byte(value) + err = h.configsvc.Update(ctx, res) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -136,7 +113,7 @@ func (h *configHandler) List(w http.ResponseWriter, r *http.Request) { q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1) q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10) ctx := r.Context() - res, count, err := h.biz.SystemV1().ConfigBiz().List(ctx, q) + res, count, err := h.configsvc.List(ctx, q) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -166,25 +143,25 @@ func (h *configHandler) Refresh(w http.ResponseWriter, r *http.Request) { } func (h *configHandler) ResetPear(w http.ResponseWriter, r *http.Request) { - b, err := json.Marshal(pearadmin.PearJson) - if err != nil { - h.render.JSONERR(w, err.Error()) - return - } + // b, err := json.Marshal(pearadmin.PearJson) + // if err != nil { + // h.render.JSONERR(w, err.Error()) + // return + // } - err = h.biz.SystemV1().ConfigBiz().Update(r.Context(), &db.UpdateSysConfigByKeyParams{ - Key: pearadmin.PearKey, - Value: b, - }) - if err != nil { - h.render.JSONERR(w, err.Error()) - return - } + // err = h.configsvc.Update(r.Context(), &db.UpdateSysConfigByKeyParams{ + // Key: pearadmin.PearKey, + // Value: b, + // }) + // if err != nil { + // h.render.JSONERR(w, err.Error()) + // return + // } h.render.JSONOK(w, "重置成功") } func (h *configHandler) Pear(w http.ResponseWriter, r *http.Request) { - pear, err := h.biz.SystemV1().ConfigBiz().Pear(r.Context()) + pear, err := h.configsvc.Pear(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/erpserver/handler/system/department.go b/internal/erpserver/handler/system/department.go index 35021f5..9e80f31 100644 --- a/internal/erpserver/handler/system/department.go +++ b/internal/erpserver/handler/system/department.go @@ -4,36 +4,23 @@ import ( "net/http" "management/internal/db/model/dto" - db "management/internal/db/sqlc" - "management/internal/erpserver/biz" "management/internal/erpserver/model/form" + systemmodel "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/binding" "management/internal/pkg/convertor" "management/internal/pkg/tpl" ) -type DepartmentHandler interface { - List(w http.ResponseWriter, r *http.Request) - Add(w http.ResponseWriter, r *http.Request) - AddChildren(w http.ResponseWriter, r *http.Request) - Edit(w http.ResponseWriter, r *http.Request) - Save(w http.ResponseWriter, r *http.Request) - Data(w http.ResponseWriter, r *http.Request) - Refresh(w http.ResponseWriter, r *http.Request) - RebuildParentPath(w http.ResponseWriter, r *http.Request) -} - type departmentHandler struct { render tpl.Renderer - biz biz.IBiz + svc v1.DepartmentService } -var _ DepartmentHandler = (*departmentHandler)(nil) - -func NewDepartmentHandler(render tpl.Renderer, biz biz.IBiz) *departmentHandler { +func NewDepartmentHandler(render tpl.Renderer, svc v1.DepartmentService) *departmentHandler { return &departmentHandler{ render: render, - biz: biz, + svc: svc, } } @@ -43,13 +30,14 @@ func (h *departmentHandler) List(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "department/list.tmpl", nil) case http.MethodPost: var q dto.SearchDto + q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd")) q.SearchStatus = convertor.ConvertInt(r.PostFormValue("status"), 9999) q.SearchParentID = convertor.ConvertInt(r.PostFormValue("parentId"), 0) q.SearchName = r.PostFormValue("name") q.SearchID = convertor.ConvertInt[int64](r.PostFormValue("id"), 0) q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1) q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10) - res, count, err := h.biz.SystemV1().DepartmentBiz().List(r.Context(), q) + res, count, err := h.svc.List(r.Context(), q) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -69,14 +57,14 @@ func (h *departmentHandler) List(w http.ResponseWriter, r *http.Request) { func (h *departmentHandler) Add(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "department/edit.tmpl", map[string]any{ - "Item": &db.SysDepartment{Sort: 6666}, + "Item": &systemmodel.Department{Sort: 6666}, }) } func (h *departmentHandler) AddChildren(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() parentID := convertor.QueryInt(vars, "parentID", 0) - vm := &db.SysDepartment{ParentID: int32(parentID), Sort: 6666} + vm := &systemmodel.Department{ParentID: int32(parentID), Sort: 6666} h.render.HTML(w, r, "department/edit.tmpl", map[string]any{ "Item": vm, }) @@ -85,9 +73,9 @@ func (h *departmentHandler) AddChildren(w http.ResponseWriter, r *http.Request) func (h *departmentHandler) Edit(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() id := convertor.QueryInt[int32](vars, "id", 0) - vm := &db.SysDepartment{Sort: 6666} + vm := &systemmodel.Department{Sort: 6666} if id > 0 { - vm, _ = h.biz.SystemV1().DepartmentBiz().Get(r.Context(), id) + vm, _ = h.svc.Get(r.Context(), id) } h.render.HTML(w, r, "department/edit.tmpl", map[string]any{ "Item": vm, @@ -103,14 +91,14 @@ func (h *departmentHandler) Save(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if *req.ID == 0 { - err := h.biz.SystemV1().DepartmentBiz().Create(ctx, &req) + err := h.svc.Create(ctx, &req) if err != nil { h.render.JSONERR(w, err.Error()) return } h.render.JSONOK(w, "添加成功") } else { - err := h.biz.SystemV1().DepartmentBiz().Update(ctx, &req) + err := h.svc.Update(ctx, &req) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -125,39 +113,37 @@ func (h *departmentHandler) Data(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() t := vars.Get("type") if t == "tree" { - res, err := h.biz.SystemV1().DepartmentBiz().Tree(ctx, 0) + res, err := h.svc.Tree(ctx, 0) if err != nil { h.render.JSONERR(w, err.Error()) return } h.render.JSON(w, res) - return } else if t == "xmselect_tree" { - res, err := h.biz.SystemV1().DepartmentBiz().XmSelectTree(ctx, 0) + res, err := h.svc.XmSelectTree(ctx, 0) if err != nil { h.render.JSONERR(w, err.Error()) return } h.render.JSON(w, res) - return } } -func (h *departmentHandler) Refresh(w http.ResponseWriter, r *http.Request) { - _, err := h.biz.SystemV1().DepartmentBiz().Refresh(r.Context()) +func (h *departmentHandler) RefreshCache(w http.ResponseWriter, r *http.Request) { + err := h.svc.RefreshCache(r.Context()) if err != nil { h.render.JSONERR(w, err.Error()) return } - h.render.JSONOK(w, "刷新成功") + h.render.JSONOK(w, "缓存刷新成功") } func (h *departmentHandler) RebuildParentPath(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - err := h.biz.SystemV1().DepartmentBiz().RebuildParentPath(ctx) + err := h.svc.RebuildParentPath(ctx) if err != nil { h.render.JSONERR(w, err.Error()) return diff --git a/internal/erpserver/handler/system/home.go b/internal/erpserver/handler/system/home.go index 388f7ab..7b5345f 100644 --- a/internal/erpserver/handler/system/home.go +++ b/internal/erpserver/handler/system/home.go @@ -1,11 +1,43 @@ package system -import "net/http" +import ( + "net/http" -func (h *systemHandler) Home(w http.ResponseWriter, r *http.Request) { + v1 "management/internal/erpserver/service/v1" + "management/internal/pkg/middleware" + "management/internal/pkg/tpl" +) + +type homeHandler struct { + render tpl.Renderer + mi middleware.Middleware + usersvc v1.UserService + loginLogsvc v1.LoginLogService +} + +func NewHomeHandler(render tpl.Renderer, mi middleware.Middleware, usersvc v1.UserService, loginLogsvc v1.LoginLogService) *homeHandler { + return &homeHandler{ + render: render, + mi: mi, + usersvc: usersvc, + loginLogsvc: loginLogsvc, + } +} + +func (h *homeHandler) Home(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "home/home.tmpl", nil) } -func (h *systemHandler) Dashboard(w http.ResponseWriter, r *http.Request) { - h.render.HTML(w, r, "home/dashboard.tmpl", nil) +func (h *homeHandler) Dashboard(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + auth := h.mi.AuthUser(ctx) + user, _ := h.usersvc.Get(ctx, auth.ID) + t := h.loginLogsvc.LoginLatestTime(ctx, auth.Email) + c := h.loginLogsvc.LoginCount(ctx, auth.Email) + h.render.HTML(w, r, "home/dashboard.tmpl", map[string]any{ + "Auth": auth, + "User": user, + "LastLoginTime": t, + "LoginCount": c, + }) } diff --git a/internal/erpserver/handler/system/login_log.go b/internal/erpserver/handler/system/login_log.go index e592e28..ce96027 100644 --- a/internal/erpserver/handler/system/login_log.go +++ b/internal/erpserver/handler/system/login_log.go @@ -4,26 +4,20 @@ import ( "net/http" "management/internal/db/model/dto" - "management/internal/erpserver/biz" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/convertor" "management/internal/pkg/tpl" ) -type LoginLogHandler interface { - List(w http.ResponseWriter, r *http.Request) -} - type loginLogHandler struct { render tpl.Renderer - biz biz.IBiz + svc v1.LoginLogService } -var _ LoginLogHandler = (*loginLogHandler)(nil) - -func NewLoginLogHandler(render tpl.Renderer, biz biz.IBiz) *loginLogHandler { +func NewLoginLogHandler(render tpl.Renderer, svc v1.LoginLogService) *loginLogHandler { return &loginLogHandler{ render: render, - biz: biz, + svc: svc, } } @@ -34,11 +28,10 @@ func (h *loginLogHandler) List(w http.ResponseWriter, r *http.Request) { case http.MethodPost: var q dto.SearchDto q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd")) - q.SearchName = r.PostFormValue("name") q.SearchEmail = r.PostFormValue("email") q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1) q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10) - res, count, err := h.biz.SystemV1().LoginLogBiz().List(r.Context(), q) + res, count, err := h.svc.List(r.Context(), q) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/erpserver/handler/system/menu.go b/internal/erpserver/handler/system/menu.go index 9622b49..59da6a0 100644 --- a/internal/erpserver/handler/system/menu.go +++ b/internal/erpserver/handler/system/menu.go @@ -1,18 +1,16 @@ package system import ( - "encoding/json" "net/http" "strconv" "strings" "time" - "management/internal/db/model/dto" db "management/internal/db/sqlc" - "management/internal/erpserver/biz" + systemmodel "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/convertor" - "management/internal/pkg/know" - "management/internal/pkg/session" + "management/internal/pkg/middleware" "management/internal/pkg/tpl" "github.com/google/uuid" @@ -20,47 +18,25 @@ import ( const style = "layui-btn-primary layui-btn-sm" -type MenuHandler interface { - MenuExpansion -} - -type MenuExpansion interface { - Menus(w http.ResponseWriter, r *http.Request) - - List(w http.ResponseWriter, r *http.Request) - Add(w http.ResponseWriter, r *http.Request) - AddChildren(w http.ResponseWriter, r *http.Request) - Edit(w http.ResponseWriter, r *http.Request) - Save(w http.ResponseWriter, r *http.Request) - Data(w http.ResponseWriter, r *http.Request) - Refresh(w http.ResponseWriter, r *http.Request) -} - type menuHandler struct { - render tpl.Renderer - session session.ISession - biz biz.IBiz + render tpl.Renderer + mi middleware.Middleware + + svc v1.MenuService } -var _ MenuHandler = (*menuHandler)(nil) - -func NewMenuHandler(render tpl.Renderer, session session.ISession, biz biz.IBiz) *menuHandler { +func NewMenuHandler(render tpl.Renderer, mi middleware.Middleware, svc v1.MenuService) *menuHandler { return &menuHandler{ - render: render, - session: session, - biz: biz, + render: render, + mi: mi, + svc: svc, } } func (h *menuHandler) Menus(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - b := h.session.GetBytes(ctx, know.StoreName) - var u dto.AuthorizeUser - if err := json.Unmarshal(b, &u); err != nil { - h.render.JSONERR(w, err.Error()) - return - } - menus, err := h.biz.SystemV1().MenuBiz().RecursiveSysMenus(ctx, u.RoleID) + user := h.mi.AuthUser(ctx) + menus, err := h.svc.OwerMenus(ctx, user.RoleID) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -74,7 +50,7 @@ func (h *menuHandler) List(w http.ResponseWriter, r *http.Request) { case http.MethodGet: h.render.HTML(w, r, "menu/list.tmpl", nil) case http.MethodPost: - res, err := h.biz.SystemV1().MenuBiz().ListMenuTree(r.Context()) + res, err := h.svc.ListMenuTree(r.Context()) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -94,15 +70,15 @@ func (h *menuHandler) List(w http.ResponseWriter, r *http.Request) { func (h *menuHandler) Add(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "menu/edit.tmpl", map[string]any{ - "Item": &db.SysMenu{Style: style, Visible: true, Sort: 6666}, + "Item": &systemmodel.Menu{Style: style, Visible: true, Sort: 6666}, }) } func (h *menuHandler) AddChildren(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() parentID := convertor.QueryInt[int32](vars, "parentID", 0) - vm := &db.SysMenu{ParentID: int32(parentID), Style: style, Visible: true, Sort: 6666} - parent, err := h.biz.SystemV1().MenuBiz().Get(r.Context(), parentID) + vm := &systemmodel.Menu{ParentID: int32(parentID), Style: style, Visible: true, Sort: 6666} + parent, err := h.svc.Get(r.Context(), parentID) if err == nil { if parent.Type == "node" { vm.Type = "menu" @@ -118,9 +94,9 @@ func (h *menuHandler) AddChildren(w http.ResponseWriter, r *http.Request) { func (h *menuHandler) Edit(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() id := convertor.QueryInt[int32](vars, "id", 0) - vm := &db.SysMenu{Style: style, Sort: 6666} + vm := &systemmodel.Menu{Style: style, Sort: 6666} if id > 0 { - vm, _ = h.biz.SystemV1().MenuBiz().Get(r.Context(), id) + vm, _ = h.svc.Get(r.Context(), id) } h.render.HTML(w, r, "menu/edit.tmpl", map[string]any{ "Item": vm, @@ -151,7 +127,7 @@ func (h *menuHandler) Save(w http.ResponseWriter, r *http.Request) { parentPath := "" if parentID > 0 { - parent, err := h.biz.SystemV1().MenuBiz().Get(ctx, parentID) + parent, err := h.svc.Get(ctx, parentID) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -161,7 +137,7 @@ func (h *menuHandler) Save(w http.ResponseWriter, r *http.Request) { } if id == 0 { - arg := &db.CreateSysMenuParams{ + arg := &systemmodel.Menu{ Name: name, DisplayName: dispalyName, Url: url, @@ -177,7 +153,7 @@ func (h *menuHandler) Save(w http.ResponseWriter, r *http.Request) { CreatedAt: time.Now(), UpdatedAt: time.Now(), } - _, err := h.biz.SystemV1().MenuBiz().Create(ctx, arg) + err := h.svc.Create(ctx, arg) if err != nil { if db.IsUniqueViolation(err) { h.render.JSONERR(w, "菜单已存在") @@ -189,29 +165,26 @@ func (h *menuHandler) Save(w http.ResponseWriter, r *http.Request) { h.render.JSONOK(w, "添加成功") } else { - res, err := h.biz.SystemV1().MenuBiz().Get(ctx, id) + res, err := h.svc.Get(ctx, id) if err != nil { h.render.JSONERR(w, err.Error()) return } - arg := &db.UpdateSysMenuParams{ - ID: res.ID, - Name: name, - DisplayName: dispalyName, - Url: url, - Type: t, - ParentID: parentID, - ParentPath: parentPath, - Avatar: avatar, - Style: style, - Visible: visible, - IsList: isList, - Status: status, - Sort: sort, - UpdatedAt: time.Now(), - } - _, err = h.biz.SystemV1().MenuBiz().Update(ctx, arg) + res.Name = name + res.DisplayName = dispalyName + res.Url = url + res.Type = t + res.ParentID = parentID + res.ParentPath = parentPath + res.Avatar = avatar + res.Style = style + res.Visible = visible + res.IsList = isList + res.Status = status + res.Sort = sort + res.UpdatedAt = time.Now() + err = h.svc.Update(ctx, res) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -226,7 +199,7 @@ func (h *menuHandler) Data(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() t := vars.Get("type") if t == "tree" { - res, err := h.biz.SystemV1().MenuBiz().Tree(ctx, 0) + res, err := h.svc.Tree(ctx, 0) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -235,7 +208,7 @@ func (h *menuHandler) Data(w http.ResponseWriter, r *http.Request) { h.render.JSON(w, res) return } else if t == "xmselect_tree" { - res, err := h.biz.SystemV1().MenuBiz().XmSelectTree(ctx, 0) + res, err := h.svc.XmSelectTree(ctx, 0) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -244,14 +217,15 @@ func (h *menuHandler) Data(w http.ResponseWriter, r *http.Request) { h.render.JSON(w, res) return } + h.render.JSON(w, nil) } -func (h *menuHandler) Refresh(w http.ResponseWriter, r *http.Request) { - err := h.biz.SystemV1().MenuBiz().RefreshMenus(r.Context()) +func (h *menuHandler) RefreshCache(w http.ResponseWriter, r *http.Request) { + err := h.svc.RefreshCache(r.Context()) if err != nil { h.render.JSONERR(w, err.Error()) return } - h.render.JSONOK(w, "刷新成功") + h.render.JSONOK(w, "缓存刷新成功") } diff --git a/internal/erpserver/handler/system/role.go b/internal/erpserver/handler/system/role.go index aa4105a..65f2b54 100644 --- a/internal/erpserver/handler/system/role.go +++ b/internal/erpserver/handler/system/role.go @@ -5,38 +5,25 @@ import ( "strings" "management/internal/db/model/dto" - db "management/internal/db/sqlc" - "management/internal/erpserver/biz" "management/internal/erpserver/model/form" + "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/binding" "management/internal/pkg/convertor" "management/internal/pkg/tpl" ) -type RoleHandler interface { - List(w http.ResponseWriter, r *http.Request) - Add(w http.ResponseWriter, r *http.Request) - AddChildren(w http.ResponseWriter, r *http.Request) - Edit(w http.ResponseWriter, r *http.Request) - Save(w http.ResponseWriter, r *http.Request) - Data(w http.ResponseWriter, r *http.Request) - Refresh(w http.ResponseWriter, r *http.Request) - RebuildParentPath(w http.ResponseWriter, r *http.Request) - RefreshRoleMenus(w http.ResponseWriter, r *http.Request) - SetMenu(w http.ResponseWriter, r *http.Request) -} - type roleHandler struct { - render tpl.Renderer - biz biz.IBiz + render tpl.Renderer + svc v1.RoleService + menusvc v1.MenuService } -var _ RoleHandler = (*roleHandler)(nil) - -func NewRoleHandler(render tpl.Renderer, biz biz.IBiz) *roleHandler { +func NewRoleHandler(render tpl.Renderer, svc v1.RoleService, menusvc v1.MenuService) *roleHandler { return &roleHandler{ - render: render, - biz: biz, + render: render, + svc: svc, + menusvc: menusvc, } } @@ -46,13 +33,14 @@ func (h *roleHandler) List(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "role/list.tmpl", nil) case http.MethodPost: var q dto.SearchDto + q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd")) q.SearchStatus = convertor.ConvertInt(r.PostFormValue("status"), 9999) q.SearchParentID = convertor.ConvertInt(r.PostFormValue("parentId"), 0) q.SearchName = r.PostFormValue("name") q.SearchID = convertor.ConvertInt[int64](r.PostFormValue("id"), 0) q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1) q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10) - res, count, err := h.biz.SystemV1().RoleBiz().List(r.Context(), q) + res, count, err := h.svc.List(r.Context(), q) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -72,14 +60,14 @@ func (h *roleHandler) List(w http.ResponseWriter, r *http.Request) { func (h *roleHandler) Add(w http.ResponseWriter, r *http.Request) { h.render.HTML(w, r, "role/edit.tmpl", map[string]any{ - "Item": &db.SysRole{Sort: 6666}, + "Item": &system.Role{Sort: 6666}, }) } func (h *roleHandler) AddChildren(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() parentID := convertor.QueryInt(vars, "parentID", 0) - vm := &db.SysRole{ParentID: int32(parentID), Sort: 6666} + vm := &system.Role{ParentID: int32(parentID), Sort: 6666} h.render.HTML(w, r, "role/edit.tmpl", map[string]any{ "Item": vm, }) @@ -88,9 +76,9 @@ func (h *roleHandler) AddChildren(w http.ResponseWriter, r *http.Request) { func (h *roleHandler) Edit(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() id := convertor.QueryInt[int32](vars, "id", 0) - vm := &db.SysRole{Sort: 6666} + vm := &system.Role{Sort: 6666} if id > 0 { - vm, _ = h.biz.SystemV1().RoleBiz().Get(r.Context(), id) + vm, _ = h.svc.Get(r.Context(), id) } h.render.HTML(w, r, "role/edit.tmpl", map[string]any{ "Item": vm, @@ -106,14 +94,14 @@ func (h *roleHandler) Save(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if *req.ID == 0 { - err := h.biz.SystemV1().RoleBiz().Create(ctx, &req) + err := h.svc.Create(ctx, &req) if err != nil { h.render.JSONERR(w, err.Error()) return } h.render.JSONOK(w, "添加成功") } else { - err := h.biz.SystemV1().RoleBiz().Update(ctx, &req) + err := h.svc.Update(ctx, &req) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -128,7 +116,7 @@ func (h *roleHandler) Data(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() t := vars.Get("type") if t == "tree" { - res, err := h.biz.SystemV1().RoleBiz().Tree(ctx, 0) + res, err := h.svc.Tree(ctx, 0) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -137,7 +125,7 @@ func (h *roleHandler) Data(w http.ResponseWriter, r *http.Request) { h.render.JSON(w, res) return } else if t == "xmselect_tree" { - res, err := h.biz.SystemV1().RoleBiz().XmSelectTree(ctx, 0) + res, err := h.svc.XmSelectTree(ctx, 0) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -148,19 +136,19 @@ func (h *roleHandler) Data(w http.ResponseWriter, r *http.Request) { } } -func (h *roleHandler) Refresh(w http.ResponseWriter, r *http.Request) { - _, err := h.biz.SystemV1().RoleBiz().Refresh(r.Context()) +func (h *roleHandler) RefreshCache(w http.ResponseWriter, r *http.Request) { + err := h.svc.RefreshCache(r.Context()) if err != nil { h.render.JSONERR(w, err.Error()) return } - h.render.JSONOK(w, "刷新成功") + h.render.JSONOK(w, "缓存刷新成功") } func (h *roleHandler) RebuildParentPath(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - err := h.biz.SystemV1().RoleBiz().RebuildParentPath(ctx) + err := h.svc.RebuildParentPath(ctx) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -174,28 +162,28 @@ func (h *roleHandler) RefreshRoleMenus(w http.ResponseWriter, r *http.Request) { // 获取需要刷新的角色ID roleID := convertor.ConvertInt[int32](r.PostFormValue("roleID"), 0) - sysRole, err := h.biz.SystemV1().RoleBiz().Get(ctx, int32(roleID)) - if err != nil || sysRole == nil { + role, err := h.svc.Get(ctx, int32(roleID)) + if err != nil || role == nil { h.render.JSONERR(w, err.Error()) return } // 刷新角色菜单 (角色所拥有的菜单集合) - _, err = h.biz.SystemV1().MenuBiz().SetOwnerListMenuByRoleID(ctx, sysRole.ID) + _, err = h.menusvc.SetListByRoleID(ctx, role.ID) if err != nil { h.render.JSONERR(w, err.Error()) return } // 刷新角色菜单 (角色所拥有的菜单集合) - _, err = h.biz.SystemV1().MenuBiz().SetOwnerMapMenuByRoleID(ctx, sysRole.ID) + _, err = h.menusvc.SetListByRoleIDToMap(ctx, role.ID) if err != nil { h.render.JSONERR(w, err.Error()) return } // 刷新角色菜单树 (pear admin layui 使用的格式) - _, err = h.biz.SystemV1().MenuBiz().SetRecursiveSysMenus(ctx, sysRole.ID) + _, err = h.menusvc.SetOwerMenus(ctx, role.ID) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -210,15 +198,15 @@ func (h *roleHandler) SetMenu(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() id := convertor.QueryInt[int32](vars, "id", 0) vm := struct { - Role *db.SysRole + Role *system.Role Menus []*dto.SetMenuDto }{} if id > 0 { ctx := r.Context() var err error - vm.Role, err = h.biz.SystemV1().RoleBiz().Get(ctx, id) + vm.Role, err = h.svc.Get(ctx, id) if err == nil { - vm.Menus, _ = h.biz.SystemV1().MenuBiz().SetMenuViewData(ctx, vm.Role.ID) + vm.Menus, _ = h.menusvc.MenuViewData(ctx, vm.Role.ID) } } @@ -226,52 +214,52 @@ func (h *roleHandler) SetMenu(w http.ResponseWriter, r *http.Request) { "Item": vm, }) case http.MethodPost: + ctx := r.Context() id := convertor.ConvertInt[int32](r.PostFormValue("ID"), 0) - menus := r.PostFormValue("roleMenu") - if id == 0 { h.render.JSONERR(w, "角色异常, 请刷新重试") return } - - if len(menus) == 0 { - h.render.JSONERR(w, "请选择菜单") - return - } - - ctx := r.Context() - _, err := h.biz.SystemV1().RoleBiz().Get(ctx, id) + role, err := h.svc.Get(ctx, id) if err != nil { h.render.JSONERR(w, err.Error()) return } + menus := r.PostFormValue("roleMenu") + if len(menus) == 0 { + h.render.JSONERR(w, "请选择菜单") + return + } menuArr := strings.Split(menus, ",") if len(menuArr) == 0 { h.render.JSONERR(w, "请选择菜单") return } - var menuList []*db.SysMenu + var rms []*system.RoleMenu for _, v := range menuArr { menuID := convertor.ConvertInt(v, 0) if menuID > 0 { - menu, err := h.biz.SystemV1().MenuBiz().Get(ctx, int32(menuID)) + menu, err := h.menusvc.Get(ctx, int32(menuID)) if err != nil { h.render.JSONERR(w, err.Error()) return } - menuList = append(menuList, menu) + rms = append(rms, &system.RoleMenu{ + RoleID: role.ID, + MenuID: menu.ID, + }) } } - if len(menuList) == 0 { + if len(rms) == 0 { h.render.JSONERR(w, "请选择正确的菜单") return } - err = h.biz.SystemV1().MenuBiz().SetMenu(ctx, id, menuList) + err = h.menusvc.SetRoleMenu(ctx, id, rms) if err != nil { h.render.JSONERR(w, err.Error()) return diff --git a/internal/erpserver/handler/system/system.go b/internal/erpserver/handler/system/system.go deleted file mode 100644 index 52a0c66..0000000 --- a/internal/erpserver/handler/system/system.go +++ /dev/null @@ -1,76 +0,0 @@ -package system - -import ( - "net/http" - - "management/internal/erpserver/biz" - "management/internal/pkg/middleware" - "management/internal/pkg/redis" - "management/internal/pkg/session" - "management/internal/pkg/tpl" -) - -type SystemHandler interface { - Home(w http.ResponseWriter, req *http.Request) - Dashboard(w http.ResponseWriter, req *http.Request) - UserHandler() UserHandler - MenuHandler() MenuHandler - RoleHandler() RoleHandler - DepartmentHandler() DepartmentHandler - ConfigHandler() ConfigHandler - AuditHandler() AuditHandler - LoginLogHandler() LoginLogHandler - CategoryHandler() CategoryHandler -} - -type systemHandler struct { - render tpl.Renderer - redis redis.IRedis - session session.ISession - biz biz.IBiz - mi middleware.IMiddleware -} - -var _ SystemHandler = (*systemHandler)(nil) - -func NewSystemHandler(render tpl.Renderer, redis redis.IRedis, session session.ISession, biz biz.IBiz, mi middleware.IMiddleware) *systemHandler { - return &systemHandler{ - render: render, - redis: redis, - session: session, - biz: biz, - mi: mi, - } -} - -func (h *systemHandler) UserHandler() UserHandler { - return NewUserHandler(h.render, h.session, h.biz, h.mi) -} - -func (h *systemHandler) MenuHandler() MenuHandler { - return NewMenuHandler(h.render, h.session, h.biz) -} - -func (h *systemHandler) RoleHandler() RoleHandler { - return NewRoleHandler(h.render, h.biz) -} - -func (h *systemHandler) DepartmentHandler() DepartmentHandler { - return NewDepartmentHandler(h.render, h.biz) -} - -func (h *systemHandler) ConfigHandler() ConfigHandler { - return NewConfigHandler(h.render, h.redis, h.biz) -} - -func (h *systemHandler) AuditHandler() AuditHandler { - return NewAuditHandler(h.render, h.biz) -} - -func (h *systemHandler) LoginLogHandler() LoginLogHandler { - return NewLoginLogHandler(h.render, h.biz) -} - -func (h *systemHandler) CategoryHandler() CategoryHandler { - return NewCategoryHandler(h.render, h.biz) -} diff --git a/internal/erpserver/handler/system/user.go b/internal/erpserver/handler/system/user.go index e34b7f9..b18a8e7 100644 --- a/internal/erpserver/handler/system/user.go +++ b/internal/erpserver/handler/system/user.go @@ -1,57 +1,47 @@ package system import ( - "encoding/json" + "log" "net/http" "management/internal/db/model/dto" db "management/internal/db/sqlc" - "management/internal/erpserver/biz" "management/internal/erpserver/model/form" + systemmodel "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/binding" "management/internal/pkg/convertor" - "management/internal/pkg/know" "management/internal/pkg/middleware" - "management/internal/pkg/session" "management/internal/pkg/tpl" "management/internal/pkg/tpl/html" - - "github.com/zhang2092/browser" ) -type UserHandler interface { - Add(w http.ResponseWriter, r *http.Request) - Edit(w http.ResponseWriter, r *http.Request) - Save(w http.ResponseWriter, r *http.Request) - List(w http.ResponseWriter, r *http.Request) - Profile(w http.ResponseWriter, r *http.Request) - Data(w http.ResponseWriter, r *http.Request) - - UserExpansion -} - -type UserExpansion interface { - Login(w http.ResponseWriter, r *http.Request) - Logout(w http.ResponseWriter, r *http.Request) -} - // userHandler 是 UserHandler 接口的实现. type userHandler struct { - render tpl.Renderer - session session.ISession - biz biz.IBiz - mi middleware.IMiddleware + render tpl.Renderer + mi middleware.Middleware + + captchasvc v1.CaptchaService + usersvc v1.UserService + rolesvc v1.RoleService + departmentsvc v1.DepartmentService } -// 确保 userHandler 实现了 UserHandler 接口. -var _ UserHandler = (*userHandler)(nil) - -func NewUserHandler(render tpl.Renderer, session session.ISession, biz biz.IBiz, mi middleware.IMiddleware) *userHandler { +func NewUserHandler( + render tpl.Renderer, + mi middleware.Middleware, + captchasvc v1.CaptchaService, + usersvc v1.UserService, + rolesvc v1.RoleService, + departmentsvc v1.DepartmentService, +) *userHandler { return &userHandler{ - render: render, - session: session, - biz: biz, - mi: mi, + render: render, + mi: mi, + captchasvc: captchasvc, + usersvc: usersvc, + rolesvc: rolesvc, + departmentsvc: departmentsvc, } } @@ -66,16 +56,16 @@ func (h *userHandler) Add(w http.ResponseWriter, r *http.Request) { func (h *userHandler) Edit(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() id := convertor.QueryInt[int32](vars, "id", 0) - sysUser := &db.SysUser{} + user := &systemmodel.User{} if id > 0 { ctx := r.Context() - if user, err := h.biz.SystemV1().UserBiz().Get(ctx, id); err == nil { + if u, err := h.usersvc.Get(ctx, id); err == nil { user.HashedPassword = []byte("********") - sysUser = user + user = u } } h.render.HTML(w, r, "user/edit.tmpl", map[string]any{ - "Item": sysUser, + "Item": user, }) } @@ -88,20 +78,20 @@ func (h *userHandler) Save(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if req.DepartmentID > 0 { - if _, err := h.biz.SystemV1().DepartmentBiz().Get(ctx, req.DepartmentID); err != nil { + if _, err := h.departmentsvc.Get(ctx, req.DepartmentID); err != nil { h.render.JSONERR(w, "部门数据错误") return } } if req.RoleID > 0 { - if _, err := h.biz.SystemV1().RoleBiz().Get(ctx, req.RoleID); err != nil { + if _, err := h.rolesvc.Get(ctx, req.RoleID); err != nil { h.render.JSONERR(w, "角色数据错误") return } } if *req.ID == 0 { - err := h.biz.SystemV1().UserBiz().Create(ctx, &req) + err := h.usersvc.Create(ctx, &req) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -109,7 +99,7 @@ func (h *userHandler) Save(w http.ResponseWriter, r *http.Request) { h.render.JSONOK(w, "添加成功") } else { - err := h.biz.SystemV1().UserBiz().Update(ctx, &req) + err := h.usersvc.Update(ctx, &req) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -127,13 +117,14 @@ func (h *userHandler) List(w http.ResponseWriter, r *http.Request) { }) case http.MethodPost: var q dto.SearchDto + q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd")) q.SearchStatus = convertor.ConvertInt(r.PostFormValue("status"), 9999) q.SearchName = r.PostFormValue("name") q.SearchEmail = r.PostFormValue("email") q.SearchID = convertor.ConvertInt[int64](r.PostFormValue("id"), 0) q.Page = convertor.ConvertInt(r.PostFormValue("page"), 1) q.Rows = convertor.ConvertInt(r.PostFormValue("rows"), 10) - res, count, err := h.biz.SystemV1().UserBiz().List(r.Context(), q) + res, count, err := h.usersvc.List(r.Context(), q) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -154,7 +145,7 @@ func (h *userHandler) List(w http.ResponseWriter, r *http.Request) { func (h *userHandler) Profile(w http.ResponseWriter, r *http.Request) { ctx := r.Context() user := h.mi.AuthUser(ctx) - vm, _ := h.biz.SystemV1().UserBiz().Get(ctx, user.ID) + vm, _ := h.usersvc.Get(ctx, user.ID) h.render.HTML(w, r, "user/profile.tmpl", map[string]any{ "Item": vm, }) @@ -164,7 +155,7 @@ func (h *userHandler) Data(w http.ResponseWriter, r *http.Request) { vars := r.URL.Query() t := vars.Get("type") if t == "xmselect" { - res, err := h.biz.SystemV1().UserBiz().XmSelect(r.Context()) + res, err := h.usersvc.XmSelect(r.Context()) if err != nil { h.render.JSONERR(w, err.Error()) return @@ -181,17 +172,11 @@ func (h *userHandler) Login(w http.ResponseWriter, r *http.Request) { ctx := r.Context() switch r.Method { case http.MethodGet: - var user dto.AuthorizeUser - u := h.session.GetBytes(ctx, know.StoreName) - if err := json.Unmarshal(u, &user); err == nil { - // 判断用户是否登陆, 已经登陆则刷新令牌,跳转到首页 - if err := h.session.RenewToken(ctx); err == nil { - h.session.Put(ctx, know.StoreName, u) - http.Redirect(w, r, "/home.html", http.StatusFound) - return - } + if h.mi.IsAuth(ctx) && h.mi.RefreshToken(ctx) { + http.Redirect(w, r, "/home.html", http.StatusFound) + return } - h.session.Destroy(ctx) + _ = h.mi.Destroy(ctx) h.render.HTML(w, r, "oauth/login.tmpl", nil) case http.MethodPost: defer r.Body.Close() @@ -202,35 +187,26 @@ func (h *userHandler) Login(w http.ResponseWriter, r *http.Request) { return } - if !h.biz.CommonV1().CaptchaBiz().Verify(req.CaptchaID, req.Captcha, true) { + if !h.captchasvc.Verify(req.CaptchaID, req.Captcha, true) { h.render.JSONERR(w, "验证码错误") return } - br, err := browser.NewBrowser(r.Header.Get("User-Agent")) - if err != nil { - h.render.JSONERR(w, "平台信息获取错误") - return - } - - req.Ip = r.RemoteAddr - req.Referrer = r.Header.Get("Referer") - req.Url = r.URL.RequestURI() - req.Os = br.Platform().Name() - req.Browser = br.Name() - err = h.biz.SystemV1().UserBiz().Login(ctx, &req) + req = req.SetAttributes(r) + err := h.usersvc.Login(ctx, &req) if err != nil { + log.Println(err) h.render.JSONERR(w, err.Error()) return } - h.render.JSONOK(w, "login successful") + h.render.JSONOK(w, "login successfully") default: http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) } } func (h *userHandler) Logout(w http.ResponseWriter, r *http.Request) { - h.session.Destroy(r.Context()) + h.mi.Destroy(r.Context()) http.Redirect(w, r, "/", http.StatusFound) } diff --git a/internal/erpserver/http.go b/internal/erpserver/http.go index eb8c00a..a44c98a 100644 --- a/internal/erpserver/http.go +++ b/internal/erpserver/http.go @@ -1,191 +1,207 @@ package erpserver -import ( - "net/http" +// import ( +// "net/http" - "management/internal/erpserver/handler" - mw "management/internal/pkg/middleware" +// "management/internal/erpserver/handler" +// mw "management/internal/pkg/middleware" - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" -) +// "github.com/go-chi/chi/v5" +// "github.com/go-chi/chi/v5/middleware" +// ) -func NewRouter(handler handler.IHandler, mw mw.IMiddleware) *chi.Mux { - r := chi.NewRouter() +// func NewRouter(handler handler.IHandler, mw mw.Middleware) *chi.Mux { +// r := chi.NewRouter() - r.Use(middleware.RequestID) - r.Use(middleware.RealIP) - // r.Use(middleware.Logger) - r.Use(middleware.Recoverer) +// r.Use(middleware.RequestID) +// r.Use(middleware.RealIP) +// // r.Use(middleware.Logger) +// r.Use(middleware.Recoverer) - staticServer := http.FileServer(http.Dir("./web/statics/")) - r.Handle("/statics/*", http.StripPrefix("/statics", staticServer)) +// staticServer := http.FileServer(http.Dir("./web/statics/")) +// r.Handle("/statics/*", http.StripPrefix("/statics", staticServer)) - uploadServer := http.FileServer(http.Dir("./upload/")) - r.Handle("/upload/*", http.StripPrefix("/upload", uploadServer)) +// uploadServer := http.FileServer(http.Dir("./upload/")) +// r.Handle("/upload/*", http.StripPrefix("/upload", uploadServer)) - r.Group(func(r chi.Router) { - r.Use(mw.NoSurf) // CSRF - r.Use(mw.LoadSession) // Session +// r.Group(func(r chi.Router) { +// r.Use(mw.NoSurf) // CSRF +// r.Use(mw.LoadSession) // Session - r.Get("/captcha", handler.CommonHandler().CaptchaHandler().Captcha) +// // capthchaHandler := common.NewCaptchaHandler(handler)) +// // r.Get("/captcha", handler.CommonHandler().CaptchaHandler().Captcha) - r.Get("/", handler.SystemHandler().UserHandler().Login) - r.Post("/login", handler.SystemHandler().UserHandler().Login) - r.Get("/logout", handler.SystemHandler().UserHandler().Logout) +// r.Get("/", handler.SystemHandler().UserHandler().Login) +// r.Post("/login", handler.SystemHandler().UserHandler().Login) +// r.Get("/logout", handler.SystemHandler().UserHandler().Logout) - r.With(mw.Authorize, mw.Audit).Post("/upload/img", handler.CommonHandler().UploadHandler().Img) - r.With(mw.Authorize, mw.Audit).Post("/upload/file", handler.CommonHandler().UploadHandler().File) - r.With(mw.Authorize, mw.Audit).Post("/upload/mutilfile", handler.CommonHandler().UploadHandler().MutilFiles) +// r.With(mw.Authorize, mw.Audit).Post("/upload/img", handler.CommonHandler().UploadHandler().Img) +// r.With(mw.Authorize, mw.Audit).Post("/upload/file", handler.CommonHandler().UploadHandler().File) +// r.With(mw.Authorize, mw.Audit).Post("/upload/mutilfile", handler.CommonHandler().UploadHandler().MutilFiles) - r.With(mw.Authorize).Get("/home.html", handler.SystemHandler().Home) - r.With(mw.Authorize).Get("/dashboard", handler.SystemHandler().Dashboard) - r.With(mw.Authorize).Get("/pear.json", handler.SystemHandler().ConfigHandler().Pear) +// r.With(mw.Authorize).Get("/home.html", handler.SystemHandler().Home) +// r.With(mw.Authorize).Get("/dashboard", handler.SystemHandler().Dashboard) +// r.With(mw.Authorize).Get("/pear.json", handler.SystemHandler().ConfigHandler().Pear) - r.Route("/system", func(r chi.Router) { - r.Use(mw.Authorize) +// r.Route("/system", func(r chi.Router) { +// r.Use(mw.Authorize) - r.Route("/config", func(r chi.Router) { - r.Use(mw.Audit) - r.Get("/list", handler.SystemHandler().ConfigHandler().List) - r.Post("/list", handler.SystemHandler().ConfigHandler().List) - r.Get("/add", handler.SystemHandler().ConfigHandler().Add) - r.Get("/edit", handler.SystemHandler().ConfigHandler().Edit) - r.Post("/save", handler.SystemHandler().ConfigHandler().Save) - r.Post("/reset_pear", handler.SystemHandler().ConfigHandler().ResetPear) - r.Post("/refresh", handler.SystemHandler().ConfigHandler().Refresh) - }) +// r.Route("/config", func(r chi.Router) { +// r.Use(mw.Audit) +// r.Get("/list", handler.SystemHandler().ConfigHandler().List) +// r.Post("/list", handler.SystemHandler().ConfigHandler().List) +// r.Get("/add", handler.SystemHandler().ConfigHandler().Add) +// r.Get("/edit", handler.SystemHandler().ConfigHandler().Edit) +// r.Post("/save", handler.SystemHandler().ConfigHandler().Save) +// r.Post("/reset_pear", handler.SystemHandler().ConfigHandler().ResetPear) +// r.Post("/refresh", handler.SystemHandler().ConfigHandler().Refresh) +// }) - r.Route("/department", func(r chi.Router) { - r.Use(mw.Audit) - r.Get("/list", handler.SystemHandler().DepartmentHandler().List) - r.Post("/list", handler.SystemHandler().DepartmentHandler().List) - r.Get("/add", handler.SystemHandler().DepartmentHandler().Add) - r.Get("/add_children", handler.SystemHandler().DepartmentHandler().AddChildren) - r.Get("/edit", handler.SystemHandler().DepartmentHandler().Edit) - r.Post("/save", handler.SystemHandler().DepartmentHandler().Save) - r.Post("/data", handler.SystemHandler().DepartmentHandler().Data) - r.Post("/refresh", handler.SystemHandler().DepartmentHandler().Refresh) - r.Post("/rebuild_parent_path", handler.SystemHandler().DepartmentHandler().RebuildParentPath) - }) +// r.Route("/department", func(r chi.Router) { +// r.Use(mw.Audit) +// r.Get("/list", handler.SystemHandler().DepartmentHandler().List) +// r.Post("/list", handler.SystemHandler().DepartmentHandler().List) +// r.Get("/add", handler.SystemHandler().DepartmentHandler().Add) +// r.Get("/add_children", handler.SystemHandler().DepartmentHandler().AddChildren) +// r.Get("/edit", handler.SystemHandler().DepartmentHandler().Edit) +// r.Post("/save", handler.SystemHandler().DepartmentHandler().Save) +// r.Post("/data", handler.SystemHandler().DepartmentHandler().Data) +// r.Post("/refresh", handler.SystemHandler().DepartmentHandler().Refresh) +// r.Post("/rebuild_parent_path", handler.SystemHandler().DepartmentHandler().RebuildParentPath) +// }) - r.Route("/role", func(r chi.Router) { - r.Use(mw.Audit) - r.Get("/list", handler.SystemHandler().RoleHandler().List) - r.Post("/list", handler.SystemHandler().RoleHandler().List) - r.Get("/add", handler.SystemHandler().RoleHandler().Add) - r.Get("/add_children", handler.SystemHandler().RoleHandler().AddChildren) - r.Get("/edit", handler.SystemHandler().RoleHandler().Edit) - r.Post("/save", handler.SystemHandler().RoleHandler().Save) - r.Post("/data", handler.SystemHandler().RoleHandler().Data) - r.Post("/refresh", handler.SystemHandler().RoleHandler().Refresh) - r.Post("/rebuild_parent_path", handler.SystemHandler().RoleHandler().RebuildParentPath) - r.Post("/refresh_role_menus", handler.SystemHandler().RoleHandler().RefreshRoleMenus) - r.Get("/set_menu", handler.SystemHandler().RoleHandler().SetMenu) - r.Post("/set_menu", handler.SystemHandler().RoleHandler().SetMenu) - }) +// r.Route("/role", func(r chi.Router) { +// r.Use(mw.Audit) +// r.Get("/list", handler.SystemHandler().RoleHandler().List) +// r.Post("/list", handler.SystemHandler().RoleHandler().List) +// r.Get("/add", handler.SystemHandler().RoleHandler().Add) +// r.Get("/add_children", handler.SystemHandler().RoleHandler().AddChildren) +// r.Get("/edit", handler.SystemHandler().RoleHandler().Edit) +// r.Post("/save", handler.SystemHandler().RoleHandler().Save) +// r.Post("/data", handler.SystemHandler().RoleHandler().Data) +// r.Post("/refresh", handler.SystemHandler().RoleHandler().Refresh) +// r.Post("/rebuild_parent_path", handler.SystemHandler().RoleHandler().RebuildParentPath) +// r.Post("/refresh_role_menus", handler.SystemHandler().RoleHandler().RefreshRoleMenus) +// r.Get("/set_menu", handler.SystemHandler().RoleHandler().SetMenu) +// r.Post("/set_menu", handler.SystemHandler().RoleHandler().SetMenu) +// }) - r.Route("/user", func(r chi.Router) { - r.Get("/list", handler.SystemHandler().UserHandler().List) - r.Post("/list", handler.SystemHandler().UserHandler().List) - r.Get("/add", handler.SystemHandler().UserHandler().Add) - r.Get("/edit", handler.SystemHandler().UserHandler().Edit) - r.Post("/save", handler.SystemHandler().UserHandler().Save) - r.Get("/profile", handler.SystemHandler().UserHandler().Profile) - r.Post("/data", handler.SystemHandler().UserHandler().Data) - }) +// r.Route("/user", func(r chi.Router) { +// r.Get("/list", handler.SystemHandler().UserHandler().List) +// r.Post("/list", handler.SystemHandler().UserHandler().List) +// r.Get("/add", handler.SystemHandler().UserHandler().Add) +// r.Get("/edit", handler.SystemHandler().UserHandler().Edit) +// r.Post("/save", handler.SystemHandler().UserHandler().Save) +// r.Get("/profile", handler.SystemHandler().UserHandler().Profile) +// r.Post("/data", handler.SystemHandler().UserHandler().Data) +// }) - r.Route("/login_log", func(r chi.Router) { - r.Get("/list", handler.SystemHandler().LoginLogHandler().List) - r.Post("/list", handler.SystemHandler().LoginLogHandler().List) - }) +// r.Route("/login_log", func(r chi.Router) { +// r.Get("/list", handler.SystemHandler().LoginLogHandler().List) +// r.Post("/list", handler.SystemHandler().LoginLogHandler().List) +// }) - r.Route("/audit_log", func(r chi.Router) { - r.Get("/list", handler.SystemHandler().AuditHandler().List) - r.Post("/list", handler.SystemHandler().AuditHandler().List) - }) +// r.Route("/audit_log", func(r chi.Router) { +// r.Get("/list", handler.SystemHandler().AuditHandler().List) +// r.Post("/list", handler.SystemHandler().AuditHandler().List) +// }) - r.Get("/menus", handler.SystemHandler().MenuHandler().Menus) - r.Route("/menu", func(r chi.Router) { - r.Use(mw.Audit) - r.Get("/list", handler.SystemHandler().MenuHandler().List) - r.Post("/list", handler.SystemHandler().MenuHandler().List) - r.Get("/add", handler.SystemHandler().MenuHandler().Add) - r.Get("/add_children", handler.SystemHandler().MenuHandler().AddChildren) - r.Get("/edit", handler.SystemHandler().MenuHandler().Edit) - r.Post("/save", handler.SystemHandler().MenuHandler().Save) - r.Post("/data", handler.SystemHandler().MenuHandler().Data) - r.Post("/refresh_cache", handler.SystemHandler().MenuHandler().Refresh) - }) +// r.Get("/menus", handler.SystemHandler().MenuHandler().Menus) +// r.Route("/menu", func(r chi.Router) { +// r.Use(mw.Audit) +// r.Get("/list", handler.SystemHandler().MenuHandler().List) +// r.Post("/list", handler.SystemHandler().MenuHandler().List) +// r.Get("/add", handler.SystemHandler().MenuHandler().Add) +// r.Get("/add_children", handler.SystemHandler().MenuHandler().AddChildren) +// r.Get("/edit", handler.SystemHandler().MenuHandler().Edit) +// r.Post("/save", handler.SystemHandler().MenuHandler().Save) +// r.Post("/data", handler.SystemHandler().MenuHandler().Data) +// r.Post("/refresh_cache", handler.SystemHandler().MenuHandler().Refresh) +// }) - r.Route("/category", func(r chi.Router) { - r.Use(mw.Audit) - r.Get("/list", handler.SystemHandler().CategoryHandler().List) - r.Post("/list", handler.SystemHandler().CategoryHandler().List) - r.Get("/add", handler.SystemHandler().CategoryHandler().Add) - r.Get("/add_children", handler.SystemHandler().CategoryHandler().AddChildren) - r.Get("/edit", handler.SystemHandler().CategoryHandler().Edit) - r.Post("/save", handler.SystemHandler().CategoryHandler().Save) - r.Post("/data", handler.SystemHandler().CategoryHandler().Data) - r.Post("/refresh", handler.SystemHandler().CategoryHandler().Refresh) - r.Post("/rebuild_parent_path", handler.SystemHandler().CategoryHandler().RebuildParentPath) - }) - }) +// r.Route("/category", func(r chi.Router) { +// r.Use(mw.Audit) +// r.Get("/list", handler.SystemHandler().CategoryHandler().List) +// r.Post("/list", handler.SystemHandler().CategoryHandler().List) +// r.Get("/add", handler.SystemHandler().CategoryHandler().Add) +// r.Get("/add_children", handler.SystemHandler().CategoryHandler().AddChildren) +// r.Get("/edit", handler.SystemHandler().CategoryHandler().Edit) +// r.Post("/save", handler.SystemHandler().CategoryHandler().Save) +// r.Post("/data", handler.SystemHandler().CategoryHandler().Data) +// r.Post("/refresh", handler.SystemHandler().CategoryHandler().Refresh) +// r.Post("/rebuild_parent_path", handler.SystemHandler().CategoryHandler().RebuildParentPath) +// }) +// }) - // 客户 - r.Route("/customer", func(r chi.Router) { - r.Use(mw.Authorize) - r.Get("/list", handler.CustomerHandler().List) - r.Post("/list", handler.CustomerHandler().List) - r.Get("/add", handler.CustomerHandler().Add) - r.Get("/edit", handler.CustomerHandler().Edit) - r.Post("/save", handler.CustomerHandler().Save) - }) +// // 客户 +// r.Route("/customer", func(r chi.Router) { +// r.Use(mw.Authorize) +// r.Get("/list", handler.CustomerHandler().List) +// r.Post("/list", handler.CustomerHandler().List) +// r.Get("/add", handler.CustomerHandler().Add) +// r.Get("/edit", handler.CustomerHandler().Edit) +// r.Post("/save", handler.CustomerHandler().Save) +// }) - // 项目 - r.Route("/project", func(r chi.Router) { - r.Use(mw.Authorize) - r.Get("/list", handler.ProjectHandler().List) - r.Post("/list", handler.ProjectHandler().List) - r.Get("/add", handler.ProjectHandler().Add) - r.Get("/edit", handler.ProjectHandler().Edit) - r.Post("/save", handler.ProjectHandler().Save) - r.Post("/data", handler.ProjectHandler().Data) - r.Get("/dashboard", handler.ProjectHandler().Dashboard) - r.Post("/dashboard", handler.ProjectHandler().Dashboard) - }) +// // 项目 +// r.Route("/project", func(r chi.Router) { +// r.Use(mw.Authorize) +// r.Get("/list", handler.ProjectHandler().List) +// r.Post("/list", handler.ProjectHandler().List) +// r.Get("/add", handler.ProjectHandler().Add) +// r.Get("/edit", handler.ProjectHandler().Edit) +// r.Post("/save", handler.ProjectHandler().Save) +// r.Post("/data", handler.ProjectHandler().Data) +// r.Get("/dashboard", handler.ProjectHandler().Dashboard) +// r.Post("/dashboard", handler.ProjectHandler().Dashboard) +// }) - // 项目预算 - r.Route("/budget", func(r chi.Router) { - r.Use(mw.Authorize) - r.Get("/list", handler.BudgetHandler().List) - r.Post("/list", handler.BudgetHandler().List) - r.Get("/add", handler.BudgetHandler().Add) - r.Get("/edit", handler.BudgetHandler().Edit) - r.Post("/save", handler.BudgetHandler().Save) - r.Post("/data", handler.BudgetHandler().Data) - }) +// // 项目预算 +// r.Route("/budget", func(r chi.Router) { +// r.Use(mw.Authorize) +// r.Get("/list", handler.BudgetHandler().List) +// r.Post("/list", handler.BudgetHandler().List) +// r.Get("/add", handler.BudgetHandler().Add) +// r.Get("/edit", handler.BudgetHandler().Edit) +// r.Post("/save", handler.BudgetHandler().Save) +// r.Post("/data", handler.BudgetHandler().Data) +// }) - // 回款单 - r.Route("/income", func(r chi.Router) { - r.Use(mw.Authorize) - r.Get("/list", handler.IncomeHandler().List) - r.Post("/list", handler.IncomeHandler().List) - r.Get("/add", handler.IncomeHandler().Add) - r.Get("/edit", handler.IncomeHandler().Edit) - r.Post("/save", handler.IncomeHandler().Save) - }) +// // 回款单 +// r.Route("/income", func(r chi.Router) { +// r.Use(mw.Authorize) +// r.Get("/list", handler.IncomeHandler().List) +// r.Post("/list", handler.IncomeHandler().List) +// r.Get("/add", handler.IncomeHandler().Add) +// r.Get("/edit", handler.IncomeHandler().Edit) +// r.Post("/save", handler.IncomeHandler().Save) +// }) - // 费用报销单 - r.Route("/expense", func(r chi.Router) { - r.Use(mw.Authorize) - r.Get("/list", handler.ExpenseHandler().List) - r.Post("/list", handler.ExpenseHandler().List) - r.Get("/add", handler.ExpenseHandler().Add) - r.Get("/edit", handler.ExpenseHandler().Edit) - r.Post("/save", handler.ExpenseHandler().Save) - }) - }) +// // 费用报销单 +// r.Route("/expense", func(r chi.Router) { +// r.Use(mw.Authorize) +// r.Get("/list", handler.ExpenseHandler().List) +// r.Post("/list", handler.ExpenseHandler().List) +// r.Get("/add", handler.ExpenseHandler().Add) +// r.Get("/edit", handler.ExpenseHandler().Edit) +// r.Post("/save", handler.ExpenseHandler().Save) +// }) +// }) - return r -} +// return r +// } + +/* + +我现在要设计一个订单号,要求如下: +1、可读性强,但不能被预测:包含:业务标识(订单、支付),便于人工识别。 +2、唯一性:确保全局唯一,不能重复(特别是高并发场景下)。 +3、值递增:生成的订单号都是递增,可排序。 +4、高性能:支持高并发生成,避免阻塞或延迟,设计的时候尽量考虑内存生成,最差也是基于Redis生成。 +5、能适应分库分表:订单号中包括分库分表信息,方便路由。 +6、分布式部署:以服务的方式对外提供,集群部署避免单点瓶颈, + +用golang语言开发 +该chat使用中文交流 + +*/ diff --git a/internal/erpserver/model/form/user.go b/internal/erpserver/model/form/user.go index b8ae7c8..337a3e4 100644 --- a/internal/erpserver/model/form/user.go +++ b/internal/erpserver/model/form/user.go @@ -1,5 +1,11 @@ package form +import ( + "net/http" + + "github.com/zhang2092/browser" +) + type Login struct { Email string `form:"email" binding:"required,email"` Password string `form:"password" binding:"required,min=6"` @@ -14,6 +20,20 @@ type Login struct { Url string } +func (login Login) SetAttributes(r *http.Request) Login { + login.Ip = r.RemoteAddr + login.Referrer = r.Header.Get("Referer") + login.Url = r.URL.RequestURI() + + br, err := browser.NewBrowser(r.Header.Get("User-Agent")) + if err == nil { + login.Os = br.Platform().Name() + login.Browser = br.Name() + } + + return login +} + type User struct { ID *int32 `form:"id" binding:"required"` Email string `form:"email" binding:"required,email"` diff --git a/internal/erpserver/model/system/audit_log.go b/internal/erpserver/model/system/audit_log.go new file mode 100644 index 0000000..482a504 --- /dev/null +++ b/internal/erpserver/model/system/audit_log.go @@ -0,0 +1,71 @@ +package system + +import ( + "context" + "net/http" + "strconv" + "strings" + "time" + + "management/internal/db/model/dto" +) + +type AuditLogRepository interface { + Create(ctx context.Context, obj *AuditLog) error + List(ctx context.Context, q dto.SearchDto) ([]*AuditLog, int64, error) +} + +type AuditLog struct { + ID int64 `json:"id"` + CreatedAt time.Time `json:"created_at"` + Email string `json:"email"` + StartAt time.Time `json:"start_at"` + EndAt time.Time `json:"end_at"` + Duration string `json:"duration"` + Url string `json:"url"` + Method string `json:"method"` + Parameters string `json:"parameters"` + RefererUrl string `json:"referer_url"` + Os string `json:"os"` + Ip string `json:"ip"` + Browser string `json:"browser"` + Remark string `json:"remark"` +} + +func (AuditLog) TableName() string { + return "sys_audit_log" +} + +func NewAuditLog(r *http.Request, email, os, browser string, start, end time.Time) *AuditLog { + var params string + method := r.Method + if method == "GET" { + params = r.URL.Query().Encode() + } else if method == "POST" { + contentType := r.Header.Get("Content-Type") + if strings.Contains(contentType, "application/json") { + body := make([]byte, r.ContentLength) + r.Body.Read(body) + params = string(body) + } else if strings.Contains(contentType, "application/x-www-form-urlencoded") { + params = r.Form.Encode() + } + } + + duration := end.Sub(start) + + return &AuditLog{ + CreatedAt: time.Now(), + Email: email, + StartAt: start, + EndAt: end, + Duration: strconv.FormatInt(duration.Milliseconds(), 10), + Url: r.URL.RequestURI(), + Method: method, + Parameters: params, + RefererUrl: r.Header.Get("Referer"), + Os: os, + Ip: r.RemoteAddr, + Browser: browser, + } +} diff --git a/internal/erpserver/model/system/config.go b/internal/erpserver/model/system/config.go new file mode 100644 index 0000000..ae172ac --- /dev/null +++ b/internal/erpserver/model/system/config.go @@ -0,0 +1,28 @@ +package system + +import ( + "context" + "time" + + "management/internal/db/model/dto" +) + +type ConfigRepository interface { + Create(ctx context.Context, obj *Config) error + Update(ctx context.Context, obj *Config) error + Get(ctx context.Context, id int32) (*Config, error) + GetByKey(ctx context.Context, key string) (*Config, error) + List(ctx context.Context, q dto.SearchDto) ([]*Config, int64, error) +} + +type Config struct { + ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + Key string `json:"key" gorm:"type:varchar(200);not null;uniqueIndex"` + Value []byte `json:"value" gorm:"type:bytea;not null;"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamptz;not null;default:'now()'"` + UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamptz;not null;default:'0001-01-01 00:00:00+8';"` +} + +func (Config) TableName() string { + return "sys_config" +} diff --git a/internal/erpserver/model/system/department.go b/internal/erpserver/model/system/department.go new file mode 100644 index 0000000..e5f6fc7 --- /dev/null +++ b/internal/erpserver/model/system/department.go @@ -0,0 +1,32 @@ +package system + +import ( + "context" + "time" + + "management/internal/db/model/dto" +) + +type DepartmentRepository interface { + Create(ctx context.Context, obj *Department) error + Update(ctx context.Context, obj *Department) error + Get(ctx context.Context, id int32) (*Department, error) + All(ctx context.Context) ([]*Department, error) + List(ctx context.Context, q dto.SearchDto) ([]*Department, int64, error) + RebuildParentPath(ctx context.Context) error +} + +type Department struct { + ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + Name string `json:"name" gorm:"type:varchar(200);not null;uniqueIndex"` + ParentID int32 `json:"parent_id" gorm:"type:int;not null;"` + ParentPath string `json:"parent_path" gorm:"type:varchar(500);not null;"` + Status int32 `json:"status" gorm:"type:int;not null;default:0;"` + Sort int32 `json:"sort" gorm:"type:int;not null;default:0;"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamptz;not null;default:'now()'"` + UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamptz;not null;default:'0001-01-01 00:00:00+8';"` +} + +func (Department) TableName() string { + return "sys_department" +} diff --git a/internal/erpserver/model/system/login_log.go b/internal/erpserver/model/system/login_log.go new file mode 100644 index 0000000..f555976 --- /dev/null +++ b/internal/erpserver/model/system/login_log.go @@ -0,0 +1,56 @@ +package system + +import ( + "context" + "time" + + "management/internal/db/model/dto" +) + +type LoginLogRepository interface { + Create(ctx context.Context, obj *LoginLog) error + GetLatest(ctx context.Context, email string) (*LoginLog, error) + List(ctx context.Context, q dto.SearchDto) ([]*LoginLog, int64, error) + Count(ctx context.Context, email string) (int64, error) +} + +type LoginLog struct { + ID int64 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamptz;not null;default:'now()'"` + Email string `json:"email" gorm:"type:varchar(100);not null;"` + IsSuccess bool `json:"is_success" gorm:"type:boolean;not null;"` + Message string `json:"message" gorm:"type:varchar(300);not null;"` + RefererUrl string `json:"referer_url" gorm:"type:varchar(500);not null;"` + Url string `json:"url" gorm:"type:varchar(500);not null;"` + Os string `json:"os" gorm:"type:varchar(50);not null;"` + Ip string `json:"ip" gorm:"type:varchar(20);not null;"` + Browser string `json:"browser" gorm:"type:varchar(100);not null;"` +} + +func (LoginLog) TableName() string { + return "sys_user_login_log" +} + +func NewLoginLog(email, os, ip, browser, url, referer string) *LoginLog { + return &LoginLog{ + CreatedAt: time.Now(), + Email: email, + IsSuccess: false, + RefererUrl: referer, + Url: url, + Os: os, + Ip: ip, + Browser: browser, + } +} + +func (l *LoginLog) SetMessage(message string) *LoginLog { + l.Message = message + return l +} + +func (l *LoginLog) SetOk(message string) *LoginLog { + l.Message = message + l.IsSuccess = true + return l +} diff --git a/internal/erpserver/model/system/menu.go b/internal/erpserver/model/system/menu.go new file mode 100644 index 0000000..9983e47 --- /dev/null +++ b/internal/erpserver/model/system/menu.go @@ -0,0 +1,37 @@ +package system + +import ( + "context" + "time" +) + +type MenuRepository interface { + Create(ctx context.Context, obj *Menu) error + Update(ctx context.Context, obj *Menu) error + Get(ctx context.Context, id int32) (*Menu, error) + GetByUrl(ctx context.Context, url string) (*Menu, error) + All(ctx context.Context) ([]*Menu, error) + RebuildParentPath(ctx context.Context) error +} + +type Menu struct { + ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + Name string `json:"name" gorm:"type:varchar(200);not null;uniqueIndex"` + DisplayName string `json:"display_name" gorm:"type:varchar(200);not null;uniqueIndex"` + Url string `json:"url" gorm:"type:varchar(200);not null;"` + Type string `json:"type" gorm:"type:varchar(50);not null;"` + ParentID int32 `json:"parent_id" gorm:"type:int;not null;"` + ParentPath string `json:"parent_path" gorm:"type:varchar(500);not null;"` + Avatar string `json:"avatar" gorm:"type:varchar(100);not null;"` + Style string `json:"style" gorm:"type:varchar(100);not null;"` + Visible bool `json:"visible" gorm:"type:boolean;not null;"` + IsList bool `json:"is_list" gorm:"type:boolean;not null;"` + Status int32 `json:"status" gorm:"type:int;not null;default:0;"` + Sort int32 `json:"sort" gorm:"type:int;not null;default:0;"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamptz;not null;default:'now()'"` + UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamptz;not null;default:'0001-01-01 00:00:00+8';"` +} + +func (Menu) TableName() string { + return "sys_menu" +} diff --git a/internal/erpserver/model/system/role.go b/internal/erpserver/model/system/role.go new file mode 100644 index 0000000..98b2835 --- /dev/null +++ b/internal/erpserver/model/system/role.go @@ -0,0 +1,34 @@ +package system + +import ( + "context" + "time" + + "management/internal/db/model/dto" +) + +type RoleRepository interface { + Create(ctx context.Context, obj *Role) error + Update(ctx context.Context, obj *Role) error + Get(ctx context.Context, id int32) (*Role, error) + All(ctx context.Context) ([]*Role, error) + List(ctx context.Context, q dto.SearchDto) ([]*Role, int64, error) + RebuildParentPath(ctx context.Context) error +} + +type Role struct { + ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + Name string `json:"name" gorm:"type:varchar(200);not null;uniqueIndex"` + DisplayName string `json:"display_name" gorm:"type:varchar(200);not null;uniqueIndex"` + ParentID int32 `json:"parent_id" gorm:"type:int;not null;"` + ParentPath string `json:"parent_path" gorm:"type:varchar(500);not null;"` + Vip bool `json:"-" gorm:"type:boolean;not null;"` + Status int32 `json:"status" gorm:"type:int;not null;default:0;"` + Sort int32 `json:"sort" gorm:"type:int;not null;default:0;"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamptz;not null;default:'now()'"` + UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamptz;not null;default:'0001-01-01 00:00:00+8';"` +} + +func (Role) TableName() string { + return "sys_role" +} diff --git a/internal/erpserver/model/system/role_menu.go b/internal/erpserver/model/system/role_menu.go new file mode 100644 index 0000000..eeb3722 --- /dev/null +++ b/internal/erpserver/model/system/role_menu.go @@ -0,0 +1,18 @@ +package system + +import "context" + +type RoleMenuRepository interface { + Create(ctx context.Context, obj []*RoleMenu) error + DeleteByRoleID(ctx context.Context, roleID int32) error + ListByRoleID(ctx context.Context, roleID int32) ([]*RoleMenu, error) +} + +type RoleMenu struct { + RoleID int32 `json:"role_id" gorm:"primaryKey;autoIncrement:false;type:int;not null;"` + MenuID int32 `json:"menu_id" gorm:"primaryKey;autoIncrement:false;type:int;not null;"` +} + +func (RoleMenu) TableName() string { + return "sys_role_menu" +} diff --git a/internal/erpserver/model/system/user.go b/internal/erpserver/model/system/user.go index 60d01e2..ec66e32 100644 --- a/internal/erpserver/model/system/user.go +++ b/internal/erpserver/model/system/user.go @@ -1,38 +1,41 @@ package system import ( + "context" "time" + "management/internal/db/model/dto" + "github.com/google/uuid" ) +type UserRepository interface { + Create(ctx context.Context, obj *User) error + Update(ctx context.Context, obj *User) error + Get(ctx context.Context, id int32) (*User, error) + GetByEmail(ctx context.Context, email string) (*User, error) + All(ctx context.Context) ([]*User, error) + List(ctx context.Context, q dto.SearchDto) ([]*User, int64, error) +} + type User struct { - ID int32 `json:"id" gorm:"primaryKey"` - Uuid uuid.UUID `json:"uuid"` - // 邮箱地址 - Email string `json:"email"` - // 用户名称 - Username string `json:"username"` - // 加密密码 - HashedPassword []byte `json:"hashed_password"` - // 密码盐值 - Salt string `json:"salt"` - // 头像 - Avatar string `json:"avatar"` - // 性别 - Gender int32 `json:"gender"` - // 部门 - DepartmentID int32 `json:"department_id"` - // 角色 - RoleID int32 `json:"role_id"` - // 状态 - Status int32 `json:"status"` - // 密码修改时间 - ChangePasswordAt time.Time `json:"change_password_at"` - // 创建时间 - CreatedAt time.Time `json:"created_at"` - // 更新时间 - UpdatedAt time.Time `json:"updated_at"` + ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + Uuid uuid.UUID `json:"uuid" gorm:"type:uuid;not null;uniqueIndex"` + Email string `json:"email" gorm:"type:varchar(100);not null;uniqueIndex"` + Username string `json:"username" gorm:"type:varchar(100);not null;uniqueIndex"` + HashedPassword []byte `json:"-" gorm:"type:bytea;not null;"` + Salt string `json:"-" gorm:"type:varchar(20);not null;"` + Avatar string `json:"avatar" gorm:"type:varchar(200);not null;"` + Gender int32 `json:"gender" gorm:"type:int;not null;default:0;"` + DepartmentID int32 `json:"department_id" gorm:"type:int;not null;default:0;"` + RoleID int32 `json:"role_id" gorm:"type:int;not null;default:0;"` + Status int32 `json:"status" gorm:"type:int;not null;default:0;"` + ChangePasswordAt time.Time `json:"-" gorm:"type:timestamptz;not null;default:'0001-01-01 00:00:00+8';"` + CreatedAt time.Time `json:"created_at" gorm:"type:timestamptz;not null;default:'now()'"` + UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamptz;not null;default:'0001-01-01 00:00:00+8';"` + + Role *Role `json:"role" gorm:"ForeignKey:RoleID"` + Department *Department `json:"department" gorm:"ForeignKey:DepartmentID"` } func (User) TableName() string { diff --git a/internal/erpserver/model/view/system.go b/internal/erpserver/model/view/system.go index 7b8b11b..7f83cbb 100644 --- a/internal/erpserver/model/view/system.go +++ b/internal/erpserver/model/view/system.go @@ -1,5 +1,7 @@ package view +import "time" + type LayuiTree struct { ID string `json:"id"` Title string `json:"title"` @@ -17,3 +19,37 @@ type XmSelect struct { Name string `json:"name"` Value string `json:"value"` } + +type MenuTree struct { + ID int32 `json:"id"` + // 名称 + Name string `json:"name"` + // 显示名称 + DisplayName string `json:"display_name"` + // 菜单url + Url string `json:"url"` + // 菜单类型(node, menu, btn) + Type string `json:"type"` + // 上级id + ParentID int32 `json:"parent_id"` + // 树路径 + ParentPath string `json:"parent_path"` + // 菜单图标 + Avatar string `json:"avatar"` + // 菜单样式 + Style string `json:"style"` + // 是否可见 + Visible bool `json:"visible"` + // 是否列表 + IsList bool `json:"is_list"` + // 状态 + Status int32 `json:"status"` + // 排序 + Sort int32 `json:"sort"` + // 创建时间 + CreatedAt time.Time `json:"created_at"` + // 更新时间 + UpdatedAt time.Time `json:"updated_at"` + + Children []*MenuTree `json:"children"` +} diff --git a/internal/erpserver/repository/store.go b/internal/erpserver/repository/store.go new file mode 100644 index 0000000..9b0a216 --- /dev/null +++ b/internal/erpserver/repository/store.go @@ -0,0 +1,59 @@ +package repository + +import ( + "context" + "sync" + + "gorm.io/gorm" +) + +var ( + once sync.Once + // 全局变量,方便其它包直接调用已初始化好的 datastore 实例. + engine *datastore +) + +type Store interface { + DB(ctx context.Context) *gorm.DB + TX(ctx context.Context, fn func(ctx context.Context) error) error +} + +// transactionKey 用于在 context.Context 中存储事务上下文的键. +type transactionKey struct{} + +// datastore 是 Storer 的具体实现. +type datastore struct { + core *gorm.DB +} + +// 确保 datastore 实现了 Storer 接口. +var _ Store = (*datastore)(nil) + +// NewStore 创建一个 Storer 类型的实例. +func NewStore(db *gorm.DB) *datastore { + // 确保 engine 只被初始化一次 + once.Do(func() { + engine = &datastore{db} + }) + + return engine +} + +func (store *datastore) DB(ctx context.Context) *gorm.DB { + db := store.core + // 从上下文中提取事务实例 + if tx, ok := ctx.Value(transactionKey{}).(*gorm.DB); ok { + db = tx + } + + return db +} + +func (store *datastore) TX(ctx context.Context, fn func(ctx context.Context) error) error { + return store.core.WithContext(ctx).Transaction( + func(tx *gorm.DB) error { + ctx = context.WithValue(ctx, transactionKey{}, tx) + return fn(ctx) + }, + ) +} diff --git a/internal/erpserver/repository/system/audit_log.go b/internal/erpserver/repository/system/audit_log.go new file mode 100644 index 0000000..0ffd285 --- /dev/null +++ b/internal/erpserver/repository/system/audit_log.go @@ -0,0 +1,52 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type auditLogRepository struct { + store repository.Store +} + +var _ system.AuditLogRepository = (*auditLogRepository)(nil) + +func NewAuditLogRepository(store repository.Store) *auditLogRepository { + return &auditLogRepository{ + store: store, + } +} + +func (s *auditLogRepository) Create(ctx context.Context, obj *system.AuditLog) error { + return s.store.DB(ctx).Create(obj).Error +} + +func (s *auditLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) { + query := s.store.DB(ctx). + Model(&system.AuditLog{}). + Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchEmail != "" { + query = query.Where("email LIKE ?", "%"+q.SearchEmail+"%") + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var logs []*system.AuditLog + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&logs). + Error + if err != nil { + return nil, 0, err + } + return logs, count, nil +} diff --git a/internal/erpserver/repository/system/config.go b/internal/erpserver/repository/system/config.go new file mode 100644 index 0000000..b1cc9fd --- /dev/null +++ b/internal/erpserver/repository/system/config.go @@ -0,0 +1,51 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type configRepository struct { + store repository.Store +} + +var _ system.ConfigRepository = (*configRepository)(nil) + +func NewConfigRepository(store repository.Store) *configRepository { + return &configRepository{ + store: store, + } +} + +func (r *configRepository) Create(ctx context.Context, obj *system.Config) error { + return r.store.DB(ctx).Create(obj).Error +} + +func (r *configRepository) Update(ctx context.Context, obj *system.Config) error { + return r.store.DB(ctx).Save(obj).Error +} + +func (r *configRepository) Get(ctx context.Context, id int32) (*system.Config, error) { + var obj system.Config + err := r.store.DB(ctx).First(&obj, id).Error + if err != nil { + return nil, err + } + return &obj, nil +} + +func (r *configRepository) GetByKey(ctx context.Context, key string) (*system.Config, error) { + var obj system.Config + err := r.store.DB(ctx).Where("key = ?", key).First(&obj).Error + if err != nil { + return nil, err + } + return &obj, nil +} + +func (r *configRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.Config, int64, error) { + return nil, 0, nil +} diff --git a/internal/erpserver/repository/system/department.go b/internal/erpserver/repository/system/department.go new file mode 100644 index 0000000..10f7fab --- /dev/null +++ b/internal/erpserver/repository/system/department.go @@ -0,0 +1,100 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type departmentRepository struct { + store repository.Store +} + +var _ system.DepartmentRepository = (*departmentRepository)(nil) + +func NewDepartmentRepository(store repository.Store) *departmentRepository { + return &departmentRepository{ + store: store, + } +} + +func (r *departmentRepository) Create(ctx context.Context, obj *system.Department) error { + return r.store.DB(ctx).Create(obj).Error +} + +func (r *departmentRepository) Update(ctx context.Context, obj *system.Department) error { + return r.store.DB(ctx).Save(obj).Error +} + +func (r *departmentRepository) Get(ctx context.Context, id int32) (*system.Department, error) { + var obj system.Department + err := r.store.DB(ctx).First(&obj, id).Error + if err != nil { + return nil, err + } + return &obj, nil +} + +func (r *departmentRepository) All(ctx context.Context) ([]*system.Department, error) { + var departs []*system.Department + err := r.store.DB(ctx).Find(&departs).Error + if err != nil { + return nil, err + } + return departs, nil +} + +func (r *departmentRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.Department, int64, error) { + query := r.store.DB(ctx). + Model(&system.Department{}). + Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchID != 0 { + query = query.Where("id = ?", q.SearchID) + } + if q.SearchParentID != 0 && q.SearchParentID != 1 { + query = query.Where("parent_id = ?", q.SearchParentID) + } + if q.SearchName != "" { + query = query.Where("name LIKE ?", "%"+q.SearchName+"%") + } + if q.SearchStatus != 9999 { + query = query.Where("status = ?", q.SearchStatus) + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var departs []*system.Department + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&departs).Error + if err != nil { + return nil, 0, err + } + return departs, count, nil +} + +func (r *departmentRepository) RebuildParentPath(ctx context.Context) error { + query := `UPDATE sys_department AS tm +SET parent_path = (SELECT ',' || string_agg(cast(t.parent_id AS VARCHAR), ',') || ',' + FROM (WITH RECURSIVE temp (id, parent_id) AS (SELECT id, tm.parent_id + FROM sys_department + WHERE id = tm.id + UNION ALL + SELECT sys_department.id, sys_department.parent_id + FROM sys_department, + temp + WHERE sys_department.id = temp.parent_id) + SELECT id, parent_id + FROM temp + ORDER BY id) AS t) +WHERE tm.status = 0;` + return r.store.DB(ctx).Exec(query).Error +} diff --git a/internal/erpserver/repository/system/login_log.go b/internal/erpserver/repository/system/login_log.go new file mode 100644 index 0000000..cd47522 --- /dev/null +++ b/internal/erpserver/repository/system/login_log.go @@ -0,0 +1,78 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type loginLogRepository struct { + store repository.Store +} + +var _ system.LoginLogRepository = (*loginLogRepository)(nil) + +func NewLoginLogRepository(store repository.Store) *loginLogRepository { + return &loginLogRepository{ + store: store, + } +} + +func (s *loginLogRepository) Create(ctx context.Context, obj *system.LoginLog) error { + return s.store.DB(ctx).Create(obj).Error +} + +func (s *loginLogRepository) GetLatest(ctx context.Context, email string) (*system.LoginLog, error) { + var log system.LoginLog + err := s.store.DB(ctx). + Where("email = ?", email). + Order("id DESC"). + First(&log). + Error + if err != nil { + return nil, err + } + return &log, nil +} + +func (s *loginLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) { + query := s.store.DB(ctx). + Model(&system.LoginLog{}). + Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchEmail != "" { + query = query.Where("email LIKE ?", "%"+q.SearchEmail+"%") + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var logs []*system.LoginLog + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&logs). + Error + if err != nil { + return nil, 0, err + } + return logs, count, nil +} + +func (s *loginLogRepository) Count(ctx context.Context, email string) (int64, error) { + var count int64 + err := s.store.DB(ctx). + Model(&system.LoginLog{}). + Where("email = ?", email). + Count(&count). + Error + if err != nil { + return 0, err + } + return count, nil +} diff --git a/internal/erpserver/repository/system/menu.go b/internal/erpserver/repository/system/menu.go new file mode 100644 index 0000000..3076711 --- /dev/null +++ b/internal/erpserver/repository/system/menu.go @@ -0,0 +1,73 @@ +package system + +import ( + "context" + + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type menuRepository struct { + store repository.Store +} + +var _ system.MenuRepository = (*menuRepository)(nil) + +func NewMenuRepository(store repository.Store) *menuRepository { + return &menuRepository{ + store: store, + } +} + +func (r *menuRepository) Create(ctx context.Context, obj *system.Menu) error { + return r.store.DB(ctx).Create(obj).Error +} + +func (r *menuRepository) Update(ctx context.Context, obj *system.Menu) error { + return r.store.DB(ctx).Save(obj).Error +} + +func (r *menuRepository) Get(ctx context.Context, id int32) (*system.Menu, error) { + var menu system.Menu + err := r.store.DB(ctx).Where("id = ?", id).First(&menu).Error + if err != nil { + return nil, err + } + return &menu, nil +} + +func (r *menuRepository) GetByUrl(ctx context.Context, url string) (*system.Menu, error) { + var menu system.Menu + err := r.store.DB(ctx).Where("url = ?", url).First(&menu).Error + if err != nil { + return nil, err + } + return &menu, nil +} + +func (r *menuRepository) All(ctx context.Context) ([]*system.Menu, error) { + var menus []*system.Menu + err := r.store.DB(ctx).Find(&menus).Error + if err != nil { + return nil, err + } + return menus, nil +} + +func (r *menuRepository) RebuildParentPath(ctx context.Context) error { + query := `UPDATE sys_menu AS tm + SET parent_path = (SELECT ',' || string_agg(cast(t.parent_id AS VARCHAR), ',') || ',' + FROM (WITH RECURSIVE temp (id, parent_id) AS (SELECT id, tm.parent_id + FROM sys_menu + WHERE id = tm.id + UNION ALL + SELECT sys_menu.id, sys_menu.parent_id + FROM sys_menu, + temp + WHERE sys_menu.id = temp.parent_id) + SELECT id, parent_id + FROM temp + ORDER BY id) AS t) + WHERE tm.status = 0;` + return r.store.DB(ctx).Exec(query).Error +} diff --git a/internal/erpserver/repository/system/role.go b/internal/erpserver/repository/system/role.go new file mode 100644 index 0000000..5215914 --- /dev/null +++ b/internal/erpserver/repository/system/role.go @@ -0,0 +1,100 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type roleRepository struct { + store repository.Store +} + +var _ system.RoleRepository = (*roleRepository)(nil) + +func NewRoleRepository(store repository.Store) *roleRepository { + return &roleRepository{ + store: store, + } +} + +func (r *roleRepository) Create(ctx context.Context, obj *system.Role) error { + return r.store.DB(ctx).Create(obj).Error +} + +func (r *roleRepository) Update(ctx context.Context, obj *system.Role) error { + return r.store.DB(ctx).Save(obj).Error +} + +func (r *roleRepository) Get(ctx context.Context, id int32) (*system.Role, error) { + var role system.Role + err := r.store.DB(ctx).Where("id = ?", id).First(&role).Error + if err != nil { + return nil, err + } + return &role, nil +} + +func (r *roleRepository) All(ctx context.Context) ([]*system.Role, error) { + var roles []*system.Role + err := r.store.DB(ctx).Find(&roles).Error + if err != nil { + return nil, err + } + return roles, nil +} + +func (r *roleRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.Role, int64, error) { + query := r.store.DB(ctx). + Model(&system.Role{}). + Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchID != 0 { + query = query.Where("id = ?", q.SearchID) + } + if q.SearchParentID != 0 && q.SearchParentID != 1 { + query = query.Where("parent_id = ?", q.SearchParentID) + } + if q.SearchName != "" { + query = query.Where("name LIKE ?", "%"+q.SearchName+"%") + } + if q.SearchStatus != 9999 { + query = query.Where("status = ?", q.SearchStatus) + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var departs []*system.Role + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&departs).Error + if err != nil { + return nil, 0, err + } + return departs, count, nil +} + +func (r *roleRepository) RebuildParentPath(ctx context.Context) error { + query := `UPDATE sys_role AS tm + SET parent_path = (SELECT ',' || string_agg(cast(t.parent_id AS VARCHAR), ',') || ',' + FROM (WITH RECURSIVE temp (id, parent_id) AS (SELECT id, tm.parent_id + FROM sys_role + WHERE id = tm.id + UNION ALL + SELECT sys_role.id, sys_role.parent_id + FROM sys_role, + temp + WHERE sys_role.id = temp.parent_id) + SELECT id, parent_id + FROM temp + ORDER BY id) AS t) + WHERE tm.status = 0;` + return r.store.DB(ctx).Exec(query).Error +} diff --git a/internal/erpserver/repository/system/role_menu.go b/internal/erpserver/repository/system/role_menu.go new file mode 100644 index 0000000..21e6121 --- /dev/null +++ b/internal/erpserver/repository/system/role_menu.go @@ -0,0 +1,37 @@ +package system + +import ( + "context" + + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type roleMenuRepository struct { + store repository.Store +} + +var _ system.RoleMenuRepository = (*roleMenuRepository)(nil) + +func NewRoleMenuRepository(store repository.Store) *roleMenuRepository { + return &roleMenuRepository{ + store: store, + } +} + +func (r *roleMenuRepository) Create(ctx context.Context, obj []*system.RoleMenu) error { + return r.store.DB(ctx).Create(obj).Error +} + +func (r *roleMenuRepository) DeleteByRoleID(ctx context.Context, roleID int32) error { + return r.store.DB(ctx).Where("role_id = ?", roleID).Delete(&system.RoleMenu{}).Error +} + +func (r *roleMenuRepository) ListByRoleID(ctx context.Context, roleID int32) ([]*system.RoleMenu, error) { + var roleMenus []*system.RoleMenu + err := r.store.DB(ctx).Where("role_id = ?", roleID).Find(&roleMenus).Error + if err != nil { + return nil, err + } + return roleMenus, nil +} diff --git a/internal/erpserver/repository/system/user.go b/internal/erpserver/repository/system/user.go new file mode 100644 index 0000000..f095842 --- /dev/null +++ b/internal/erpserver/repository/system/user.go @@ -0,0 +1,93 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/repository" +) + +type userRepository struct { + store repository.Store +} + +var _ system.UserRepository = (*userRepository)(nil) + +func NewUserRepository(store repository.Store) *userRepository { + return &userRepository{ + store: store, + } +} + +func (s *userRepository) Create(ctx context.Context, obj *system.User) error { + return s.store.DB(ctx).Create(obj).Error +} + +func (s *userRepository) Update(ctx context.Context, obj *system.User) error { + return s.store.DB(ctx).Save(obj).Error +} + +func (s *userRepository) Get(ctx context.Context, id int32) (*system.User, error) { + var user system.User + err := s.store.DB(ctx).Where("id = ?", id).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *userRepository) GetByEmail(ctx context.Context, email string) (*system.User, error) { + var user system.User + err := s.store.DB(ctx).Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *userRepository) All(ctx context.Context) ([]*system.User, error) { + var users []*system.User + err := s.store.DB(ctx).Find(&users).Error + if err != nil { + return nil, err + } + return users, nil +} + +func (s *userRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) { + query := s.store.DB(ctx). + Model(&system.User{}). + Preload("Role"). + Preload("Department"). + Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchID != 0 { + query = query.Where("id = ?", q.SearchID) + } + if q.SearchName != "" { + query = query.Where("username LIKE ?", "%"+q.SearchName+"%") + } + if q.SearchEmail != "" { + query = query.Where("email LIKE ?", "%"+q.SearchEmail+"%") + } + if q.SearchStatus != 9999 { + query = query.Where("status = ?", q.SearchStatus) + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var users []*system.User + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&users).Error + if err != nil { + return nil, 0, err + } + return users, count, nil +} diff --git a/internal/erpserver/service/v1/common/captcha.go b/internal/erpserver/service/v1/common/captcha.go new file mode 100644 index 0000000..0a125e6 --- /dev/null +++ b/internal/erpserver/service/v1/common/captcha.go @@ -0,0 +1,30 @@ +package common + +import ( + v1 "management/internal/erpserver/service/v1" + + "github.com/mojocn/base64Captcha" +) + +type captchaService struct{} + +var _ v1.CaptchaService = (*captchaService)(nil) + +func NewCaptchaService() *captchaService { + return &captchaService{} +} + +var captchaStore base64Captcha.Store = base64Captcha.DefaultMemStore + +func (b *captchaService) Generate(height int, width int, length int, maxSkew float64, dotCount int) (id, b64s, answer string, err error) { + driver := base64Captcha.NewDriverDigit(height, width, length, maxSkew, dotCount) + // driver := base64Captcha.NewDriverString(config.File.Captcha.ImgHeight, + // config.File.Captcha.ImgWidth, + // 6, 1, keyLong, source, nil, nil, nil) + cp := base64Captcha.NewCaptcha(driver, captchaStore) + return cp.Generate() +} + +func (b *captchaService) Verify(id, answer string, clear bool) bool { + return captchaStore.Verify(id, answer, clear) +} diff --git a/internal/erpserver/service/v1/service.go b/internal/erpserver/service/v1/service.go new file mode 100644 index 0000000..4f57fee --- /dev/null +++ b/internal/erpserver/service/v1/service.go @@ -0,0 +1,102 @@ +package v1 + +import ( + "context" + "time" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/form" + "management/internal/erpserver/model/system" + "management/internal/erpserver/model/view" +) + +type CaptchaService interface { + Generate(height int, width int, length int, maxSkew float64, dotCount int) (id, b64s, answer string, err error) + Verify(id, answer string, clear bool) bool +} + +type ConfigService interface { + Create(ctx context.Context, obj *system.Config) error + Update(ctx context.Context, obj *system.Config) error + Get(ctx context.Context, id int32) (*system.Config, error) + List(ctx context.Context, q dto.SearchDto) ([]*system.Config, int64, error) + Pear(ctx context.Context) (*dto.PearConfig, error) +} + +type UserService interface { + Create(ctx context.Context, req *form.User) error + Update(ctx context.Context, req *form.User) error + All(ctx context.Context) ([]*system.User, error) + List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) + Get(ctx context.Context, id int32) (*system.User, error) + + XmSelect(ctx context.Context) ([]*view.XmSelect, error) + Login(ctx context.Context, req *form.Login) error +} + +type LoginLogService interface { + Create(ctx context.Context, req *system.LoginLog) error + List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) + + LoginLatestTime(ctx context.Context, email string) time.Time + LoginCount(ctx context.Context, email string) int64 +} + +type AuditLogService interface { + Create(ctx context.Context, req *system.AuditLog) error + List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) +} + +type RoleService interface { + Create(ctx context.Context, req *form.Role) error + Update(ctx context.Context, req *form.Role) error + Get(ctx context.Context, id int32) (*system.Role, error) + All(ctx context.Context) ([]*system.Role, error) + List(ctx context.Context, q dto.SearchDto) ([]*system.Role, int64, error) + RefreshCache(ctx context.Context) error + RebuildParentPath(ctx context.Context) error + + Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) + XmSelectTree(ctx context.Context, id int32) ([]*view.XmSelectTree, error) +} + +type DepartmentService interface { + Create(ctx context.Context, req *form.Department) error + Update(ctx context.Context, req *form.Department) error + Get(ctx context.Context, id int32) (*system.Department, error) + All(ctx context.Context) ([]*system.Department, error) + List(ctx context.Context, q dto.SearchDto) ([]*system.Department, int64, error) + RefreshCache(ctx context.Context) error + RebuildParentPath(ctx context.Context) error + + Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) + XmSelectTree(ctx context.Context, id int32) ([]*view.XmSelectTree, error) +} + +type MenuService interface { + Create(ctx context.Context, req *system.Menu) error + Update(ctx context.Context, req *system.Menu) error + Get(ctx context.Context, id int32) (*system.Menu, error) + GetByUrl(ctx context.Context, url string) (*system.Menu, error) + All(ctx context.Context) ([]*system.Menu, error) + ListMenuTree(ctx context.Context) ([]*view.MenuTree, error) + ListByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error) + SetListByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error) + ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) + SetListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) + OwerMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error) + SetOwerMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error) + MenuViewData(ctx context.Context, roleID int32) ([]*dto.SetMenuDto, error) + SetRoleMenu(ctx context.Context, roleID int32, rms []*system.RoleMenu) error + RefreshCache(ctx context.Context) error + RebuildParentPath(ctx context.Context) error + + Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) + XmSelectTree(ctx context.Context, id int32) ([]*view.XmSelectTree, error) +} + +type RoleMenuService interface { + Create(ctx context.Context, req []*system.RoleMenu) error + DeleteByRoleID(ctx context.Context, roleID int32) error + ListByRoleID(ctx context.Context, roleID int32) ([]*system.RoleMenu, error) +} diff --git a/internal/erpserver/service/v1/system/audit_log.go b/internal/erpserver/service/v1/system/audit_log.go new file mode 100644 index 0000000..705cad3 --- /dev/null +++ b/internal/erpserver/service/v1/system/audit_log.go @@ -0,0 +1,29 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" +) + +type auditLogService struct { + repo system.AuditLogRepository +} + +var _ v1.AuditLogService = (*auditLogService)(nil) + +func NewAuditLogService(repo system.AuditLogRepository) *auditLogService { + return &auditLogService{ + repo: repo, + } +} + +func (b *auditLogService) Create(ctx context.Context, req *system.AuditLog) error { + return b.repo.Create(ctx, req) +} + +func (b *auditLogService) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) { + return b.repo.List(ctx, q) +} diff --git a/internal/erpserver/service/v1/system/config.go b/internal/erpserver/service/v1/system/config.go new file mode 100644 index 0000000..805be03 --- /dev/null +++ b/internal/erpserver/service/v1/system/config.go @@ -0,0 +1,69 @@ +package system + +import ( + "context" + "encoding/json" + "time" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" + "management/internal/pkg/know" + "management/internal/pkg/know/pearadmin" + "management/internal/pkg/redis" +) + +type configService struct { + repo system.ConfigRepository + redis redis.RedisCache +} + +var _ v1.ConfigService = (*configService)(nil) + +func NewConfigService(repo system.ConfigRepository, redis redis.RedisCache) *configService { + return &configService{ + repo: repo, + redis: redis, + } +} + +func (s *configService) Create(ctx context.Context, obj *system.Config) error { + return s.repo.Create(ctx, obj) +} + +func (s *configService) Update(ctx context.Context, obj *system.Config) error { + return s.repo.Update(ctx, obj) +} + +func (s *configService) Get(ctx context.Context, id int32) (*system.Config, error) { + return s.repo.Get(ctx, id) +} + +func (s *configService) List(ctx context.Context, q dto.SearchDto) ([]*system.Config, int64, error) { + return s.repo.List(ctx, q) +} + +func (s *configService) Pear(ctx context.Context) (*dto.PearConfig, error) { + // 判断redis是否存储 + key := know.GetManageKey(ctx, know.PearAdmin) + bs, err := s.redis.GetBytes(ctx, key) + if err == nil { + var res *dto.PearConfig + if err := json.Unmarshal(bs, &res); err == nil { + return res, nil + } + } + + conf, err := s.repo.GetByKey(ctx, pearadmin.PearKey) + if err != nil { + return nil, err + } + + var pear dto.PearConfig + if err := json.Unmarshal(conf.Value, &pear); err != nil { + return nil, err + } + + _ = s.redis.Set(ctx, key, conf.Value, time.Hour*6) + return &pear, nil +} diff --git a/internal/erpserver/service/v1/system/department.go b/internal/erpserver/service/v1/system/department.go new file mode 100644 index 0000000..cdd5e2e --- /dev/null +++ b/internal/erpserver/service/v1/system/department.go @@ -0,0 +1,207 @@ +package system + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + "management/internal/db/model/dto" + db "management/internal/db/sqlc" + "management/internal/erpserver/model/form" + "management/internal/erpserver/model/system" + "management/internal/erpserver/model/view" + v1 "management/internal/erpserver/service/v1" + "management/internal/pkg/convertor" + "management/internal/pkg/know" + "management/internal/pkg/redis" +) + +type departmentService struct { + repo system.DepartmentRepository +} + +var _ v1.DepartmentService = (*departmentService)(nil) + +func NewDepartmentService(repo system.DepartmentRepository) *departmentService { + return &departmentService{ + repo: repo, + } +} + +func (s *departmentService) Create(ctx context.Context, req *form.Department) error { + parent := &system.Department{ + ID: 0, + ParentID: 0, + ParentPath: ",0,", + } + if *req.ParentID > 0 { + var err error + parent, err = s.Get(ctx, *req.ParentID) + if err != nil { + return errors.New("父级节点错误") + } + } + + var order int32 = 6666 + if *req.Sort > 0 { + order = *req.Sort + } + + arg := &system.Department{ + Name: req.Name, + ParentID: parent.ID, + ParentPath: convertor.HandleParentPath(fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID)), + Status: *req.Status, + Sort: order, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + err := s.repo.Create(ctx, arg) + if err != nil { + if db.IsUniqueViolation(err) { + return errors.New("部门已存在") + } + return err + } + return nil +} + +func (s *departmentService) Update(ctx context.Context, req *form.Department) error { + parent := &system.Department{ + ID: 0, + ParentID: 0, + ParentPath: ",0,", + } + if *req.ParentID > 0 { + var err error + parent, err = s.Get(ctx, *req.ParentID) + if err != nil { + return errors.New("父级节点错误") + } + } + + depart, err := s.Get(ctx, *req.ID) + if err != nil { + return err + } + + var order int32 = 6666 + if *req.Sort > 0 { + order = *req.Sort + } + + depart.Name = req.Name + depart.ParentID = parent.ID + depart.ParentPath = convertor.HandleParentPath(fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID)) + depart.Status = *req.Status + depart.Sort = order + depart.UpdatedAt = time.Now() + return s.repo.Update(ctx, depart) +} + +func (s *departmentService) Get(ctx context.Context, id int32) (*system.Department, error) { + return s.repo.Get(ctx, id) +} + +func (s *departmentService) All(ctx context.Context) ([]*system.Department, error) { + key := know.GetManageKey(ctx, know.AllDepartments) + bs, err := redis.GetBytes(ctx, key) + if err == nil { + var res []*system.Department + if err := json.Unmarshal(bs, &res); err == nil { + return res, nil + } + } + + res, err := s.repo.All(ctx) + if err != nil { + return nil, err + } + + bs, err = json.Marshal(res) + if err != nil { + return nil, err + } + + _ = redis.Set(ctx, key, bs, time.Hour*6) + return res, nil +} + +func (s *departmentService) List(ctx context.Context, q dto.SearchDto) ([]*system.Department, int64, error) { + return s.repo.List(ctx, q) +} + +func (s *departmentService) RefreshCache(ctx context.Context) error { + key := know.GetManageKey(ctx, know.AllDepartments) + res, err := s.All(ctx) + if err != nil { + return err + } + + b, err := json.Marshal(res) + if err != nil { + return err + } + + _ = redis.Set(ctx, key, b, time.Hour*6) + return nil +} + +func (s *departmentService) RebuildParentPath(ctx context.Context) error { + return s.repo.RebuildParentPath(ctx) +} + +func (s *departmentService) Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return s.toTree(id, all), nil +} + +func (s *departmentService) XmSelectTree(ctx context.Context, id int32) ([]*view.XmSelectTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return s.toXmSelectTree(id, all), nil +} + +func (s *departmentService) toTree(parentId int32, data []*system.Department) []*view.LayuiTree { + var res []*view.LayuiTree + for _, v := range data { + if v.ParentID == parentId { + item := view.LayuiTree{} + item.ID = strconv.FormatInt(int64(v.ID), 10) + item.Title = v.Name + item.Children = s.toTree(v.ID, data) + if v.ParentID == 0 { + item.Spread = true + } + res = append(res, &item) + } + } + + return res +} + +func (s *departmentService) toXmSelectTree(parentId int32, data []*system.Department) []*view.XmSelectTree { + var res []*view.XmSelectTree + for _, v := range data { + if v.ParentID == parentId { + item := view.XmSelectTree{ + Name: v.Name, + Value: strconv.FormatInt(int64(v.ID), 10), + Children: s.toXmSelectTree(v.ID, data), + } + res = append(res, &item) + } + } + + return res +} diff --git a/internal/erpserver/service/v1/system/login_log.go b/internal/erpserver/service/v1/system/login_log.go new file mode 100644 index 0000000..2c53bb3 --- /dev/null +++ b/internal/erpserver/service/v1/system/login_log.go @@ -0,0 +1,46 @@ +package system + +import ( + "context" + "time" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" +) + +type loginLogService struct { + repo system.LoginLogRepository +} + +var _ v1.LoginLogService = (*loginLogService)(nil) + +func NewLoginLogService(repo system.LoginLogRepository) *loginLogService { + return &loginLogService{ + repo: repo, + } +} + +func (s *loginLogService) Create(ctx context.Context, req *system.LoginLog) error { + return s.repo.Create(ctx, req) +} + +func (s *loginLogService) List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) { + return s.repo.List(ctx, q) +} + +func (s *loginLogService) LoginLatestTime(ctx context.Context, email string) time.Time { + log, err := s.repo.GetLatest(ctx, email) + if err != nil { + return time.Time{} + } + return log.CreatedAt +} + +func (s *loginLogService) LoginCount(ctx context.Context, email string) int64 { + count, err := s.repo.Count(ctx, email) + if err != nil { + return 0 + } + return count +} diff --git a/internal/erpserver/service/v1/system/menu.go b/internal/erpserver/service/v1/system/menu.go new file mode 100644 index 0000000..81d78ed --- /dev/null +++ b/internal/erpserver/service/v1/system/menu.go @@ -0,0 +1,457 @@ +package system + +import ( + "context" + "encoding/json" + "strconv" + "strings" + "time" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + "management/internal/erpserver/model/view" + "management/internal/erpserver/repository" + v1 "management/internal/erpserver/service/v1" + "management/internal/pkg/know" + "management/internal/pkg/redis" +) + +type menuService struct { + redis redis.RedisCache + store repository.Store + repo system.MenuRepository + rolesvc v1.RoleService + rolemenusvc v1.RoleMenuService +} + +var _ v1.MenuService = (*menuService)(nil) + +func NewMenuService(redis redis.RedisCache, store repository.Store, repo system.MenuRepository, rolesvc v1.RoleService, rolemenusvc v1.RoleMenuService) *menuService { + return &menuService{ + redis: redis, + store: store, + repo: repo, + rolesvc: rolesvc, + rolemenusvc: rolemenusvc, + } +} + +func (s *menuService) Create(ctx context.Context, req *system.Menu) error { + return s.repo.Create(ctx, req) +} + +func (s *menuService) Update(ctx context.Context, req *system.Menu) error { + return s.repo.Update(ctx, req) +} + +func (s *menuService) Get(ctx context.Context, id int32) (*system.Menu, error) { + return s.repo.Get(ctx, id) +} + +func (s *menuService) GetByUrl(ctx context.Context, url string) (*system.Menu, error) { + return s.repo.GetByUrl(ctx, url) +} + +func (s *menuService) All(ctx context.Context) ([]*system.Menu, error) { + key := know.GetManageKey(ctx, know.AllMenus) + b, err := s.redis.GetBytes(ctx, key) + if err == nil { + var res []*system.Menu + if err := json.Unmarshal(b, &res); err == nil { + return res, nil + } + } + + res, err := s.repo.All(ctx) + if err != nil { + return nil, err + } + + b, err = json.Marshal(res) + if err != nil { + return nil, err + } + + _ = s.redis.Set(ctx, key, b, time.Hour*6) + return res, nil +} + +func (s *menuService) ListMenuTree(ctx context.Context) ([]*view.MenuTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return toListTree(0, all), nil +} + +func (s *menuService) ListByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error) { + key := know.GetManageKey(ctx, know.OwnerMenus, roleID) + bs, err := s.redis.GetBytes(ctx, key) + if err == nil { + var res []*dto.OwnerMenuDto + if err := json.Unmarshal(bs, &res); err == nil { + return res, nil + } + } + + return s.SetListByRoleID(ctx, roleID) +} + +func (s *menuService) SetListByRoleID(ctx context.Context, roleID int32) ([]*dto.OwnerMenuDto, error) { + key := know.GetManageKey(ctx, know.OwnerMenus, roleID) + menus, err := s.listByRoleID(ctx, roleID) + if err != nil { + return nil, err + } + + res := toOwnerMenuDto(menus) + b, err := json.Marshal(res) + if err != nil { + return nil, err + } + + _ = s.redis.Set(ctx, key, b, time.Hour*6) + return res, nil +} + +func (s *menuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) { + key := know.GetManageKey(ctx, know.OwnerMenusMap, roleID) + bs, err := s.redis.GetBytes(ctx, key) + if err == nil { + var res map[string]*dto.OwnerMenuDto + if err := json.Unmarshal(bs, &res); err == nil { + return res, nil + } + } + + return s.SetListByRoleIDToMap(ctx, roleID) +} + +func (s *menuService) SetListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) { + key := know.GetManageKey(ctx, know.OwnerMenusMap, roleID) + menus, err := s.listByRoleID(ctx, roleID) + if err != nil { + return nil, err + } + + res := toOwnerMenuDtoMap(menus) + b, err := json.Marshal(res) + if err != nil { + return nil, err + } + + _ = s.redis.Set(ctx, key, b, time.Hour*6) + return res, nil +} + +func (s *menuService) OwerMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error) { + key := know.GetManageKey(ctx, know.AdminMenus, roleID) + bs, err := s.redis.GetBytes(ctx, key) + if err == nil { + var res []*dto.MenuUIDto + if err := json.Unmarshal(bs, &res); err == nil { + return res, nil + } + } + + return s.SetOwerMenus(ctx, roleID) +} + +func (s *menuService) SetOwerMenus(ctx context.Context, roleID int32) ([]*dto.MenuUIDto, error) { + key := know.GetManageKey(ctx, know.AdminMenus, roleID) + + menus, err := s.listByRoleID(ctx, roleID) + if err != nil { + return nil, err + } + + res := convertToUITree(menus, 0) + b, err := json.Marshal(res) + if err != nil { + return nil, err + } + + _ = s.redis.Set(ctx, key, b, time.Hour*6) + return res, nil +} + +func (s *menuService) MenuViewData(ctx context.Context, roleID int32) ([]*dto.SetMenuDto, error) { + // 获取该用户已经有的权限 + hs, err := s.listByRoleID(ctx, roleID) + if err != nil { + return nil, err + } + + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return toSetMenuTree(all, hs, 0), nil +} + +func (s *menuService) SetRoleMenu(ctx context.Context, roleID int32, rms []*system.RoleMenu) error { + // 开启事务 + return s.store.TX(ctx, func(ctx context.Context) error { + // 先删除该角色的所有权限 + err := s.rolemenusvc.DeleteByRoleID(ctx, roleID) + if err != nil { + return err + } + + // 再添加该角色的所有权限 + return s.rolemenusvc.Create(ctx, rms) + }) +} + +func (s *menuService) RefreshCache(ctx context.Context) error { + key := know.GetManageKey(ctx, know.AllMenus) + res, err := s.All(ctx) + if err != nil { + return err + } + + b, err := json.Marshal(res) + if err != nil { + return err + } + + _ = redis.Set(ctx, key, b, time.Hour*6) + return nil +} + +func (s *menuService) RebuildParentPath(ctx context.Context) error { + return s.repo.RebuildParentPath(ctx) +} + +func (s *menuService) Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return s.toTree(id, all), nil +} + +func (s *menuService) XmSelectTree(ctx context.Context, id int32) ([]*view.XmSelectTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return s.toXmSelectTree(id, all), nil +} + +// listByRoleID 获取该角色所拥有的菜单集合 +func (s *menuService) listByRoleID(ctx context.Context, roleID int32) ([]*system.Menu, error) { + role, err := s.rolesvc.Get(ctx, roleID) + if err != nil { + return nil, err + } + + res, err := s.repo.All(ctx) + if err != nil { + return nil, err + } + + if role.Vip { + return res, nil + } + + // 通过角色获取所拥有的权限 + roleMenus, err := s.rolemenusvc.ListByRoleID(ctx, roleID) + if err != nil { + return nil, err + } + + return findMenu(roleMenus, res), nil +} + +func (s *menuService) toTree(parentId int32, data []*system.Menu) []*view.LayuiTree { + var res []*view.LayuiTree + for _, v := range data { + if v.ParentID == parentId { + item := view.LayuiTree{} + item.ID = strconv.FormatInt(int64(v.ID), 10) + item.Title = v.Name + item.Children = s.toTree(v.ID, data) + if v.ParentID == 0 { + item.Spread = true + } + res = append(res, &item) + } + } + + return res +} + +func (s *menuService) toXmSelectTree(parentId int32, data []*system.Menu) []*view.XmSelectTree { + var res []*view.XmSelectTree + for _, v := range data { + if v.ParentID == parentId { + item := view.XmSelectTree{ + Name: v.Name, + Value: strconv.FormatInt(int64(v.ID), 10), + Children: s.toXmSelectTree(v.ID, data), + } + res = append(res, &item) + } + } + + return res +} + +func findMenu(rms []*system.RoleMenu, ms []*system.Menu) []*system.Menu { + var res []*system.Menu + for _, rm := range rms { + for _, m := range ms { + if rm.MenuID == m.ID { + res = append(res, m) + } + } + } + return res +} + +func toOwnerMenuDto(ms []*system.Menu) []*dto.OwnerMenuDto { + var res []*dto.OwnerMenuDto + for _, m := range ms { + res = append(res, &dto.OwnerMenuDto{ + ID: m.ID, + DisplayName: m.DisplayName, + Url: m.Url, + ParentID: m.ParentID, + Avatar: m.Avatar, + Style: m.Style, + IsList: m.IsList, + }) + } + return res +} + +func toOwnerMenuDtoMap(ms []*system.Menu) map[string]*dto.OwnerMenuDto { + res := make(map[string]*dto.OwnerMenuDto) + for _, m := range ms { + res[m.Url] = &dto.OwnerMenuDto{ + ID: m.ID, + DisplayName: m.DisplayName, + Url: m.Url, + ParentID: m.ParentID, + Avatar: m.Avatar, + Style: m.Style, + IsList: m.IsList, + } + } + return res +} + +func convertToUITree(data []*system.Menu, parentID int32) []*dto.MenuUIDto { + var root []*dto.MenuUIDto + for _, item := range data { + if item.ParentID == parentID { + if item.IsList { + temp := &dto.MenuUIDto{ + ID: strings.ToLower(item.Url), + Title: item.DisplayName, + Icon: item.Avatar, + Type: 1, + OpenType: "_iframe", + // OpenType: "_component", + Href: item.Url, + } + root = append(root, temp) + } else { + temp := &dto.MenuUIDto{ + ID: strconv.Itoa(int(item.ID)), + Title: item.DisplayName, + Icon: item.Avatar, + Type: 0, + } + temp.Children = convertToUITree(data, item.ID) + root = append(root, temp) + } + } + } + return root +} + +func toSetMenuTree(data []*system.Menu, ids []*system.Menu, parentID int32) []*dto.SetMenuDto { + var res []*dto.SetMenuDto + for _, v := range data { + if v.ParentID == parentID { + isSelect := hasValueInArray(ids, v.ID) + if v.IsList { + item := dto.SetMenuDto{ + ID: v.ID, + Name: v.DisplayName, + Link: v.Type, + IsList: v.IsList, + IsSelect: isSelect, + } + item.Items = []*dto.SetMenuDto{ + { + ID: v.ID, + Name: "列表", + Link: "btn", + IsList: false, + IsSelect: isSelect, + Items: toSetMenuTree(data, ids, v.ID), + }, + } + item.Items = append(item.Items, toSetMenuTree(data, ids, v.ID)...) + res = append(res, &item) + } else { + item := dto.SetMenuDto{ + ID: v.ID, + Name: v.DisplayName, + Link: v.Type, + IsList: v.IsList, + IsSelect: isSelect, + Items: toSetMenuTree(data, ids, v.ID), + } + res = append(res, &item) + } + } + } + + return res +} + +func hasValueInArray(data []*system.Menu, id int32) bool { + for _, v := range data { + if v.ID == id { + return true + } + } + + return false +} + +func toListTree(parentId int32, data []*system.Menu) []*view.MenuTree { + var res []*view.MenuTree + for _, v := range data { + if v.ParentID == parentId { + item := view.MenuTree{} + item.ID = v.ID + item.Name = v.Name + item.DisplayName = v.DisplayName + item.Url = v.Url + item.Type = v.Type + item.ParentID = v.ParentID + item.ParentPath = v.ParentPath + item.Avatar = v.Avatar + item.Style = v.Style + item.Visible = v.Visible + item.IsList = v.IsList + item.Status = v.Status + item.Sort = v.Sort + item.CreatedAt = v.CreatedAt + item.UpdatedAt = v.UpdatedAt + item.Children = toListTree(v.ID, data) + res = append(res, &item) + } + } + + return res +} diff --git a/internal/erpserver/service/v1/system/role.go b/internal/erpserver/service/v1/system/role.go new file mode 100644 index 0000000..ba7e897 --- /dev/null +++ b/internal/erpserver/service/v1/system/role.go @@ -0,0 +1,210 @@ +package system + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + "management/internal/db/model/dto" + db "management/internal/db/sqlc" + "management/internal/erpserver/model/form" + "management/internal/erpserver/model/system" + "management/internal/erpserver/model/view" + v1 "management/internal/erpserver/service/v1" + "management/internal/pkg/convertor" + "management/internal/pkg/know" + "management/internal/pkg/redis" +) + +type roleService struct { + repo system.RoleRepository +} + +var _ v1.RoleService = (*roleService)(nil) + +func NewRoleService(repo system.RoleRepository) *roleService { + return &roleService{ + repo: repo, + } +} + +func (s *roleService) Create(ctx context.Context, req *form.Role) error { + parent := &system.Role{ + ID: 0, + ParentID: 0, + ParentPath: ",0,", + } + if *req.ParentID > 0 { + var err error + parent, err = s.repo.Get(ctx, *req.ParentID) + if err != nil { + return errors.New("父级节点错误") + } + } + + var order int32 = 6666 + if *req.Sort > 0 { + order = *req.Sort + } + + arg := &system.Role{ + Name: req.Name, + DisplayName: req.DisplayName, + ParentID: parent.ID, + ParentPath: convertor.HandleParentPath(fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID)), + Vip: false, + Status: *req.Status, + Sort: order, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + err := s.repo.Create(ctx, arg) + if err != nil { + if db.IsUniqueViolation(err) { + return errors.New("角色名称已存在") + } + return err + } + return nil +} + +func (s *roleService) Update(ctx context.Context, req *form.Role) error { + parent := &system.Role{ + ID: 0, + ParentID: 0, + ParentPath: ",0,", + } + if *req.ParentID > 0 { + var err error + parent, err = s.repo.Get(ctx, *req.ParentID) + if err != nil { + return errors.New("父级节点错误") + } + } + + role, err := s.repo.Get(ctx, *req.ID) + if err != nil { + return err + } + + var order int32 = 6666 + if *req.Sort > 0 { + order = *req.Sort + } + + role.DisplayName = req.DisplayName + role.Status = *req.Status + role.ParentID = parent.ID + role.ParentPath = convertor.HandleParentPath(fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID)) + role.Sort = order + role.UpdatedAt = time.Now() + err = s.repo.Update(ctx, role) + return err +} + +func (s *roleService) Get(ctx context.Context, id int32) (*system.Role, error) { + return s.repo.Get(ctx, id) +} + +func (s *roleService) All(ctx context.Context) ([]*system.Role, error) { + key := know.GetManageKey(ctx, know.AllRoles) + bs, err := redis.GetBytes(ctx, key) + if err == nil { + var res []*system.Role + if err := json.Unmarshal(bs, &res); err == nil { + return res, nil + } + } + + res, err := s.repo.All(ctx) + if err != nil { + return nil, err + } + + bs, err = json.Marshal(res) + if err != nil { + return nil, err + } + + _ = redis.Set(ctx, key, bs, time.Hour*6) + return res, nil +} + +func (s *roleService) List(ctx context.Context, q dto.SearchDto) ([]*system.Role, int64, error) { + return s.repo.List(ctx, q) +} + +func (s *roleService) RefreshCache(ctx context.Context) error { + key := know.GetManageKey(ctx, know.AllRoles) + res, err := s.All(ctx) + if err != nil { + return err + } + + b, err := json.Marshal(res) + if err != nil { + return err + } + + _ = redis.Set(ctx, key, b, time.Hour*6) + return nil +} + +func (s *roleService) RebuildParentPath(ctx context.Context) error { + return s.repo.RebuildParentPath(ctx) +} + +func (s *roleService) Tree(ctx context.Context, id int32) ([]*view.LayuiTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return s.toTree(id, all), nil +} + +func (s *roleService) XmSelectTree(ctx context.Context, id int32) ([]*view.XmSelectTree, error) { + all, err := s.All(ctx) + if err != nil { + return nil, err + } + + return s.toXmSelectTree(id, all), nil +} + +func (s *roleService) toTree(parentId int32, data []*system.Role) []*view.LayuiTree { + var res []*view.LayuiTree + for _, v := range data { + if v.ParentID == parentId { + item := view.LayuiTree{} + item.ID = strconv.FormatInt(int64(v.ID), 10) + item.Title = v.DisplayName + item.Children = s.toTree(v.ID, data) + if v.ParentID == 0 { + item.Spread = true + } + res = append(res, &item) + } + } + + return res +} + +func (s *roleService) toXmSelectTree(parentId int32, data []*system.Role) []*view.XmSelectTree { + var res []*view.XmSelectTree + for _, v := range data { + if v.ParentID == parentId { + item := view.XmSelectTree{ + Name: v.DisplayName, + Value: strconv.FormatInt(int64(v.ID), 10), + Children: s.toXmSelectTree(v.ID, data), + } + res = append(res, &item) + } + } + + return res +} diff --git a/internal/erpserver/service/v1/system/role_menu.go b/internal/erpserver/service/v1/system/role_menu.go new file mode 100644 index 0000000..cf1b800 --- /dev/null +++ b/internal/erpserver/service/v1/system/role_menu.go @@ -0,0 +1,32 @@ +package system + +import ( + "context" + + "management/internal/erpserver/model/system" + v1 "management/internal/erpserver/service/v1" +) + +type roleMenuService struct { + repo system.RoleMenuRepository +} + +var _ v1.RoleMenuService = (*roleMenuService)(nil) + +func NewRoleMenuService(repo system.RoleMenuRepository) *roleMenuService { + return &roleMenuService{ + repo: repo, + } +} + +func (s *roleMenuService) Create(ctx context.Context, req []*system.RoleMenu) error { + return s.repo.Create(ctx, req) +} + +func (s *roleMenuService) DeleteByRoleID(ctx context.Context, roleID int32) error { + return s.repo.DeleteByRoleID(ctx, roleID) +} + +func (s *roleMenuService) ListByRoleID(ctx context.Context, roleID int32) ([]*system.RoleMenu, error) { + return s.repo.ListByRoleID(ctx, roleID) +} diff --git a/internal/erpserver/service/v1/system/user.go b/internal/erpserver/service/v1/system/user.go new file mode 100644 index 0000000..ed8b362 --- /dev/null +++ b/internal/erpserver/service/v1/system/user.go @@ -0,0 +1,203 @@ +package system + +import ( + "context" + "encoding/json" + "errors" + "strconv" + "time" + + "management/internal/db/model/dto" + db "management/internal/db/sqlc" + "management/internal/erpserver/model/form" + "management/internal/erpserver/model/system" + "management/internal/erpserver/model/view" + v1 "management/internal/erpserver/service/v1" + "management/internal/pkg/crypto" + "management/internal/pkg/know" + "management/internal/pkg/rand" + "management/internal/pkg/session" + + "github.com/drhin/logger" + "github.com/google/uuid" + "go.uber.org/zap" +) + +type userService struct { + session session.Session + log *logger.Logger + repo system.UserRepository + roleService v1.RoleService + loginLogService v1.LoginLogService +} + +var _ v1.UserService = (*userService)(nil) + +func NewUserService(session session.Session, log *logger.Logger, repo system.UserRepository, roleService v1.RoleService, loginLogService v1.LoginLogService) *userService { + return &userService{ + session: session, + log: log, + repo: repo, + roleService: roleService, + loginLogService: loginLogService, + } +} + +func (b *userService) Create(ctx context.Context, req *form.User) error { + salt, err := rand.String(10) + if err != nil { + return err + } + + hashedPassword, err := crypto.BcryptHashPassword(req.Password + salt) + if err != nil { + return err + } + + initTime, err := time.ParseInLocation(time.DateTime, "0001-01-01 00:00:00", time.Local) + if err != nil { + return err + } + + user := &system.User{ + Uuid: uuid.Must(uuid.NewV7()), + Email: req.Email, + Username: req.Username, + HashedPassword: hashedPassword, + Salt: salt, + Avatar: req.Avatar, + Gender: req.Gender, + DepartmentID: req.DepartmentID, + RoleID: req.RoleID, + Status: *req.Status, + ChangePasswordAt: initTime, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + err = b.repo.Create(ctx, user) + if err != nil { + if db.IsUniqueViolation(err) { + return errors.New("用户已经存在") + } + return err + } + return nil +} + +func (b *userService) Update(ctx context.Context, req *form.User) error { + user, err := b.repo.Get(ctx, *req.ID) + if err != nil { + return err + } + + user.Username = req.Username + user.Avatar = req.Avatar + user.Gender = req.Gender + user.DepartmentID = req.DepartmentID + user.RoleID = req.RoleID + user.Status = *req.Status + user.UpdatedAt = time.Now() + if req.ChangePassword == "on" { + hashedPassword, err := crypto.BcryptHashPassword(req.Password + user.Salt) + if err != nil { + return err + } + user.HashedPassword = hashedPassword + user.ChangePasswordAt = time.Now() + } + return b.repo.Update(ctx, user) +} + +func (b *userService) All(ctx context.Context) ([]*system.User, error) { + return b.repo.All(ctx) +} + +func (b *userService) List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) { + return b.repo.List(ctx, q) +} + +func (b *userService) Get(ctx context.Context, id int32) (*system.User, error) { + return b.repo.Get(ctx, id) +} + +func (b *userService) XmSelect(ctx context.Context) ([]*view.XmSelect, error) { + all, err := b.repo.All(ctx) + if err != nil || len(all) == 0 { + return nil, err + } + + var res []*view.XmSelect + for _, user := range all { + res = append(res, &view.XmSelect{ + Name: user.Username, + Value: strconv.Itoa(int(user.ID)), + }) + } + return res, nil +} + +func (b *userService) Login(ctx context.Context, req *form.Login) error { + l := system.NewLoginLog(req.Email, req.Os, req.Ip, req.Browser, req.Url, req.Referrer) + err := b.login(ctx, req) + if err != nil { + if err := b.loginLogService.Create(ctx, l.SetMessage(err.Error())); err != nil { + b.log.Error(err.Error(), err, zap.Any("login_log", l)) + } + return err + } + + if err := b.loginLogService.Create(ctx, l.SetOk("登录成功")); err != nil { + b.log.Error(err.Error(), err, zap.Any("login_log", l)) + } + return nil +} + +func (b *userService) login(ctx context.Context, req *form.Login) error { + user, err := b.repo.GetByEmail(ctx, req.Email) + if err != nil { + return err + } + + err = crypto.BcryptComparePassword(user.HashedPassword, req.Password+user.Salt) + if err != nil { + return errors.New("账号或密码错误") + } + + user.Role, err = b.roleService.Get(ctx, user.RoleID) + if err != nil { + return err + } + if user.Role == nil || user.Role.ID == 0 { + return errors.New("账号没有配置角色, 请联系管理员") + } + + // 登陆成功 + err = b.loginSuccess(ctx, user, req) + if err != nil { + return err + } + + return nil +} + +func (b *userService) loginSuccess(ctx context.Context, user *system.User, req *form.Login) error { + auth := dto.AuthorizeUser{ + ID: user.ID, + Uuid: user.Uuid, + Email: user.Email, + Username: user.Username, + RoleID: user.Role.ID, + RoleName: user.Role.DisplayName, + OS: req.Os, + IP: req.Ip, + Browser: req.Browser, + } + + gob, err := json.Marshal(auth) + if err != nil { + return err + } + + b.session.Put(ctx, know.StoreName, gob) + return nil +} diff --git a/internal/erpserver/store/expansion.go b/internal/erpserver/store/expansion.go new file mode 100644 index 0000000..3da45bd --- /dev/null +++ b/internal/erpserver/store/expansion.go @@ -0,0 +1,24 @@ +package store + +import "management/internal/erpserver/store/system" + +// ====================================================================================== +// system + +type SystemExpansion interface { + User() system.UserStore + LoginLog() system.LoginLogStore + Role() system.RoleStore +} + +func (store *datastore) User() system.UserStore { + return system.NewUserStore(store.core) +} + +func (store *datastore) LoginLog() system.LoginLogStore { + return system.NewLoginLogStore(store.core) +} + +func (store *datastore) Role() system.RoleStore { + return system.NewRoleStore(store.core) +} diff --git a/internal/erpserver/store/store.go b/internal/erpserver/store/store.go index 09ed57f..2968470 100644 --- a/internal/erpserver/store/store.go +++ b/internal/erpserver/store/store.go @@ -4,8 +4,6 @@ import ( "context" "sync" - "management/internal/erpserver/store/system" - "gorm.io/gorm" ) @@ -20,7 +18,7 @@ type IStore interface { DB(ctx context.Context) *gorm.DB TX(ctx context.Context, fn func(ctx context.Context) error) error - User() system.UserStore + SystemExpansion } // transactionKey 用于在 context.Context 中存储事务上下文的键. @@ -62,8 +60,3 @@ func (store *datastore) TX(ctx context.Context, fn func(ctx context.Context) err }, ) } - -// Users 返回一个实现了 UserStore 接口的实例. -func (store *datastore) User() system.UserStore { - return system.NewUserStore(store.core) -} diff --git a/internal/erpserver/store/system/login_log.go b/internal/erpserver/store/system/login_log.go new file mode 100644 index 0000000..e547846 --- /dev/null +++ b/internal/erpserver/store/system/login_log.go @@ -0,0 +1,58 @@ +package system + +import ( + "context" + + "management/internal/db/model/dto" + "management/internal/erpserver/model/system" + + "gorm.io/gorm" +) + +type LoginLogStore interface { + Create(ctx context.Context, obj *system.LoginLog) error + List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) +} + +type loginLogStore struct { + db *gorm.DB +} + +var _ LoginLogStore = (*loginLogStore)(nil) + +func NewLoginLogStore(db *gorm.DB) *loginLogStore { + return &loginLogStore{ + db: db, + } +} + +func (s *loginLogStore) Create(ctx context.Context, obj *system.LoginLog) error { + return s.db.WithContext(ctx).Create(obj).Error +} + +func (s *loginLogStore) List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) { + query := s.db.WithContext(ctx). + Model(&system.LoginLog{}). + Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchEmail != "" { + query = query.Where("email LIKE ?", "%"+q.SearchEmail+"%") + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var logs []*system.LoginLog + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&logs). + Error + if err != nil { + return nil, 0, err + } + return logs, count, nil +} diff --git a/internal/erpserver/store/system/role.go b/internal/erpserver/store/system/role.go new file mode 100644 index 0000000..9406b82 --- /dev/null +++ b/internal/erpserver/store/system/role.go @@ -0,0 +1,44 @@ +package system + +import ( + "context" + + "management/internal/erpserver/model/system" + + "gorm.io/gorm" +) + +type RoleStore interface { + Create(ctx context.Context, obj *system.Role) error + Update(ctx context.Context, obj *system.Role) error + Get(ctx context.Context, id int32) (*system.Role, error) +} + +type roleStore struct { + db *gorm.DB +} + +var _ RoleStore = (*roleStore)(nil) + +func NewRoleStore(db *gorm.DB) *roleStore { + return &roleStore{ + db: db, + } +} + +func (s *roleStore) Create(ctx context.Context, obj *system.Role) error { + return s.db.WithContext(ctx).Create(obj).Error +} + +func (s *roleStore) Update(ctx context.Context, obj *system.Role) error { + return s.db.WithContext(ctx).Save(obj).Error +} + +func (s *roleStore) Get(ctx context.Context, id int32) (*system.Role, error) { + var role system.Role + err := s.db.WithContext(ctx).Where("id =?", id).First(&role).Error + if err != nil { + return nil, err + } + return &role, nil +} diff --git a/internal/erpserver/store/system/user.go b/internal/erpserver/store/system/user.go index 9baa690..f578238 100644 --- a/internal/erpserver/store/system/user.go +++ b/internal/erpserver/store/system/user.go @@ -3,6 +3,7 @@ package system import ( "context" + "management/internal/db/model/dto" "management/internal/erpserver/model/system" "gorm.io/gorm" @@ -14,6 +15,8 @@ type UserStore interface { Update(ctx context.Context, obj *system.User) error Get(ctx context.Context, id int32) (*system.User, error) GetByEmail(ctx context.Context, email string) (*system.User, error) + All(ctx context.Context) ([]*system.User, error) + List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) UserExpansion } @@ -61,3 +64,47 @@ func (s *userStore) GetByEmail(ctx context.Context, email string) (*system.User, } return &user, nil } + +func (s *userStore) All(ctx context.Context) ([]*system.User, error) { + var users []*system.User + err := s.db.WithContext(ctx).Find(&users).Error + if err != nil { + return nil, err + } + return users, nil +} + +func (s *userStore) List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) { + query := s.db.WithContext(ctx). + Model(&system.User{}). + Where("created_at BETWEEN? AND?", q.SearchTimeBegin, q.SearchTimeEnd) + if q.SearchID != 0 { + query = query.Where("id = ?", q.SearchID) + } + if q.SearchName != "" { + query = query.Where("username LIKE ?", "%"+q.SearchName+"%") + } + if q.SearchEmail != "" { + query = query.Where("email LIKE ?", "%"+q.SearchEmail+"%") + } + if q.SearchStatus != 9999 { + query = query.Where("status = ?", q.SearchStatus) + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return nil, 0, err + } + + var users []*system.User + err = query. + Order("id DESC"). + Offset((q.Page - 1) * q.Rows). + Limit(q.Rows). + Find(&users).Error + if err != nil { + return nil, 0, err + } + return users, count, nil +} diff --git a/internal/pkg/database/postgresql.go b/internal/pkg/database/postgresql.go index c34cfde..c3e0df7 100644 --- a/internal/pkg/database/postgresql.go +++ b/internal/pkg/database/postgresql.go @@ -87,6 +87,6 @@ func setPostgreSQLDefaults(opts *PostgreSQLOptions) { opts.MaxConnectionLifeTime = time.Duration(10) * time.Second } if opts.Logger == nil { - opts.Logger = logger.Default + opts.Logger = logger.Default.LogMode(logger.Info) } } diff --git a/internal/pkg/middleware/audit.go b/internal/pkg/middleware/audit.go index ac31824..9b9e26b 100644 --- a/internal/pkg/middleware/audit.go +++ b/internal/pkg/middleware/audit.go @@ -3,13 +3,9 @@ package middleware import ( "context" "net/http" - "strconv" - "strings" "time" - db "management/internal/db/sqlc" - - "github.com/zhang2092/browser" + systemmodel "management/internal/erpserver/model/system" ) func (m *middleware) Audit(next http.Handler) http.Handler { @@ -27,47 +23,11 @@ func (m *middleware) Audit(next http.Handler) http.Handler { func (m *middleware) writeLog(req *http.Request, start time.Time) { end := time.Now() - duration := end.Sub(start) - var params string - method := req.Method - if method == "GET" { - params = req.URL.Query().Encode() - } else if method == "POST" { - contentType := req.Header.Get("Content-Type") - if strings.Contains(contentType, "application/json") { - body := make([]byte, req.ContentLength) - req.Body.Read(body) - params = string(body) - } else if strings.Contains(contentType, "application/x-www-form-urlencoded") { - params = req.Form.Encode() - } - } - - ctx := req.Context() - au := m.AuthUser(ctx) - arg := &db.CreateSysAuditLogParams{ - CreatedAt: time.Now(), - Email: au.Email, - Username: au.Username, - UserUuid: au.Uuid, - StartAt: start, - EndAt: end, - Duration: strconv.FormatInt(duration.Milliseconds(), 10), - Url: req.URL.RequestURI(), - Method: method, - Parameters: params, - RefererUrl: req.Header.Get("Referer"), - Ip: req.RemoteAddr, - Remark: "", - } - br, err := browser.NewBrowser(req.Header.Get("User-Agent")) - if err == nil { - arg.Os = br.Platform().Name() - arg.Browser = br.Name() - } + user := m.AuthUser(req.Context()) + al := systemmodel.NewAuditLog(req, user.Email, user.OS, user.Browser, start, end) c, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() - _ = m.biz.AuditBiz().Create(c, arg) + _ = m.auditLogsvc.Create(c, al) } diff --git a/internal/pkg/middleware/authorize.go b/internal/pkg/middleware/authorize.go index 67a1ac7..d2b2aca 100644 --- a/internal/pkg/middleware/authorize.go +++ b/internal/pkg/middleware/authorize.go @@ -42,7 +42,7 @@ func (m *middleware) Authorize(next http.Handler) http.Handler { return } - menus, err := m.biz.MenuBiz().MapOwnerMenuByRoleID(ctx, user.RoleID) + menus, err := m.menusvc.ListByRoleIDToMap(ctx, user.RoleID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -80,3 +80,23 @@ func (m *middleware) AuthUser(ctx context.Context) dto.AuthorizeUser { } return user } + +func (m *middleware) IsAuth(ctx context.Context) bool { + var user dto.AuthorizeUser + b := m.session.GetBytes(ctx, know.StoreName) + if err := json.Unmarshal(b, &user); err == nil { + return true + } + return false +} + +func (m *middleware) RefreshToken(ctx context.Context) bool { + if err := m.session.RenewToken(ctx); err == nil { + return true + } + return false +} + +func (m *middleware) Destroy(ctx context.Context) error { + return m.session.Destroy(ctx) +} diff --git a/internal/pkg/middleware/middleware.go b/internal/pkg/middleware/middleware.go index 3172bdd..ea04afa 100644 --- a/internal/pkg/middleware/middleware.go +++ b/internal/pkg/middleware/middleware.go @@ -5,28 +5,33 @@ import ( "net/http" "management/internal/db/model/dto" - systemv1 "management/internal/erpserver/biz/v1/system" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/session" ) -type IMiddleware interface { +type Middleware interface { Audit(next http.Handler) http.Handler NoSurf(next http.Handler) http.Handler LoadSession(next http.Handler) http.Handler Authorize(next http.Handler) http.Handler AuthUser(ctx context.Context) dto.AuthorizeUser + IsAuth(ctx context.Context) bool + RefreshToken(ctx context.Context) bool + Destroy(ctx context.Context) error } type middleware struct { - biz systemv1.SystemBiz - session session.ISession + session session.Session + menusvc v1.MenuService + auditLogsvc v1.AuditLogService } -var _ IMiddleware = (*middleware)(nil) +var _ Middleware = (*middleware)(nil) -func New(biz systemv1.SystemBiz, session session.ISession) IMiddleware { +func New(session session.Session, menusvc v1.MenuService, auditLogsvc v1.AuditLogService) Middleware { return &middleware{ - biz: biz, - session: session, + session: session, + menusvc: menusvc, + auditLogsvc: auditLogsvc, } } diff --git a/internal/pkg/redis/redis.go b/internal/pkg/redis/redis.go index 23205be..962b2c7 100644 --- a/internal/pkg/redis/redis.go +++ b/internal/pkg/redis/redis.go @@ -15,7 +15,7 @@ import ( var ErrRedisKeyNotFound = errors.New("redis key not found") -type IRedis interface { +type RedisCache interface { Encode(a any) ([]byte, error) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error Del(ctx context.Context, keys ...string) error @@ -30,7 +30,7 @@ type redisCache struct { engine *redis.Client } -var _ IRedis = (*redisCache)(nil) +var _ RedisCache = (*redisCache)(nil) func New(conf config.Redis) (*redisCache, error) { rdb := redis.NewClient(&redis.Options{ diff --git a/internal/pkg/session/session.go b/internal/pkg/session/session.go index f54e11d..dad7ad5 100644 --- a/internal/pkg/session/session.go +++ b/internal/pkg/session/session.go @@ -2,15 +2,15 @@ package session import ( "context" + "database/sql" "net/http" "time" - "github.com/alexedwards/scs/pgxstore" + "github.com/alexedwards/scs/postgresstore" "github.com/alexedwards/scs/v2" - "github.com/jackc/pgx/v5/pgxpool" ) -type ISession interface { +type Session interface { Destroy(ctx context.Context) error LoadAndSave(next http.Handler) http.Handler Put(ctx context.Context, key string, val any) @@ -23,7 +23,7 @@ type session struct { sessionManager *scs.SessionManager } -func New(pool *pgxpool.Pool, prod bool) ISession { +func New(db *sql.DB, prod bool) Session { sessionManager := scs.New() sessionManager.Lifetime = 24 * time.Hour sessionManager.IdleTimeout = 2 * time.Hour @@ -35,10 +35,10 @@ func New(pool *pgxpool.Pool, prod bool) ISession { // postgres // github.com/alexedwards/scs/postgresstore - // sessionManager.Store = postgresstore.New(db) + sessionManager.Store = postgresstore.New(db) // pgx // github.com/alexedwards/scs/pgxstore - sessionManager.Store = pgxstore.New(pool) + // sessionManager.Store = pgxstore.New(pool) // redis // sessionManager.Store = newRedisStore() diff --git a/internal/pkg/tpl/render.go b/internal/pkg/tpl/render.go index 3ba1a09..4934df0 100644 --- a/internal/pkg/tpl/render.go +++ b/internal/pkg/tpl/render.go @@ -4,7 +4,7 @@ import ( "html/template" "net/http" - systemv1 "management/internal/erpserver/biz/v1/system" + v1 "management/internal/erpserver/service/v1" "management/internal/pkg/session" ) @@ -17,17 +17,17 @@ type Renderer interface { } type render struct { - session session.ISession + session session.Session config *TemplateConfig templates map[string]*template.Template - menuBiz systemv1.MenuBiz + menusvc v1.MenuService } -func New(session session.ISession, menuBiz systemv1.MenuBiz) (Renderer, error) { +func New(session session.Session, menusvc v1.MenuService) (Renderer, error) { render := &render{ session: session, - menuBiz: menuBiz, + menusvc: menusvc, config: &TemplateConfig{ Root: ".", Extension: ".tmpl", diff --git a/internal/pkg/tpl/util.go b/internal/pkg/tpl/util.go index d98e6a3..6430e3e 100644 --- a/internal/pkg/tpl/util.go +++ b/internal/pkg/tpl/util.go @@ -44,15 +44,14 @@ func (r *render) setDefaultData(req *http.Request, data map[string]any) map[stri func (r *render) getCurrentPathBtns(ctx context.Context, roleID int32, path string) []*dto.OwnerMenuDto { var res []*dto.OwnerMenuDto - // 获取当前path的菜单 - menu, err := r.menuBiz.GetSysMenuByUrl(ctx, path) + // 获取当前登陆角色的权限 + menus, err := r.menusvc.ListByRoleIDToMap(ctx, roleID) if err != nil { return res } - // 获取权限 - menus, err := r.menuBiz.ListOwnerMenuByRoleID(ctx, roleID) - if err != nil { + menu, ok := menus[path] + if !ok { return res } diff --git a/main.go b/main.go index ad388da..0b74cfe 100644 --- a/main.go +++ b/main.go @@ -2,23 +2,8 @@ package main import ( "management/cmd" - - "github.com/drhin/logger" - "go.uber.org/zap" ) func main() { - log, err := logger.NewProduction() - if err != nil { - panic(err) - } - defer log.Sync() - - log.With( - zap.String("html", "

Hello World

"), - ).Info("Starting application") - - return - cmd.Execute() } diff --git a/web/statics/admin/css/style.css b/web/statics/admin/css/style.css index e637c14..6c08c3c 100644 --- a/web/statics/admin/css/style.css +++ b/web/statics/admin/css/style.css @@ -205,4 +205,37 @@ xm-select .xm-body .xm-option.selected .xm-option-icon { color: var(--global-pri .own-tree { height: 100%; overflow-y: auto; +} + +.userinfo { + display: flex; + padding: 20px; +} + +.userinfo .logo { + width: 60px; + height: 60px; +} + +.userinfo .logo img { + display:block; + width: 60px; + height: 60px; + border-radius: 50%; +} + +.userinfo .info { + margin-left: 20px; +} + +.userinfo .info p { + font-size: 12px; + color: #666666; +} + +.userinfo .info .name { + font-size: 15px; + color: #333333; + margin-bottom: 8px; + margin-top: 5px; } \ No newline at end of file diff --git a/web/templates/manage/home/dashboard.tmpl b/web/templates/manage/home/dashboard.tmpl index 1f0a75b..4b4cb12 100644 --- a/web/templates/manage/home/dashboard.tmpl +++ b/web/templates/manage/home/dashboard.tmpl @@ -1 +1,13 @@ -

dashboard

\ No newline at end of file +
+
+
+ +
+

欢迎您,{{.Auth.Username}} ({{.Auth.RoleName}})

+

这是您第 {{.LoginCount}} 次登录,上次登录日期:{{dateFormat .LastLoginTime}},如果不是您本人登录,请及时修改密码 。

+
+
+
+
\ No newline at end of file diff --git a/web/templates/manage/system/audit_log/list.tmpl b/web/templates/manage/system/audit_log/list.tmpl index dd8efbb..a193e37 100644 --- a/web/templates/manage/system/audit_log/list.tmpl +++ b/web/templates/manage/system/audit_log/list.tmpl @@ -93,7 +93,7 @@ limits: [15, 30, 45, 60, 75, 90], cols: [[ { field: 'email', title: '邮箱', align: 'left', width: 180, fixed: 'left' }, - { field: 'username', title: '用户名', align: 'left', width: 100 }, + // { field: 'username', title: '用户名', align: 'left', width: 100 }, { field: 'created_at', title: '创建时间', align: 'left', width: 160, templet: function (d) { return !d.created_at ? '' : util.toDateString(d.created_at) } }, { field: 'method', title: '请求方法', align: 'left', width: 90 }, { field: 'parameters', title: '请求参数', align: 'left', width: 120 }, diff --git a/web/templates/manage/system/department/list.tmpl b/web/templates/manage/system/department/list.tmpl index 9762e00..52a4541 100644 --- a/web/templates/manage/system/department/list.tmpl +++ b/web/templates/manage/system/department/list.tmpl @@ -1,7 +1,7 @@ {{template "header"}}