From b71e718308a68307ef9219d9e08785136026bf42 Mon Sep 17 00:00:00 2001 From: kenneth <1185230223@qq.com> Date: Thu, 12 Jun 2025 10:20:26 +0800 Subject: [PATCH] update --- Makefile | 24 +- cmd/erp.go | 20 +- configs/config.dev.yaml | 5 + doc/db.dbml | 6 - go.mod | 10 +- go.sum | 14 + internal/erpserver/app.go | 46 + internal/erpserver/handler/system/config.go | 4 +- internal/erpserver/handler/system/home.go | 10 +- internal/erpserver/model/dto/system.go | 5 + internal/erpserver/model/system/config.go | 14 +- internal/erpserver/model/system/department.go | 1 + internal/erpserver/model/system/login_log.go | 4 +- internal/erpserver/model/system/menu.go | 3 +- internal/erpserver/model/system/role.go | 1 + internal/erpserver/model/system/user.go | 1 + .../migration/000001_init_schema.down.sql | 17 + .../migration/000001_init_schema.up.sql | 666 ++++++++++++++ internal/erpserver/repository/seed/seed.go | 46 + internal/erpserver/repository/store.go | 39 +- .../erpserver/repository/system/config.go | 34 + .../erpserver/repository/system/department.go | 21 + .../erpserver/repository/system/login_log.go | 9 +- internal/erpserver/repository/system/menu.go | 8 +- .../erpserver/repository/system/menu_seed.go | 828 ++++++++++++++++++ internal/erpserver/repository/system/role.go | 46 + internal/erpserver/repository/system/user.go | 48 + internal/erpserver/service/v1/service.go | 3 +- .../erpserver/service/v1/system/config.go | 16 +- .../erpserver/service/v1/system/login_log.go | 17 +- internal/erpserver/service/v1/system/menu.go | 3 +- internal/erpserver/wire.go | 4 +- internal/erpserver/wire_gen.go | 33 +- internal/pkg/config/config.go | 19 +- internal/pkg/database/postgresql.go | 8 +- internal/pkg/middleware/audit.go | 22 +- internal/pkg/middleware/authorize.go | 1 + internal/pkg/render/funcs/method.go | 4 + modd.conf | 2 +- web/templates/manage/home/dashboard.tmpl | 7 +- 40 files changed, 1961 insertions(+), 108 deletions(-) create mode 100644 internal/erpserver/app.go create mode 100644 internal/erpserver/repository/migration/000001_init_schema.down.sql create mode 100644 internal/erpserver/repository/migration/000001_init_schema.up.sql create mode 100644 internal/erpserver/repository/seed/seed.go create mode 100644 internal/erpserver/repository/system/menu_seed.go diff --git a/Makefile b/Makefile index 32788a4..20f4e72 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,11 @@ network: .PHONY: redis redis: - docker run --network management --name rd -d -p 6379:6379 redis:7.2.4 --requirepass "secret" + docker run --network management --name rd -d -p 6379:6379 redis:7.4-alpine --requirepass "secret" .PHONY: postgres postgres: - docker run --name postgres --network management -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:16-alpine + docker run --name postgres --network management -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:17-alpine .PHONY: create_db create_db: @@ -26,31 +26,19 @@ psql: .PHONY: migrate_init migrate_init: - migrate create -ext sql -dir internal/db/migration -seq init_schema + migrate create -ext sql -dir internal/erpserver/repository/migration -seq init_schema .PHONY: migrate_up migrate_up: - migrate -path internal/db/migration -database "$(DB_URL)" -verbose up + migrate -path internal/erpserver/repository/migration -database "$(DB_URL)" -verbose up .PHONY: migrate_down migrate_down: - migrate -path internal/db/migration -database "$(DB_URL)" -verbose down - -.PHONY: db -db: - sql2dbml --postgres doc/ss.sql -o doc/db.dbm - -.PHONY: db_docs -db_docs: - dbdocs build doc/db.dbml + migrate -path internal/erpserver/repository/migration -database "$(DB_URL)" -verbose down .PHONY: db_schema db_schema: - dbml2sql --postgres -o internal/db/migration/000001_init_schema.up.sql doc/db.dbml - -.PHONY: sqlc -sqlc: - sqlc generate + dbml2sql --postgres -o internal/erpserver/repository/migration/000001_init_schema.up.sql doc/db.dbml .PHONY: wire wire: diff --git a/cmd/erp.go b/cmd/erp.go index b389408..8582bc8 100644 --- a/cmd/erp.go +++ b/cmd/erp.go @@ -8,6 +8,7 @@ import ( "time" "management/internal/erpserver" + "management/internal/erpserver/repository/seed" "management/internal/pkg/config" "github.com/drhin/logger" @@ -50,11 +51,22 @@ func runErp() error { return err } - mux, fn, err := erpserver.NewWire(conf, l) + app, fn, err := erpserver.NewWire(conf, l) if err != nil { return err } + // database seed + if err = seed.Init( + app.ConfigRepo, + app.DepartmentRepo, + app.RoleRepo, + app.UserRepo, + app.MenuRepo, + ); err != nil { + return err + } + defer fn() address := fmt.Sprintf("%s:%d", conf.App.Host, conf.App.Port) @@ -63,19 +75,21 @@ func runErp() error { if runtime.GOOS == "windows" { s := &http.Server{ Addr: address, - Handler: mux, + Handler: app.Router, ReadTimeout: 20 * time.Second, WriteTimeout: 20 * time.Second, MaxHeaderBytes: 1 << 20, } return s.ListenAndServe() } else { - s := endless.NewServer(address, mux) + s := endless.NewServer(address, app.Router) s.ReadHeaderTimeout = 20 * time.Second s.WriteTimeout = 20 * time.Second s.MaxHeaderBytes = 1 << 20 return s.ListenAndServe() } + + //return nil } func init() { diff --git a/configs/config.dev.yaml b/configs/config.dev.yaml index 230c922..23f0aad 100644 --- a/configs/config.dev.yaml +++ b/configs/config.dev.yaml @@ -8,6 +8,11 @@ db: username: root password: secret db_name: management + max_idle_conns: 10 + max_open_conns: 100 + conn_max_lifetime: 7h + conn_max_idle_time: 30m + log_mode: true redis: host: 127.0.0.1 port: 6379 diff --git a/doc/db.dbml b/doc/db.dbml index 6b2e355..41db299 100644 --- a/doc/db.dbml +++ b/doc/db.dbml @@ -34,8 +34,6 @@ Table "sys_user_login_log" { "id" BIGSERIAL [not null, increment] "created_at" TIMESTAMPTZ [not null, default: `NOW()`, note: '创建时间'] "email" VARCHAR(100) [not null, note: '邮箱地址'] - "username" VARCHAR(100) [not null, note: '用户名称'] - "user_uuid" uuid [not null, note: '用户uuid'] "is_success" Boolean [not null, note: '是否登陆成功'] "message" VARCHAR(300) [not null, note: '登陆消息'] "referer_url" VARCHAR(500) [not null, note: '上一个链接'] @@ -48,7 +46,6 @@ Table "sys_user_login_log" { id [pk] created_at email - username } } @@ -56,8 +53,6 @@ Table "sys_audit_log" { "id" BIGSERIAL [not null, increment] "created_at" TIMESTAMPTZ [not null, default: `NOW()`, note: '创建时间'] "email" VARCHAR(100) [not null, note: '邮箱地址'] - "username" VARCHAR(100) [not null, note: '用户名称'] - "user_uuid" uuid [not null, note: '用户uuid'] "start_at" TIMESTAMPTZ [not null, note: '请求开始时间'] "end_at" TIMESTAMPTZ [not null, note: '请求结束时间'] "duration" VARCHAR(10) [not null, note: '请求总时间'] @@ -74,7 +69,6 @@ Table "sys_audit_log" { id [pk] created_at email - username } } diff --git a/go.mod b/go.mod index 70cc86d..5d9448c 100644 --- a/go.mod +++ b/go.mod @@ -26,14 +26,16 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/crypto v0.38.0 gorm.io/driver/postgres v1.5.11 - gorm.io/gorm v1.26.1 + gorm.io/gorm v1.30.0 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -54,9 +56,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/image v0.27.0 // indirect golang.org/x/net v0.40.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/text v0.26.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/datatypes v1.2.5 // indirect + gorm.io/driver/mysql v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 91b6776..6d52bdc 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/alexedwards/scs/postgresstore v0.0.0-20250417082927-ab20b3feb5e9 h1:FGBhs+LG4w1y511QLcuLr1xfhI7Fbyq6Da1TCf6EQq4= @@ -38,6 +40,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= @@ -166,6 +170,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -199,6 +205,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -217,7 +225,13 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I= +gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= diff --git a/internal/erpserver/app.go b/internal/erpserver/app.go new file mode 100644 index 0000000..9f2eee9 --- /dev/null +++ b/internal/erpserver/app.go @@ -0,0 +1,46 @@ +package erpserver + +import ( + "management/internal/erpserver/model/system" + + "github.com/go-chi/chi/v5" +) + +type App struct { + UserRepo system.UserRepository + RoleRepo system.RoleRepository + MenuRepo system.MenuRepository + RoleMenuRepo system.RoleMenuRepository + DepartmentRepo system.DepartmentRepository + ConfigRepo system.ConfigRepository + LoginLogRepo system.LoginLogRepository + AuditLogRepo system.AuditLogRepository + + Router *chi.Mux +} + +func NewApp( + UserRepo system.UserRepository, + RoleRepo system.RoleRepository, + MenuRepo system.MenuRepository, + RoleMenuRepo system.RoleMenuRepository, + DepartmentRepo system.DepartmentRepository, + ConfigRepo system.ConfigRepository, + LoginLogRepo system.LoginLogRepository, + AuditLogRepo system.AuditLogRepository, + + Router *chi.Mux, +) App { + return App{ + UserRepo: UserRepo, + RoleRepo: RoleRepo, + MenuRepo: MenuRepo, + RoleMenuRepo: RoleMenuRepo, + DepartmentRepo: DepartmentRepo, + ConfigRepo: ConfigRepo, + LoginLogRepo: LoginLogRepo, + AuditLogRepo: AuditLogRepo, + + Router: Router, + } +} diff --git a/internal/erpserver/handler/system/config.go b/internal/erpserver/handler/system/config.go index e44304c..00248bc 100644 --- a/internal/erpserver/handler/system/config.go +++ b/internal/erpserver/handler/system/config.go @@ -101,7 +101,7 @@ func (h *ConfigHandler) Save(w http.ResponseWriter, r *http.Request) { if id == 0 { arg := &systemModel.Config{ Key: key, - Value: value, + Value: []byte(value), } err := h.configService.Create(ctx, arg) if err != nil { @@ -121,7 +121,7 @@ func (h *ConfigHandler) Save(w http.ResponseWriter, r *http.Request) { return } - res.Value = value + res.Value = []byte(value) err = h.configService.Update(ctx, res) if err != nil { h.JSONErr(w, err.Error()) diff --git a/internal/erpserver/handler/system/home.go b/internal/erpserver/handler/system/home.go index 51bd896..2d9f092 100644 --- a/internal/erpserver/handler/system/home.go +++ b/internal/erpserver/handler/system/home.go @@ -33,12 +33,12 @@ func (h *HomeHandler) Dashboard(w http.ResponseWriter, r *http.Request) { ctx := r.Context() auth := h.AuthUser(ctx) user, _ := h.userService.Get(ctx, auth.ID) - t := h.loginLogService.LoginLatestTime(ctx, auth.Email) + lt, _ := h.loginLogService.LoginTime(ctx, auth.Email) c := h.loginLogService.LoginCount(ctx, auth.Email) h.HTML(w, r, "home/dashboard.tmpl", map[string]any{ - "Auth": auth, - "User": user, - "LastLoginTime": t, - "LoginCount": c, + "Auth": auth, + "User": user, + "LoginTime": lt, + "LoginCount": c, }) } diff --git a/internal/erpserver/model/dto/system.go b/internal/erpserver/model/dto/system.go index d703b95..9095792 100644 --- a/internal/erpserver/model/dto/system.go +++ b/internal/erpserver/model/dto/system.go @@ -54,3 +54,8 @@ type SysMenuDto struct { Children []*SysMenuDto `json:"children"` } + +type LoginTimeDto struct { + ThisLoginTime time.Time + LastLoginTime time.Time +} diff --git a/internal/erpserver/model/system/config.go b/internal/erpserver/model/system/config.go index a112ba9..bf6f8e8 100644 --- a/internal/erpserver/model/system/config.go +++ b/internal/erpserver/model/system/config.go @@ -5,22 +5,26 @@ import ( "time" "management/internal/erpserver/model/dto" + + "gorm.io/datatypes" ) type ConfigRepository interface { + Initialize(ctx context.Context) error 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) + GetValueByKey(ctx context.Context, key string) ([]byte, 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 string `json:"value" gorm:"type:jsonb;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';"` + ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` + Key string `json:"key" gorm:"type:varchar(200);not null;uniqueIndex"` + Value datatypes.JSON `json:"value" gorm:"type:jsonb;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 { diff --git a/internal/erpserver/model/system/department.go b/internal/erpserver/model/system/department.go index 7562808..5831215 100644 --- a/internal/erpserver/model/system/department.go +++ b/internal/erpserver/model/system/department.go @@ -8,6 +8,7 @@ import ( ) type DepartmentRepository interface { + Initialize(ctx context.Context) error Create(ctx context.Context, obj *Department) error Update(ctx context.Context, obj *Department) error Get(ctx context.Context, id int32) (*Department, error) diff --git a/internal/erpserver/model/system/login_log.go b/internal/erpserver/model/system/login_log.go index 7503df4..67b6cf8 100644 --- a/internal/erpserver/model/system/login_log.go +++ b/internal/erpserver/model/system/login_log.go @@ -9,7 +9,7 @@ import ( type LoginLogRepository interface { Create(ctx context.Context, obj *LoginLog) error - GetLatest(ctx context.Context, email string) (*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) } @@ -27,7 +27,7 @@ type LoginLog struct { Browser string `json:"browser" gorm:"type:varchar(100);not null;"` } -func (LoginLog) TableName() string { +func (*LoginLog) TableName() string { return "sys_user_login_log" } diff --git a/internal/erpserver/model/system/menu.go b/internal/erpserver/model/system/menu.go index 9983e47..44cc7d1 100644 --- a/internal/erpserver/model/system/menu.go +++ b/internal/erpserver/model/system/menu.go @@ -6,7 +6,8 @@ import ( ) type MenuRepository interface { - Create(ctx context.Context, obj *Menu) error + Initialize(ctx context.Context) error + Create(ctx context.Context, obj *Menu) (*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) diff --git a/internal/erpserver/model/system/role.go b/internal/erpserver/model/system/role.go index 5635967..f6ee3f7 100644 --- a/internal/erpserver/model/system/role.go +++ b/internal/erpserver/model/system/role.go @@ -8,6 +8,7 @@ import ( ) type RoleRepository interface { + Initialize(ctx context.Context) (*Role, error) Create(ctx context.Context, obj *Role) error Update(ctx context.Context, obj *Role) error Get(ctx context.Context, id int32) (*Role, error) diff --git a/internal/erpserver/model/system/user.go b/internal/erpserver/model/system/user.go index ae6949d..4c51db1 100644 --- a/internal/erpserver/model/system/user.go +++ b/internal/erpserver/model/system/user.go @@ -10,6 +10,7 @@ import ( ) type UserRepository interface { + Initialize(ctx context.Context, departId, roleId int32) error Create(ctx context.Context, obj *User) error Update(ctx context.Context, obj *User) error Get(ctx context.Context, id int32) (*User, error) diff --git a/internal/erpserver/repository/migration/000001_init_schema.down.sql b/internal/erpserver/repository/migration/000001_init_schema.down.sql new file mode 100644 index 0000000..499f7a4 --- /dev/null +++ b/internal/erpserver/repository/migration/000001_init_schema.down.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS sys_user; +DROP TABLE IF EXISTS sys_user_login_log; +DROP TABLE IF EXISTS sys_audit_log; +DROP TABLE IF EXISTS sessions; +DROP TABLE IF EXISTS sys_department; +DROP TABLE IF EXISTS sys_role; +DROP TABLE IF EXISTS sys_menu; +DROP TABLE IF EXISTS sys_role_menu; +DROP TABLE IF EXISTS sys_config; +DROP TABLE IF EXISTS categories; +DROP TABLE IF EXISTS customers; +DROP TABLE IF EXISTS customer_contact; +DROP TABLE IF EXISTS projects; +DROP TABLE IF EXISTS project_files; +DROP TABLE IF EXISTS budgets; +DROP TABLE IF EXISTS incomes; +DROP TABLE IF EXISTS expenses; diff --git a/internal/erpserver/repository/migration/000001_init_schema.up.sql b/internal/erpserver/repository/migration/000001_init_schema.up.sql new file mode 100644 index 0000000..8e4245b --- /dev/null +++ b/internal/erpserver/repository/migration/000001_init_schema.up.sql @@ -0,0 +1,666 @@ +-- SQL dump generated using DBML (dbml.dbdiagram.io) +-- Database: PostgreSQL +-- Generated at: 2025-06-12T01:21:05.629Z + +CREATE TABLE "sys_user" ( + "id" SERIAL NOT NULL, + "uuid" uuid NOT NULL, + "email" VARCHAR(100) NOT NULL, + "username" VARCHAR(100) NOT NULL, + "hashed_password" bytea NOT NULL, + "salt" VARCHAR(20) NOT NULL, + "avatar" VARCHAR(200) NOT NULL, + "gender" INT NOT NULL DEFAULT 0, + "department_id" INT NOT NULL DEFAULT 0, + "role_id" INT NOT NULL DEFAULT 0, + "status" INT NOT NULL DEFAULT 0, + "change_password_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + PRIMARY KEY ("id") +); + +CREATE TABLE "sys_user_login_log" ( + "id" BIGSERIAL NOT NULL, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "email" VARCHAR(100) NOT NULL, + "is_success" Boolean NOT NULL, + "message" VARCHAR(300) NOT NULL, + "referer_url" VARCHAR(500) NOT NULL, + "url" VARCHAR(500) NOT NULL, + "os" VARCHAR(50) NOT NULL, + "ip" VARCHAR(20) NOT NULL, + "browser" VARCHAR(100) NOT NULL, + PRIMARY KEY ("id") +); + +CREATE TABLE "sys_audit_log" ( + "id" BIGSERIAL NOT NULL, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "email" VARCHAR(100) NOT NULL, + "start_at" TIMESTAMPTZ NOT NULL, + "end_at" TIMESTAMPTZ NOT NULL, + "duration" VARCHAR(10) NOT NULL, + "url" VARCHAR(500) NOT NULL, + "method" VARCHAR(50) NOT NULL, + "parameters" VARCHAR NOT NULL, + "referer_url" VARCHAR(500) NOT NULL, + "os" VARCHAR(50) NOT NULL, + "ip" VARCHAR(20) NOT NULL, + "browser" VARCHAR(100) NOT NULL, + "remark" VARCHAR(300) NOT NULL, + PRIMARY KEY ("id") +); + +CREATE TABLE "sessions" ( + "token" TEXT NOT NULL, + "data" BYTEA NOT NULL, + "expiry" TIMESTAMPTZ NOT NULL, + PRIMARY KEY ("token") +); + +CREATE TABLE "sys_department" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(200) NOT NULL, + "parent_id" INT NOT NULL, + "parent_path" VARCHAR(500) NOT NULL, + "status" INT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + PRIMARY KEY ("id") +); + +CREATE TABLE "sys_role" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(200) NOT NULL, + "display_name" VARCHAR(200) NOT NULL, + "parent_id" INT NOT NULL, + "parent_path" VARCHAR(500) NOT NULL, + "vip" Boolean NOT NULL, + "status" INT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + PRIMARY KEY ("id") +); + +CREATE TABLE "sys_menu" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(200) NOT NULL, + "display_name" VARCHAR(200) NOT NULL, + "url" VARCHAR(200) NOT NULL, + "type" VARCHAR(50) NOT NULL, + "parent_id" INT NOT NULL, + "parent_path" VARCHAR(500) NOT NULL, + "avatar" VARCHAR(100) NOT NULL, + "style" VARCHAR(100) NOT NULL, + "visible" Boolean NOT NULL, + "is_list" Boolean NOT NULL, + "status" INT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + PRIMARY KEY ("id") +); + +CREATE TABLE "sys_role_menu" ( + "role_id" INT NOT NULL, + "menu_id" INT NOT NULL, + PRIMARY KEY ("role_id", "menu_id") +); + +CREATE TABLE "sys_config" ( + "id" SERIAL NOT NULL, + "key" VARCHAR(100) NOT NULL, + "value" jsonb NOT NULL, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + PRIMARY KEY ("id") +); + +CREATE TABLE "categories" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(100) NOT NULL, + "icon" VARCHAR(300) NOT NULL DEFAULT '', + "description" VARCHAR(500) NOT NULL DEFAULT '', + "letter" VARCHAR(100) NOT NULL DEFAULT '', + "parent_id" INT NOT NULL DEFAULT 0, + "parent_path" VARCHAR(500) NOT NULL DEFAULT '', + "status" SMALLINT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + PRIMARY KEY ("id") +); + +CREATE TABLE "customers" ( + "id" BIGINT NOT NULL, + "name" VARCHAR(100) NOT NULL, + "category" INT NOT NULL DEFAULT 0, + "source" INT NOT NULL DEFAULT 0, + "address" VARCHAR(500) NOT NULL DEFAULT '', + "contact_name" VARCHAR(100) NOT NULL DEFAULT '', + "contact_phone" VARCHAR(50) NOT NULL DEFAULT '', + "status" SMALLINT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_by" INT NOT NULL DEFAULT 0, + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "updated_by" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE TABLE "customer_contact" ( + "id" SERIAL NOT NULL, + "name" VARCHAR(100) NOT NULL, + "telephone" VARCHAR(50) NOT NULL, + "customer_id" BIGINT NOT NULL, + "status" SMALLINT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_by" INT NOT NULL DEFAULT 0, + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "updated_by" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE TABLE "projects" ( + "id" BIGINT NOT NULL, + "name" VARCHAR(100) NOT NULL, + "start_at" TIMESTAMPTZ NOT NULL, + "end_at" TIMESTAMPTZ NOT NULL, + "customer_id" BIGINT NOT NULL, + "total_money" DECIMAL(10,2) NOT NULL DEFAULT 0, + "description" VARCHAR(500) NOT NULL DEFAULT '', + "apply_at" TIMESTAMPTZ NOT NULL, + "apply_user_id" INT NOT NULL, + "manager_id" INT NOT NULL, + "members" VARCHAR(1000) NOT NULL, + "status" SMALLINT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_user_id" INT NOT NULL DEFAULT 0, + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "updated_user_id" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE TABLE "project_files" ( + "id" BIGINT NOT NULL, + "name" VARCHAR(100) NOT NULL, + "path" VARCHAR(500) NOT NULL, + "project_id" BIGINT NOT NULL, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_user_id" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE TABLE "budgets" ( + "id" BIGSERIAL NOT NULL, + "project_id" BIGINT NOT NULL, + "name" VARCHAR(100) NOT NULL, + "budget_type" INT NOT NULL, + "category" INT NOT NULL, + "start_at" TIMESTAMPTZ NOT NULL, + "end_at" TIMESTAMPTZ NOT NULL, + "amount" DECIMAL(10,2) NOT NULL DEFAULT 0, + "used_amount" DECIMAL(10,2) NOT NULL DEFAULT 0, + "remaining_amount" DECIMAL(10,2) NOT NULL DEFAULT 0, + "remark" VARCHAR(500) NOT NULL DEFAULT '', + "status" SMALLINT NOT NULL DEFAULT 0, + "sort" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_user_id" INT NOT NULL DEFAULT 0, + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "updated_user_id" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE TABLE "incomes" ( + "id" BIGSERIAL NOT NULL, + "project_id" BIGINT NOT NULL, + "budget_id" BIGINT NOT NULL DEFAULT 0, + "amount" DECIMAL(10,2) NOT NULL DEFAULT 0, + "income_at" TIMESTAMPTZ NOT NULL, + "income_type" INT NOT NULL, + "income_bank" INT NOT NULL, + "remark" VARCHAR(500) NOT NULL DEFAULT '', + "status" SMALLINT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_user_id" INT NOT NULL DEFAULT 0, + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "updated_user_id" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE TABLE "expenses" ( + "id" BIGSERIAL NOT NULL, + "project_id" BIGINT NOT NULL, + "budget_id" BIGINT NOT NULL DEFAULT 0, + "amount" DECIMAL(10,2) NOT NULL DEFAULT 0, + "expenses_at" TIMESTAMPTZ NOT NULL, + "expenses_type" INT NOT NULL, + "remark" VARCHAR(500) NOT NULL DEFAULT '', + "status" SMALLINT NOT NULL DEFAULT 0, + "created_at" TIMESTAMPTZ NOT NULL DEFAULT (NOW()), + "created_user_id" INT NOT NULL DEFAULT 0, + "updated_at" TIMESTAMPTZ NOT NULL DEFAULT ('0001-01-01 00:00:00+8'), + "updated_user_id" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX ON "sys_user" ("uuid"); + +CREATE UNIQUE INDEX ON "sys_user" ("username"); + +CREATE UNIQUE INDEX ON "sys_user" ("email"); + +CREATE INDEX ON "sys_user" ("status"); + +CREATE INDEX ON "sys_user_login_log" ("created_at"); + +CREATE INDEX ON "sys_user_login_log" ("email"); + +CREATE INDEX ON "sys_audit_log" ("created_at"); + +CREATE INDEX ON "sys_audit_log" ("email"); + +CREATE INDEX ON "sessions" ("expiry"); + +CREATE UNIQUE INDEX ON "sys_department" ("name"); + +CREATE INDEX ON "sys_department" ("status"); + +CREATE UNIQUE INDEX ON "sys_role" ("name"); + +CREATE UNIQUE INDEX ON "sys_role" ("display_name"); + +CREATE INDEX ON "sys_role" ("status"); + +CREATE UNIQUE INDEX ON "sys_menu" ("url"); + +CREATE INDEX ON "sys_menu" ("type"); + +CREATE INDEX ON "sys_menu" ("parent_id"); + +CREATE INDEX ON "sys_menu" ("status"); + +CREATE UNIQUE INDEX ON "sys_config" ("key"); + +CREATE INDEX ON "categories" ("name"); + +CREATE UNIQUE INDEX ON "categories" ("letter"); + +CREATE INDEX ON "customers" ("name"); + +CREATE INDEX ON "customer_contact" ("name"); + +CREATE INDEX ON "customer_contact" ("telephone"); + +CREATE INDEX ON "projects" ("name"); + +CREATE INDEX ON "project_files" ("name"); + +CREATE INDEX ON "budgets" ("project_id"); + +CREATE INDEX ON "budgets" ("category"); + +CREATE INDEX ON "incomes" ("project_id"); + +CREATE INDEX ON "incomes" ("budget_id"); + +CREATE INDEX ON "incomes" ("income_type"); + +CREATE INDEX ON "expenses" ("project_id"); + +CREATE INDEX ON "expenses" ("budget_id"); + +CREATE INDEX ON "expenses" ("expenses_type"); + +COMMENT ON COLUMN "sys_user"."email" IS '邮箱地址'; + +COMMENT ON COLUMN "sys_user"."username" IS '用户名称'; + +COMMENT ON COLUMN "sys_user"."hashed_password" IS '加密密码'; + +COMMENT ON COLUMN "sys_user"."salt" IS '密码盐值'; + +COMMENT ON COLUMN "sys_user"."avatar" IS '头像'; + +COMMENT ON COLUMN "sys_user"."gender" IS '性别'; + +COMMENT ON COLUMN "sys_user"."department_id" IS '部门'; + +COMMENT ON COLUMN "sys_user"."role_id" IS '角色'; + +COMMENT ON COLUMN "sys_user"."status" IS '状态'; + +COMMENT ON COLUMN "sys_user"."change_password_at" IS '密码修改时间'; + +COMMENT ON COLUMN "sys_user"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_user"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "sys_user_login_log"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_user_login_log"."email" IS '邮箱地址'; + +COMMENT ON COLUMN "sys_user_login_log"."is_success" IS '是否登陆成功'; + +COMMENT ON COLUMN "sys_user_login_log"."message" IS '登陆消息'; + +COMMENT ON COLUMN "sys_user_login_log"."referer_url" IS '上一个链接'; + +COMMENT ON COLUMN "sys_user_login_log"."url" IS '链接'; + +COMMENT ON COLUMN "sys_user_login_log"."os" IS '系统'; + +COMMENT ON COLUMN "sys_user_login_log"."ip" IS 'ip'; + +COMMENT ON COLUMN "sys_user_login_log"."browser" IS '浏览器'; + +COMMENT ON COLUMN "sys_audit_log"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_audit_log"."email" IS '邮箱地址'; + +COMMENT ON COLUMN "sys_audit_log"."start_at" IS '请求开始时间'; + +COMMENT ON COLUMN "sys_audit_log"."end_at" IS '请求结束时间'; + +COMMENT ON COLUMN "sys_audit_log"."duration" IS '请求总时间'; + +COMMENT ON COLUMN "sys_audit_log"."url" IS '请求链接'; + +COMMENT ON COLUMN "sys_audit_log"."method" IS '请求类型'; + +COMMENT ON COLUMN "sys_audit_log"."parameters" IS '请求参数'; + +COMMENT ON COLUMN "sys_audit_log"."referer_url" IS '上一个链接'; + +COMMENT ON COLUMN "sys_audit_log"."os" IS '系统'; + +COMMENT ON COLUMN "sys_audit_log"."ip" IS 'ip'; + +COMMENT ON COLUMN "sys_audit_log"."browser" IS '浏览器'; + +COMMENT ON COLUMN "sys_audit_log"."remark" IS '备注'; + +COMMENT ON COLUMN "sessions"."token" IS 'token'; + +COMMENT ON COLUMN "sessions"."data" IS 'data'; + +COMMENT ON COLUMN "sessions"."expiry" IS 'expiry'; + +COMMENT ON COLUMN "sys_department"."name" IS '部门名称'; + +COMMENT ON COLUMN "sys_department"."parent_id" IS '上级id'; + +COMMENT ON COLUMN "sys_department"."parent_path" IS '树路径'; + +COMMENT ON COLUMN "sys_department"."status" IS '状态'; + +COMMENT ON COLUMN "sys_department"."sort" IS '排序'; + +COMMENT ON COLUMN "sys_department"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_department"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "sys_role"."name" IS '名称'; + +COMMENT ON COLUMN "sys_role"."display_name" IS '显示名称'; + +COMMENT ON COLUMN "sys_role"."parent_id" IS '上级id'; + +COMMENT ON COLUMN "sys_role"."parent_path" IS '树路径'; + +COMMENT ON COLUMN "sys_role"."vip" IS '是否vip'; + +COMMENT ON COLUMN "sys_role"."status" IS '状态'; + +COMMENT ON COLUMN "sys_role"."sort" IS '排序'; + +COMMENT ON COLUMN "sys_role"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_role"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "sys_menu"."name" IS '名称'; + +COMMENT ON COLUMN "sys_menu"."display_name" IS '显示名称'; + +COMMENT ON COLUMN "sys_menu"."url" IS '菜单url'; + +COMMENT ON COLUMN "sys_menu"."type" IS '菜单类型(node, menu, btn)'; + +COMMENT ON COLUMN "sys_menu"."parent_id" IS '上级id'; + +COMMENT ON COLUMN "sys_menu"."parent_path" IS '树路径'; + +COMMENT ON COLUMN "sys_menu"."avatar" IS '菜单图标'; + +COMMENT ON COLUMN "sys_menu"."style" IS '菜单样式'; + +COMMENT ON COLUMN "sys_menu"."visible" IS '是否可见'; + +COMMENT ON COLUMN "sys_menu"."is_list" IS '是否列表'; + +COMMENT ON COLUMN "sys_menu"."status" IS '状态'; + +COMMENT ON COLUMN "sys_menu"."sort" IS '排序'; + +COMMENT ON COLUMN "sys_menu"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_menu"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "sys_role_menu"."role_id" IS '角色id'; + +COMMENT ON COLUMN "sys_role_menu"."menu_id" IS '菜单id'; + +COMMENT ON COLUMN "sys_config"."key" IS '存储键'; + +COMMENT ON COLUMN "sys_config"."value" IS '存储值'; + +COMMENT ON COLUMN "sys_config"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "sys_config"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "categories"."id" IS 'ID'; + +COMMENT ON COLUMN "categories"."name" IS '名称'; + +COMMENT ON COLUMN "categories"."icon" IS '图标'; + +COMMENT ON COLUMN "categories"."description" IS '描述'; + +COMMENT ON COLUMN "categories"."letter" IS '拼音'; + +COMMENT ON COLUMN "categories"."parent_id" IS '父级ID'; + +COMMENT ON COLUMN "categories"."parent_path" IS '树路径'; + +COMMENT ON COLUMN "categories"."status" IS '状态'; + +COMMENT ON COLUMN "categories"."sort" IS '排序'; + +COMMENT ON COLUMN "categories"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "categories"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "customers"."id" IS 'ID'; + +COMMENT ON COLUMN "customers"."name" IS '名称'; + +COMMENT ON COLUMN "customers"."category" IS '类别'; + +COMMENT ON COLUMN "customers"."source" IS '来源'; + +COMMENT ON COLUMN "customers"."address" IS '地址'; + +COMMENT ON COLUMN "customers"."contact_name" IS '主要联系人'; + +COMMENT ON COLUMN "customers"."contact_phone" IS '主要联系人手机'; + +COMMENT ON COLUMN "customers"."status" IS '状态'; + +COMMENT ON COLUMN "customers"."sort" IS '排序'; + +COMMENT ON COLUMN "customers"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "customers"."created_by" IS '创建人'; + +COMMENT ON COLUMN "customers"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "customers"."updated_by" IS '更新人'; + +COMMENT ON COLUMN "customer_contact"."id" IS 'ID'; + +COMMENT ON COLUMN "customer_contact"."name" IS '名称'; + +COMMENT ON COLUMN "customer_contact"."telephone" IS '联系方式'; + +COMMENT ON COLUMN "customer_contact"."customer_id" IS '客户ID'; + +COMMENT ON COLUMN "customer_contact"."status" IS '状态'; + +COMMENT ON COLUMN "customer_contact"."sort" IS '排序'; + +COMMENT ON COLUMN "customer_contact"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "customer_contact"."created_by" IS '创建人'; + +COMMENT ON COLUMN "customer_contact"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "customer_contact"."updated_by" IS '更新人'; + +COMMENT ON COLUMN "projects"."id" IS 'ID'; + +COMMENT ON COLUMN "projects"."name" IS '名称'; + +COMMENT ON COLUMN "projects"."start_at" IS '开始时间'; + +COMMENT ON COLUMN "projects"."end_at" IS '结束时间'; + +COMMENT ON COLUMN "projects"."customer_id" IS '客户ID'; + +COMMENT ON COLUMN "projects"."total_money" IS '总金额'; + +COMMENT ON COLUMN "projects"."description" IS '简介'; + +COMMENT ON COLUMN "projects"."apply_at" IS '申请时间'; + +COMMENT ON COLUMN "projects"."apply_user_id" IS '申请人'; + +COMMENT ON COLUMN "projects"."manager_id" IS '项目经理'; + +COMMENT ON COLUMN "projects"."members" IS '项目成员'; + +COMMENT ON COLUMN "projects"."status" IS '状态'; + +COMMENT ON COLUMN "projects"."sort" IS '排序'; + +COMMENT ON COLUMN "projects"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "projects"."created_user_id" IS '创建人'; + +COMMENT ON COLUMN "projects"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "projects"."updated_user_id" IS '更新人'; + +COMMENT ON COLUMN "project_files"."id" IS 'ID'; + +COMMENT ON COLUMN "project_files"."name" IS '名称'; + +COMMENT ON COLUMN "project_files"."path" IS '路径'; + +COMMENT ON COLUMN "project_files"."project_id" IS '项目ID'; + +COMMENT ON COLUMN "project_files"."sort" IS '排序'; + +COMMENT ON COLUMN "project_files"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "project_files"."created_user_id" IS '创建人'; + +COMMENT ON COLUMN "budgets"."id" IS 'ID'; + +COMMENT ON COLUMN "budgets"."project_id" IS '项目ID'; + +COMMENT ON COLUMN "budgets"."name" IS '名称'; + +COMMENT ON COLUMN "budgets"."budget_type" IS '预算类型: 收入/支出'; + +COMMENT ON COLUMN "budgets"."category" IS '类别'; + +COMMENT ON COLUMN "budgets"."start_at" IS '开始时间'; + +COMMENT ON COLUMN "budgets"."end_at" IS '结束时间'; + +COMMENT ON COLUMN "budgets"."amount" IS '预算金额'; + +COMMENT ON COLUMN "budgets"."used_amount" IS '已使用金额'; + +COMMENT ON COLUMN "budgets"."remaining_amount" IS '剩余金额'; + +COMMENT ON COLUMN "budgets"."remark" IS '备注'; + +COMMENT ON COLUMN "budgets"."status" IS '状态'; + +COMMENT ON COLUMN "budgets"."sort" IS '排序'; + +COMMENT ON COLUMN "budgets"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "budgets"."created_user_id" IS '创建人'; + +COMMENT ON COLUMN "budgets"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "budgets"."updated_user_id" IS '更新人'; + +COMMENT ON COLUMN "incomes"."id" IS 'ID'; + +COMMENT ON COLUMN "incomes"."project_id" IS '项目ID'; + +COMMENT ON COLUMN "incomes"."budget_id" IS '预算ID'; + +COMMENT ON COLUMN "incomes"."amount" IS '收入金额'; + +COMMENT ON COLUMN "incomes"."income_at" IS '收入时间'; + +COMMENT ON COLUMN "incomes"."income_type" IS '收入类型'; + +COMMENT ON COLUMN "incomes"."income_bank" IS '收入银行'; + +COMMENT ON COLUMN "incomes"."remark" IS '备注'; + +COMMENT ON COLUMN "incomes"."status" IS '状态'; + +COMMENT ON COLUMN "incomes"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "incomes"."created_user_id" IS '创建人'; + +COMMENT ON COLUMN "incomes"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "incomes"."updated_user_id" IS '更新人'; + +COMMENT ON COLUMN "expenses"."id" IS 'ID'; + +COMMENT ON COLUMN "expenses"."project_id" IS '项目ID'; + +COMMENT ON COLUMN "expenses"."budget_id" IS '预算ID'; + +COMMENT ON COLUMN "expenses"."amount" IS '支出金额'; + +COMMENT ON COLUMN "expenses"."expenses_at" IS '支出时间'; + +COMMENT ON COLUMN "expenses"."expenses_type" IS '支出类型'; + +COMMENT ON COLUMN "expenses"."remark" IS '备注'; + +COMMENT ON COLUMN "expenses"."status" IS '状态'; + +COMMENT ON COLUMN "expenses"."created_at" IS '创建时间'; + +COMMENT ON COLUMN "expenses"."created_user_id" IS '创建人'; + +COMMENT ON COLUMN "expenses"."updated_at" IS '更新时间'; + +COMMENT ON COLUMN "expenses"."updated_user_id" IS '更新人'; diff --git a/internal/erpserver/repository/seed/seed.go b/internal/erpserver/repository/seed/seed.go new file mode 100644 index 0000000..dc9a6fa --- /dev/null +++ b/internal/erpserver/repository/seed/seed.go @@ -0,0 +1,46 @@ +package seed + +import ( + "context" + + "management/internal/erpserver/model/system" +) + +func Init( + configRepository system.ConfigRepository, + departmentRepository system.DepartmentRepository, + roleRepository system.RoleRepository, + userRepository system.UserRepository, + menuRepository system.MenuRepository, +) error { + ctx := context.Background() + + // 后台pear配置 + if err := configRepository.Initialize(ctx); err != nil { + return err + } + + // 部门 + err := departmentRepository.Initialize(ctx) + if err != nil { + return err + } + + // 角色 + role, err := roleRepository.Initialize(ctx) + if err != nil { + return err + } + + // 用户 + if err := userRepository.Initialize(ctx, 0, role.ID); err != nil { + return err + } + + // 菜单 + if err = menuRepository.Initialize(ctx); err != nil { + return err + } + + return nil +} diff --git a/internal/erpserver/repository/store.go b/internal/erpserver/repository/store.go index 1ac8ee6..221258d 100644 --- a/internal/erpserver/repository/store.go +++ b/internal/erpserver/repository/store.go @@ -10,6 +10,7 @@ import ( "github.com/drhin/logger" "gorm.io/driver/postgres" "gorm.io/gorm" + gl "gorm.io/gorm/logger" ) type txCtxKey struct{} @@ -59,15 +60,19 @@ func NewDB(log *logger.Logger, config *config.Config) (*gorm.DB, func(), error) config.DB.DBName, config.DB.Port, ) - db, err := gorm.Open(postgres.New(postgres.Config{ + pgConfig := postgres.Config{ DSN: dsn, PreferSimpleProtocol: true, // disables implicit prepared statement usage - }), &gorm.Config{}) + } + db, err := gorm.Open(postgres.New(pgConfig), &gorm.Config{ + Logger: getGormLogger(config), + }) if err != nil { return nil, nil, err } - db = db.Debug() + // db.Debug 会默认显示日志 + //db = db.Debug() // Connection Pool config sqlDB, err := db.DB() @@ -75,9 +80,24 @@ func NewDB(log *logger.Logger, config *config.Config) (*gorm.DB, func(), error) return nil, nil, err } - sqlDB.SetMaxIdleConns(10) - sqlDB.SetMaxOpenConns(100) - sqlDB.SetConnMaxLifetime(time.Hour) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + if err := sqlDB.PingContext(ctx); err != nil { + return nil, nil, err + } + + // 设置最大空闲连接数(默认 2) + sqlDB.SetMaxIdleConns(config.DB.MaxIdleConns) + + // 设置最大打开连接数(默认 0 无限制) + sqlDB.SetMaxOpenConns(config.DB.MaxOpenConns) + + // 设置连接最大存活时间 + sqlDB.SetConnMaxLifetime(config.DB.ConnMaxLifetime) + + // 设置连接最大空闲时间 + sqlDB.SetConnMaxIdleTime(config.DB.ConnMaxIdleTime) cleanup := func() { if err := sqlDB.Close(); err != nil { @@ -88,6 +108,13 @@ func NewDB(log *logger.Logger, config *config.Config) (*gorm.DB, func(), error) return db, cleanup, nil } +func getGormLogger(config *config.Config) gl.Interface { + if config.DB.LogMode { + return gl.Default.LogMode(gl.Info) // 开发环境显示日志 + } + return gl.Default.LogMode(gl.Silent) +} + //var ( // once sync.Once // // 全局变量,方便其它包直接调用已初始化好的 datastore 实例. diff --git a/internal/erpserver/repository/system/config.go b/internal/erpserver/repository/system/config.go index 96985d3..10f70f1 100644 --- a/internal/erpserver/repository/system/config.go +++ b/internal/erpserver/repository/system/config.go @@ -2,10 +2,15 @@ package system import ( "context" + "encoding/json" "management/internal/erpserver/model/dto" "management/internal/erpserver/model/system" "management/internal/erpserver/repository" + "management/internal/pkg/database" + "management/internal/pkg/know/pearadmin" + + "gorm.io/datatypes" ) type configRepository struct { @@ -18,6 +23,26 @@ func NewConfigRepository(repo *repository.Repository) system.ConfigRepository { } } +func (r *configRepository) Initialize(ctx context.Context) error { + _, err := r.GetByKey(ctx, pearadmin.PearKey) + if err != nil { + if database.IsNoRows(err) { + b, e := json.Marshal(pearadmin.PearJson) + if e != nil { + return e + } + + s := system.Config{ + Key: pearadmin.PearKey, + Value: datatypes.JSON(b), + } + return r.Create(ctx, &s) + } + return err + } + return nil +} + func (r *configRepository) Create(ctx context.Context, obj *system.Config) error { return r.repo.DB(ctx).Create(obj).Error } @@ -44,6 +69,15 @@ func (r *configRepository) GetByKey(ctx context.Context, key string) (*system.Co return &obj, nil } +func (r *configRepository) GetValueByKey(ctx context.Context, key string) ([]byte, error) { + var obj system.Config + err := r.repo.DB(ctx).Where("key = ?", key).First(&obj).Error + if err != nil { + return nil, err + } + return obj.Value, nil +} + func (r *configRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.Config, int64, error) { query := r.repo.DB(ctx). Model(&system.Config{}). diff --git a/internal/erpserver/repository/system/department.go b/internal/erpserver/repository/system/department.go index 55f5a26..61394f1 100644 --- a/internal/erpserver/repository/system/department.go +++ b/internal/erpserver/repository/system/department.go @@ -2,6 +2,7 @@ package system import ( "context" + "time" "management/internal/erpserver/model/dto" "management/internal/erpserver/model/system" @@ -18,6 +19,26 @@ func NewDepartmentRepository(repo *repository.Repository) system.DepartmentRepos } } +func (r *departmentRepository) Initialize(ctx context.Context) error { + var count int64 + if err := r.repo.DB(ctx).Model(&system.Department{}).Count(&count).Error; err != nil { + return err + } + if count == 0 { + obj := system.Department{ + Name: "公司", + ParentID: 0, + ParentPath: ",0,", + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return r.Create(ctx, &obj) + } + return nil +} + func (r *departmentRepository) Create(ctx context.Context, obj *system.Department) error { return r.repo.DB(ctx).Create(obj).Error } diff --git a/internal/erpserver/repository/system/login_log.go b/internal/erpserver/repository/system/login_log.go index d5174d9..239f85a 100644 --- a/internal/erpserver/repository/system/login_log.go +++ b/internal/erpserver/repository/system/login_log.go @@ -22,17 +22,18 @@ func (s *loginLogRepository) Create(ctx context.Context, obj *system.LoginLog) e return s.repo.DB(ctx).Create(obj).Error } -func (s *loginLogRepository) GetLatest(ctx context.Context, email string) (*system.LoginLog, error) { - var log system.LoginLog +func (s *loginLogRepository) GetLatest(ctx context.Context, email string) ([]*system.LoginLog, error) { + var logs []*system.LoginLog err := s.repo.DB(ctx). Where("email = ?", email). Order("id DESC"). - First(&log). + Limit(2). + Find(&logs). Error if err != nil { return nil, err } - return &log, nil + return logs, nil } func (s *loginLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) { diff --git a/internal/erpserver/repository/system/menu.go b/internal/erpserver/repository/system/menu.go index f90cb8f..683fe04 100644 --- a/internal/erpserver/repository/system/menu.go +++ b/internal/erpserver/repository/system/menu.go @@ -17,8 +17,12 @@ func NewMenuRepository(repo *repository.Repository) system.MenuRepository { } } -func (r *menuRepository) Create(ctx context.Context, obj *system.Menu) error { - return r.repo.DB(ctx).Create(obj).Error +func (r *menuRepository) Create(ctx context.Context, obj *system.Menu) (*system.Menu, error) { + err := r.repo.DB(ctx).Create(obj).Error + if err != nil { + return nil, err + } + return obj, nil } func (r *menuRepository) Update(ctx context.Context, obj *system.Menu) error { diff --git a/internal/erpserver/repository/system/menu_seed.go b/internal/erpserver/repository/system/menu_seed.go new file mode 100644 index 0000000..669606b --- /dev/null +++ b/internal/erpserver/repository/system/menu_seed.go @@ -0,0 +1,828 @@ +package system + +import ( + "context" + "fmt" + "time" + + "management/internal/erpserver/model/system" + + "github.com/google/uuid" +) + +func (r *menuRepository) Initialize(ctx context.Context) error { + var count int64 + if err := r.repo.DB(ctx).Model(&system.Menu{}).Count(&count).Error; err != nil { + return err + } + if count > 0 { + return nil + } + + sys, err := r.Create(ctx, &system.Menu{ + Name: "系统管理", + DisplayName: "系统管理", + Url: uuid.Must(uuid.NewRandom()).String(), + Type: "node", + ParentID: 0, + ParentPath: ",0,", + Avatar: "layui-icon layui-icon-set", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + accountPermission, err := r.Create(ctx, &system.Menu{ + Name: "账户权限", + DisplayName: "账户权限", + Url: uuid.Must(uuid.NewRandom()).String(), + Type: "node", + ParentID: sys.ID, + ParentPath: fmt.Sprintf("%s%d,", sys.ParentPath, sys.ID), + Avatar: "layui-icon layui-icon-vercode", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + systemMenu, err := r.Create(ctx, &system.Menu{ + Name: "菜单管理", + DisplayName: "菜单管理", + Url: "/system/menu/list", + Type: "menu", + ParentID: accountPermission.ID, + ParentPath: fmt.Sprintf("%s%d,", accountPermission.ParentPath, accountPermission.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + systemRole, err := r.Create(ctx, &system.Menu{ + Name: "角色管理", + DisplayName: "角色管理", + Url: "/system/role/list", + Type: "menu", + ParentID: accountPermission.ID, + ParentPath: fmt.Sprintf("%s%d,", accountPermission.ParentPath, accountPermission.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + systemDepartment, err := r.Create(ctx, &system.Menu{ + Name: "部门管理", + DisplayName: "部门管理", + Url: "/system/department/list", + Type: "menu", + ParentID: accountPermission.ID, + ParentPath: fmt.Sprintf("%s%d,", accountPermission.ParentPath, accountPermission.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + systemUser, err := r.Create(ctx, &system.Menu{ + Name: "用户管理", + DisplayName: "用户管理", + Url: "/system/user/list", + Type: "menu", + ParentID: accountPermission.ID, + ParentPath: fmt.Sprintf("%s%d,", accountPermission.ParentPath, accountPermission.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "登陆日志", + DisplayName: "登陆日志", + Url: "/system/login_log/list", + Type: "menu", + ParentID: accountPermission.ID, + ParentPath: fmt.Sprintf("%s%d,", accountPermission.ParentPath, accountPermission.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "审计日志", + DisplayName: "审计日志", + Url: "/system/audit_log/list", + Type: "menu", + ParentID: accountPermission.ID, + ParentPath: fmt.Sprintf("%s%d,", accountPermission.ParentPath, accountPermission.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // 菜单 + _, err = r.Create(ctx, &system.Menu{ + Name: "新增", + DisplayName: "新增", + Url: "/system/menu/add", + Type: "btn", + ParentID: systemMenu.ID, + ParentPath: fmt.Sprintf("%s%d,", systemMenu.ParentPath, systemMenu.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "新增子菜单", + DisplayName: "新增子菜单", + Url: "/system/menu/add_children", + Type: "btn", + ParentID: systemMenu.ID, + ParentPath: fmt.Sprintf("%s%d,", systemMenu.ParentPath, systemMenu.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "编辑", + DisplayName: "编辑", + Url: "/system/menu/edit", + Type: "btn", + ParentID: systemMenu.ID, + ParentPath: fmt.Sprintf("%s%d,", systemMenu.ParentPath, systemMenu.ID), + Avatar: "layui-icon layui-icon-edit", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "保存", + DisplayName: "保存", + Url: "/system/menu/save", + Type: "btn", + ParentID: systemMenu.ID, + ParentPath: fmt.Sprintf("%s%d,", systemMenu.ParentPath, systemMenu.ID), + Avatar: "layui-icon layui-icon-ok", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "数据", + DisplayName: "数据", + Url: "/system/menu/data", + Type: "btn", + ParentID: systemMenu.ID, + ParentPath: fmt.Sprintf("%s%d,", systemMenu.ParentPath, systemMenu.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "刷新", + DisplayName: "刷新", + Url: "/system/menu/refresh_cache", + Type: "btn", + ParentID: systemMenu.ID, + ParentPath: fmt.Sprintf("%s%d,", systemMenu.ParentPath, systemMenu.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // 角色 + _, err = r.Create(ctx, &system.Menu{ + Name: "新增", + DisplayName: "新增", + Url: "/system/role/add", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "编辑", + DisplayName: "编辑", + Url: "/system/role/edit", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-edit", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "保存", + DisplayName: "保存", + Url: "/system/role/save", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-ok", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "数据", + DisplayName: "数据", + Url: "/system/role/data", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "刷新", + DisplayName: "刷新", + Url: "/system/role/refresh", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "重建父路径", + DisplayName: "重建父路径", + Url: "/system/role/rebuild_parent_path", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-danger pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "权限刷新", + DisplayName: "权限刷新", + Url: "/system/role/refresh_role_menus", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "设置权限", + DisplayName: "设置权限", + Url: "/system/role/set_menu", + Type: "btn", + ParentID: systemRole.ID, + ParentPath: fmt.Sprintf("%s%d,", systemRole.ParentPath, systemRole.ID), + Avatar: "layui-icon layui-icon-set", + Style: "pear-btn-danger pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // 部门 + _, err = r.Create(ctx, &system.Menu{ + Name: "新增", + DisplayName: "新增", + Url: "/system/department/add", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "新增子部门", + DisplayName: "新增子部门", + Url: "/system/department/add_children", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "编辑", + DisplayName: "编辑", + Url: "/system/department/edit", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "layui-icon layui-icon-edit", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "保存", + DisplayName: "保存", + Url: "/system/department/save", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "layui-icon layui-icon-ok", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "数据", + DisplayName: "数据", + Url: "/system/department/data", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "刷新", + DisplayName: "刷新", + Url: "/system/department/refresh", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "重建父路径", + DisplayName: "重建父路径", + Url: "/system/department/rebuild_parent_path", + Type: "btn", + ParentID: systemDepartment.ID, + ParentPath: fmt.Sprintf("%s%d,", systemDepartment.ParentPath, systemDepartment.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-danger pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // 用户 + _, err = r.Create(ctx, &system.Menu{ + Name: "新增", + DisplayName: "新增", + Url: "/system/user/add", + Type: "btn", + ParentID: systemUser.ID, + ParentPath: fmt.Sprintf("%s%d,", systemUser.ParentPath, systemUser.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "编辑", + DisplayName: "编辑", + Url: "/system/user/edit", + Type: "btn", + ParentID: systemUser.ID, + ParentPath: fmt.Sprintf("%s%d,", systemUser.ParentPath, systemUser.ID), + Avatar: "layui-icon layui-icon-edit", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "基本资料", + DisplayName: "基本资料", + Url: "/system/user/profile", + Type: "btn", + ParentID: systemUser.ID, + ParentPath: fmt.Sprintf("%s%d,", systemUser.ParentPath, systemUser.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "保存", + DisplayName: "保存", + Url: "/system/user/save", + Type: "btn", + ParentID: systemUser.ID, + ParentPath: fmt.Sprintf("%s%d,", systemUser.ParentPath, systemUser.ID), + Avatar: "layui-icon layui-icon-ok", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // 基础数据 + basicData, err := r.Create(ctx, &system.Menu{ + Name: "基础数据", + DisplayName: "基础数据", + Url: uuid.Must(uuid.NewRandom()).String(), + Type: "node", + ParentID: sys.ID, + ParentPath: fmt.Sprintf("%s%d,", sys.ParentPath, sys.ID), + Avatar: "layui-icon layui-icon-vercode", + Style: "", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + // 系统配置 + systemConfig, err := r.Create(ctx, &system.Menu{ + Name: "系统属性", + DisplayName: "系统属性", + Url: "/system/config/list", + Type: "menu", + ParentID: basicData.ID, + ParentPath: fmt.Sprintf("%s%d,", basicData.ParentPath, basicData.ID), + Avatar: "", + Style: "", + Visible: true, + IsList: true, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "新增", + DisplayName: "新增", + Url: "/system/config/add", + Type: "btn", + ParentID: systemConfig.ID, + ParentPath: fmt.Sprintf("%s%d,", systemConfig.ParentPath, systemConfig.ID), + Avatar: "layui-icon layui-icon-add-1", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "编辑", + DisplayName: "编辑", + Url: "/system/config/edit", + Type: "btn", + ParentID: systemConfig.ID, + ParentPath: fmt.Sprintf("%s%d,", systemConfig.ParentPath, systemConfig.ID), + Avatar: "layui-icon layui-icon-edit", + Style: "pear-btn-primary pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "保存", + DisplayName: "保存", + Url: "/system/config/save", + Type: "btn", + ParentID: systemConfig.ID, + ParentPath: fmt.Sprintf("%s%d,", systemConfig.ParentPath, systemConfig.ID), + Avatar: "layui-icon layui-icon-ok", + Style: "pear-btn-primary pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "重置Pear", + DisplayName: "重置Pear", + Url: "/system/config/reset_pear", + Type: "btn", + ParentID: systemConfig.ID, + ParentPath: fmt.Sprintf("%s%d,", systemConfig.ParentPath, systemConfig.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-danger pear-btn-sm", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + + _, err = r.Create(ctx, &system.Menu{ + Name: "刷新", + DisplayName: "刷新", + Url: "/system/config/refresh", + Type: "btn", + ParentID: systemConfig.ID, + ParentPath: fmt.Sprintf("%s%d,", systemConfig.ParentPath, systemConfig.ID), + Avatar: "layui-icon layui-icon-refresh", + Style: "pear-btn-xs", + Visible: true, + IsList: false, + Status: 0, + Sort: 6666, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + if err != nil { + return err + } + return nil +} diff --git a/internal/erpserver/repository/system/role.go b/internal/erpserver/repository/system/role.go index f6f0436..536defd 100644 --- a/internal/erpserver/repository/system/role.go +++ b/internal/erpserver/repository/system/role.go @@ -2,6 +2,8 @@ package system import ( "context" + "fmt" + "time" "management/internal/erpserver/model/dto" "management/internal/erpserver/model/system" @@ -18,6 +20,50 @@ func NewRoleRepository(repo *repository.Repository) system.RoleRepository { } } +func (r *roleRepository) Initialize(ctx context.Context) (*system.Role, error) { + var count int64 + if err := r.repo.DB(ctx).Model(&system.Role{}).Count(&count).Error; err != nil { + return nil, err + } + if count == 0 { + obj := system.Role{ + Name: "Company", + DisplayName: "公司", + Vip: false, + ParentID: 0, + ParentPath: ",0,", + Status: 0, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if err := r.Create(ctx, &obj); err != nil { + return nil, err + } + + obj1 := system.Role{ + Name: "SuperAdmin", + DisplayName: "超级管理员", + Vip: true, + ParentID: obj.ID, + ParentPath: fmt.Sprintf(",0,%d,", obj.ID), + Status: 0, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if err := r.Create(ctx, &obj1); err != nil { + return nil, err + } + return &obj1, nil + } + + var role system.Role + err := r.repo.DB(ctx).Where("vip = ?", true).First(&role).Error + if err != nil { + return nil, err + } + return &role, nil +} + func (r *roleRepository) Create(ctx context.Context, obj *system.Role) error { return r.repo.DB(ctx).Create(obj).Error } diff --git a/internal/erpserver/repository/system/user.go b/internal/erpserver/repository/system/user.go index 7d57f53..16160b2 100644 --- a/internal/erpserver/repository/system/user.go +++ b/internal/erpserver/repository/system/user.go @@ -2,10 +2,15 @@ package system import ( "context" + "time" "management/internal/erpserver/model/dto" "management/internal/erpserver/model/system" "management/internal/erpserver/repository" + "management/internal/pkg/crypto" + "management/internal/pkg/rand" + + "github.com/google/uuid" ) type userRepository struct { @@ -18,6 +23,49 @@ func NewUserRepository(repo *repository.Repository) system.UserRepository { } } +func (s *userRepository) Initialize(ctx context.Context, departId, roleId int32) error { + var count int64 + if err := s.repo.DB(ctx).Model(&system.User{}).Count(&count).Error; err != nil { + return err + } + if count == 0 { + salt, err := rand.String(10) + if err != nil { + return err + } + + password := "secret" + hashedPassword, err := crypto.BcryptHashPassword(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: "1185230223@qq.com", + Username: "kenneth", + HashedPassword: hashedPassword, + Salt: salt, + Avatar: "/statics/admin/images/avatar.jpg", + Gender: 1, + DepartmentID: departId, + RoleID: roleId, + Status: 0, + ChangePasswordAt: initTime, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + return s.Create(ctx, &user) + } + + return nil +} + func (s *userRepository) Create(ctx context.Context, obj *system.User) error { return s.repo.DB(ctx).Create(obj).Error } diff --git a/internal/erpserver/service/v1/service.go b/internal/erpserver/service/v1/service.go index 12fe2a4..8b6eb9e 100644 --- a/internal/erpserver/service/v1/service.go +++ b/internal/erpserver/service/v1/service.go @@ -2,7 +2,6 @@ package v1 import ( "context" - "time" "management/internal/erpserver/model/dto" "management/internal/erpserver/model/form" @@ -67,7 +66,7 @@ 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 + LoginTime(ctx context.Context, email string) (dto.LoginTimeDto, error) LoginCount(ctx context.Context, email string) int64 } diff --git a/internal/erpserver/service/v1/system/config.go b/internal/erpserver/service/v1/system/config.go index 39c2dfc..8c15d57 100644 --- a/internal/erpserver/service/v1/system/config.go +++ b/internal/erpserver/service/v1/system/config.go @@ -48,7 +48,17 @@ func (s *configService) Pear(ctx context.Context) (*dto.PearConfig, error) { var res *dto.PearConfig key := know.GetManageKey(ctx, know.PearAdmin) err := util.GetOrSetCache(ctx, s.Redis, key, util.GetCacheExpire(), func() (any, error) { - return s.repo.GetByKey(ctx, pearadmin.PearKey) + conf, err := s.repo.GetByKey(ctx, pearadmin.PearKey) + if err != nil { + return nil, err + } + + var pc dto.PearConfig + err = json.Unmarshal(conf.Value, &pc) + if err != nil { + return nil, err + } + return &pc, nil }, &res) return res, err } @@ -72,7 +82,7 @@ func (s *configService) ResetPear(ctx context.Context) error { // create conf = &system.Config{ Key: pearadmin.PearKey, - Value: string(b), + Value: b, } err = s.Create(ctx, conf) if err != nil { @@ -80,7 +90,7 @@ func (s *configService) ResetPear(ctx context.Context) error { } } else { // update - conf.Value = string(b) + conf.Value = b conf.UpdatedAt = time.Now() err = s.Update(ctx, conf) if err != nil { diff --git a/internal/erpserver/service/v1/system/login_log.go b/internal/erpserver/service/v1/system/login_log.go index a9dfe83..ffa06b4 100644 --- a/internal/erpserver/service/v1/system/login_log.go +++ b/internal/erpserver/service/v1/system/login_log.go @@ -2,7 +2,6 @@ package system import ( "context" - "time" "management/internal/erpserver/model/dto" "management/internal/erpserver/model/system" @@ -29,12 +28,20 @@ func (s *loginLogService) List(ctx context.Context, q dto.SearchDto) ([]*system. return s.repo.List(ctx, q) } -func (s *loginLogService) LoginLatestTime(ctx context.Context, email string) time.Time { - log, err := s.repo.GetLatest(ctx, email) +func (s *loginLogService) LoginTime(ctx context.Context, email string) (dto.LoginTimeDto, error) { + var res dto.LoginTimeDto + logs, err := s.repo.GetLatest(ctx, email) if err != nil { - return time.Time{} + return res, err } - return log.CreatedAt + if len(logs) == 2 { + res.ThisLoginTime = logs[0].CreatedAt + res.LastLoginTime = logs[1].CreatedAt + } else if len(logs) == 1 { + res.ThisLoginTime = logs[0].CreatedAt + } + + return res, nil } func (s *loginLogService) LoginCount(ctx context.Context, email string) int64 { diff --git a/internal/erpserver/service/v1/system/menu.go b/internal/erpserver/service/v1/system/menu.go index 33f15a3..4df9e74 100644 --- a/internal/erpserver/service/v1/system/menu.go +++ b/internal/erpserver/service/v1/system/menu.go @@ -36,7 +36,8 @@ func NewMenuService( } func (s *menuService) Create(ctx context.Context, req *system.Menu) error { - return s.repo.Create(ctx, req) + _, err := s.repo.Create(ctx, req) + return err } func (s *menuService) Update(ctx context.Context, req *system.Menu) error { diff --git a/internal/erpserver/wire.go b/internal/erpserver/wire.go index d4f023b..fbf24c5 100644 --- a/internal/erpserver/wire.go +++ b/internal/erpserver/wire.go @@ -18,7 +18,6 @@ import ( "management/internal/pkg/session" "github.com/drhin/logger" - "github.com/go-chi/chi/v5" "github.com/google/wire" ) @@ -65,9 +64,10 @@ var handlerSet = wire.NewSet( var serverSet = wire.NewSet( NewHTTPServer, + NewApp, ) -func NewWire(*config.Config, *logger.Logger) (*chi.Mux, func(), error) { +func NewWire(*config.Config, *logger.Logger) (App, func(), error) { panic(wire.Build( repositorySet, redis.New, diff --git a/internal/erpserver/wire_gen.go b/internal/erpserver/wire_gen.go index abf969f..e41d472 100644 --- a/internal/erpserver/wire_gen.go +++ b/internal/erpserver/wire_gen.go @@ -8,7 +8,6 @@ package erpserver import ( "github.com/drhin/logger" - "github.com/go-chi/chi/v5" "github.com/google/wire" "management/internal/erpserver/handler" common2 "management/internal/erpserver/handler/common" @@ -26,51 +25,51 @@ import ( // Injectors from wire.go: -func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (*chi.Mux, func(), error) { +func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (App, func(), error) { db, cleanup, err := repository.NewDB(loggerLogger, configConfig) if err != nil { - return nil, nil, err + return App{}, nil, err } + repositoryRepository := repository.NewRepository(db, loggerLogger) + userRepository := system.NewUserRepository(repositoryRepository) + roleRepository := system.NewRoleRepository(repositoryRepository) + menuRepository := system.NewMenuRepository(repositoryRepository) + roleMenuRepository := system.NewRoleMenuRepository(repositoryRepository) + departmentRepository := system.NewDepartmentRepository(repositoryRepository) + configRepository := system.NewConfigRepository(repositoryRepository) + loginLogRepository := system.NewLoginLogRepository(repositoryRepository) + auditLogRepository := system.NewAuditLogRepository(repositoryRepository) manager, err := session.NewSCSManager(db, configConfig) if err != nil { cleanup() - return nil, nil, err + return App{}, nil, err } - repositoryRepository := repository.NewRepository(db, loggerLogger) transaction := repository.NewTransaction(repositoryRepository) cache, cleanup2, err := redis.New(configConfig, loggerLogger) if err != nil { cleanup() - return nil, nil, err + return App{}, nil, err } service := v1.NewService(loggerLogger, transaction, manager, cache) - menuRepository := system.NewMenuRepository(repositoryRepository) - roleRepository := system.NewRoleRepository(repositoryRepository) roleService := system2.NewRoleService(service, roleRepository) - roleMenuRepository := system.NewRoleMenuRepository(repositoryRepository) roleMenuService := system2.NewRoleMenuService(service, roleMenuRepository) menuService := system2.NewMenuService(service, menuRepository, roleService, roleMenuService) - auditLogRepository := system.NewAuditLogRepository(repositoryRepository) auditLogService := system2.NewAuditLogService(service, auditLogRepository) renderRender, err := render.New(manager, menuService) if err != nil { cleanup2() cleanup() - return nil, nil, err + return App{}, nil, err } handlerHandler := handler.NewHandler(configConfig, loggerLogger, manager, renderRender) captchaService := common.NewCaptchaService() captchaHandler := common2.NewCaptchaHandler(handlerHandler, captchaService) uploadHandler := common2.NewUploadHandler(handlerHandler) - configRepository := system.NewConfigRepository(repositoryRepository) configService := system2.NewConfigService(service, configRepository) configHandler := system3.NewConfigHandler(handlerHandler, configService) - userRepository := system.NewUserRepository(repositoryRepository) - loginLogRepository := system.NewLoginLogRepository(repositoryRepository) loginLogService := system2.NewLoginLogService(service, loginLogRepository) userService := system2.NewUserService(service, userRepository, roleService, loginLogService) homeHandler := system3.NewHomeHandler(handlerHandler, userService, loginLogService) - departmentRepository := system.NewDepartmentRepository(repositoryRepository) departmentService := system2.NewDepartmentService(service, departmentRepository) userHandler := system3.NewUserHandler(handlerHandler, captchaService, userService, roleService, departmentService) loginLogHandler := system3.NewLoginLogHandler(handlerHandler, loginLogService) @@ -79,7 +78,8 @@ func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (*chi.Mux roleHandler := system3.NewRoleHandler(handlerHandler, roleService, menuService) departmentHandler := system3.NewDepartmentHandler(handlerHandler, departmentService) mux := NewHTTPServer(manager, loggerLogger, menuService, auditLogService, captchaHandler, uploadHandler, configHandler, homeHandler, userHandler, loginLogHandler, auditHandler, menuHandler, roleHandler, departmentHandler) - return mux, func() { + app := NewApp(userRepository, roleRepository, menuRepository, roleMenuRepository, departmentRepository, configRepository, loginLogRepository, auditLogRepository, mux) + return app, func() { cleanup2() cleanup() }, nil @@ -95,4 +95,5 @@ var handlerSet = wire.NewSet(handler.NewHandler, common2.NewCaptchaHandler, comm var serverSet = wire.NewSet( NewHTTPServer, + NewApp, ) diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 6155a72..639ad3f 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -42,14 +42,17 @@ type Config struct { Prod bool `mapstructure:"prod"` // 是否正式 } `mapstructure:"app"` DB struct { - Driver string `mapstructure:"driver"` // 数据库类型 - Host string `mapstructure:"host"` // 数据库地址 - Port int `mapstructure:"port"` // 数据库端口 - Username string `mapstructure:"username"` // 数据库用户 - Password string `mapstructure:"password"` // 数据库密码 - DBName string `mapstructure:"db_name"` // 数据库名称 - MaxOpenConns int `mapstructure:"max_open_conns"` // 数据库名称 - MaxIdleConns int `mapstructure:"max_idle_conns"` // 数据库名称 + Driver string `mapstructure:"driver"` // 数据库类型 + Host string `mapstructure:"host"` // 数据库地址 + Port int `mapstructure:"port"` // 数据库端口 + Username string `mapstructure:"username"` // 数据库用户 + Password string `mapstructure:"password"` // 数据库密码 + DBName string `mapstructure:"db_name"` // 数据库名称 + MaxIdleConns int `mapstructure:"max_idle_conns"` // 最大空闲连接数 + MaxOpenConns int `mapstructure:"max_open_conns"` // 最大打开连接数 + ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` // 连接最大存活时间 + ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间 + LogMode bool `mapstructure:"log_mode"` // 是否开启日志 } `mapstructure:"db"` Redis struct { Host string `mapstructure:"host"` // redis地址 diff --git a/internal/pkg/database/postgresql.go b/internal/pkg/database/postgresql.go index c3e0df7..4caefff 100644 --- a/internal/pkg/database/postgresql.go +++ b/internal/pkg/database/postgresql.go @@ -25,10 +25,10 @@ type PostgreSQLOptions struct { // DSN return DSN from PostgreSQLOptions. func (o *PostgreSQLOptions) DSN() string { - splited := strings.Split(o.Addr, ":") - host, port := splited[0], "5432" - if len(splited) > 1 { - port = splited[1] + split := strings.Split(o.Addr, ":") + host, port := split[0], "5432" + if len(split) > 1 { + port = split[1] } return fmt.Sprintf(`user=%s password=%s host=%s port=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai`, diff --git a/internal/pkg/middleware/audit.go b/internal/pkg/middleware/audit.go index e74d15c..0683df4 100644 --- a/internal/pkg/middleware/audit.go +++ b/internal/pkg/middleware/audit.go @@ -1,6 +1,7 @@ package middleware import ( + "context" "errors" "net/http" "time" @@ -18,19 +19,24 @@ func Audit(sess session.Manager, auditLogService v1.AuditLogService, log *logger return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() + + // 提前获取用户信息(同步操作) + user, err := sess.GetUser(r.Context(), know.StoreName) + if err != nil { + log.Error("获取用户会话失败", err) + next.ServeHTTP(w, r) // 继续处理请求 + return + } + defer func() { go func() { - ctx := r.Context() - user, err := sess.GetUser(ctx, know.StoreName) - if err != nil { - log.Error(err.Error(), err) + if user.ID == 0 { + log.Error("用户信息为空", errors.New("scs get user is empty")) return } - if user.ID == 0 { - log.Error("scs get user is empty", errors.New("scs get user is empty")) - return - } + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() al := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now()) if err := auditLogService.Create(ctx, al); err != nil { diff --git a/internal/pkg/middleware/authorize.go b/internal/pkg/middleware/authorize.go index 14e3aa4..984237f 100644 --- a/internal/pkg/middleware/authorize.go +++ b/internal/pkg/middleware/authorize.go @@ -17,6 +17,7 @@ var publicRoutes = map[string]bool{ "/upload/file": true, "/upload/multi_files": true, "/pear.json": true, + "/logout": true, } func Authorize( diff --git a/internal/pkg/render/funcs/method.go b/internal/pkg/render/funcs/method.go index a5c989a..36bb387 100644 --- a/internal/pkg/render/funcs/method.go +++ b/internal/pkg/render/funcs/method.go @@ -57,5 +57,9 @@ func Methods() map[string]any { return string(b) } + res["add"] = func(n1, n2 int64) int64 { + return n1 + n2 + } + return res } diff --git a/modd.conf b/modd.conf index d2b1af5..324beef 100644 --- a/modd.conf +++ b/modd.conf @@ -1,5 +1,5 @@ # start manage **/*.go **/**/**/*.tmpl **/**/**/**/*.tmpl !web/*.go !**/*_test.go { prep: go build -o ./management . - daemon +sigterm: ./management erp -c config.dev.yaml + daemon +sigterm: ./management erp -c configs/config.dev.yaml } \ No newline at end of file diff --git a/web/templates/manage/home/dashboard.tmpl b/web/templates/manage/home/dashboard.tmpl index 4b4cb12..38617de 100644 --- a/web/templates/manage/home/dashboard.tmpl +++ b/web/templates/manage/home/dashboard.tmpl @@ -6,7 +6,12 @@
欢迎您,{{.Auth.Username}} ({{.Auth.RoleName}})
-这是您第 {{.LoginCount}} 次登录,上次登录日期:{{dateFormat .LastLoginTime}},如果不是您本人登录,请及时修改密码 。
+ {{if eq .LoginCount 1}} +这是您第 1 次登录,本次登录日期:{{dateFormat .LoginTime.ThisLoginTime}},如果不是您本人登录,请及时修改密码 。
+ {{else}} +这是您第 {{.LoginCount}} 次登录,本次登录日期:{{dateFormat .LoginTime.ThisLoginTime}}, + 上次登录日期:{{dateFormat .LoginTime.LastLoginTime}},如果不是您本人登录,请及时修改密码 。
+ {{end}}