This commit is contained in:
2025-06-18 17:44:49 +08:00
parent b171122a32
commit 0878a4e6de
66 changed files with 2841 additions and 1423 deletions

View File

@@ -20,6 +20,7 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
@@ -31,6 +32,7 @@ type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
CaptchaService v1.CaptchaService
UserService v1.UserService
RoleService v1.RoleService
@@ -56,8 +58,8 @@ func WebApp(cfg Config) http.Handler {
app.Handle("/public/*", http.StripPrefix("/public", uploadServer))
app.Group(func(r chi.Router) {
r.Use(mid.NoSurf) // CSRF
r.Use(mid.NoSurfContext) // CSRF Store Context
//r.Use(mid.NoSurf) // CSRF
//r.Use(mid.NoSurfContext) // CSRF Store Context
r.Use(mid.LoadSession(cfg.Sm)) // Session
captcha.Routes(r, captcha.Config{
@@ -67,11 +69,12 @@ func WebApp(cfg Config) http.Handler {
})
upload.Routes(r, upload.Config{
Conf: cfg.Conf,
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
MenuService: cfg.MenuService,
Conf: cfg.Conf,
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
TaskDistributor: cfg.TaskDistributor,
MenuService: cfg.MenuService,
})
home.Routes(r, home.Config{
@@ -92,13 +95,14 @@ func WebApp(cfg Config) http.Handler {
})
r.Route("/system", func(r chi.Router) {
r.Use(mid.Authorize(cfg.Sm, cfg.MenuService))
user.Routes(r, user.Config{
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
TaskDistributor: cfg.TaskDistributor,
UserService: cfg.UserService,
RoleService: cfg.RoleService,
MenuService: cfg.MenuService,
@@ -106,18 +110,20 @@ func WebApp(cfg Config) http.Handler {
})
role.Routes(r, role.Config{
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
RoleService: cfg.RoleService,
MenuService: cfg.MenuService,
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
TaskDistributor: cfg.TaskDistributor,
RoleService: cfg.RoleService,
MenuService: cfg.MenuService,
})
menu.Routes(r, menu.Config{
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
MenuService: cfg.MenuService,
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
TaskDistributor: cfg.TaskDistributor,
MenuService: cfg.MenuService,
})
department.Routes(r, department.Config{
@@ -129,11 +135,12 @@ func WebApp(cfg Config) http.Handler {
})
configapp.Routes(r, configapp.Config{
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
MenuService: cfg.MenuService,
ConfigService: cfg.ConfigService,
Log: cfg.Log,
Sm: cfg.Sm,
Render: cfg.Render,
TaskDistributor: cfg.TaskDistributor,
MenuService: cfg.MenuService,
ConfigService: cfg.ConfigService,
})
loginlog.Routes(r, loginlog.Config{

View File

@@ -5,17 +5,19 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
)
type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
MenuService v1.MenuService
ConfigService v1.ConfigService
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
MenuService v1.MenuService
ConfigService v1.ConfigService
}
func Routes(r chi.Router, cfg Config) {
@@ -23,7 +25,7 @@ func Routes(r chi.Router, cfg Config) {
r.Get("/pear.json", app.pear)
r.Route("/config", func(r chi.Router) {
r.Use(mid.Audit(cfg.Sm, cfg.Log))
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Get("/list", app.list)
r.Post("/list", app.list)
r.Get("/add", app.add)

View File

@@ -5,6 +5,7 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
@@ -14,6 +15,7 @@ type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
MenuService v1.MenuService
DepartmentService v1.DepartmentService
}
@@ -22,7 +24,7 @@ func Routes(r chi.Router, cfg Config) {
app := newApp(cfg.Render, cfg.DepartmentService)
r.Route("/department", func(r chi.Router) {
r.Use(mid.Audit(cfg.Sm, cfg.Log))
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Get("/list", app.list)
r.Post("/list", app.list)
r.Get("/add", app.add)

View File

@@ -5,16 +5,18 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
)
type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
MenuService v1.MenuService
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
MenuService v1.MenuService
}
func Routes(r chi.Router, cfg Config) {
@@ -22,7 +24,7 @@ func Routes(r chi.Router, cfg Config) {
r.Get("/menus", app.menus)
r.Route("/menu", func(r chi.Router) {
r.Use(mid.Audit(cfg.Sm, cfg.Log))
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Get("/list", app.list)
r.Post("/list", app.list)
r.Get("/add", app.add)

View File

@@ -5,24 +5,26 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
)
type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
RoleService v1.RoleService
MenuService v1.MenuService
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
RoleService v1.RoleService
MenuService v1.MenuService
}
func Routes(r chi.Router, cfg Config) {
app := newApp(cfg.Render, cfg.RoleService, cfg.MenuService)
r.Route("/role", func(r chi.Router) {
r.Use(mid.Audit(cfg.Sm, cfg.Log))
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Get("/list", app.list)
r.Post("/list", app.list)
r.Get("/add", app.add)

View File

@@ -5,6 +5,7 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
@@ -14,6 +15,7 @@ type Config struct {
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
UserService v1.UserService
RoleService v1.RoleService
MenuService v1.MenuService
@@ -31,7 +33,7 @@ func Routes(r chi.Router, cfg Config) {
)
r.Route("/user", func(r chi.Router) {
r.Use(mid.Audit(cfg.Sm, cfg.Log))
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Get("/list", app.list)
r.Post("/list", app.list)
r.Get("/add", app.add)

View File

@@ -6,17 +6,19 @@ import (
"management/internal/pkg/mid"
"management/internal/pkg/render"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/go-chi/chi/v5"
)
type Config struct {
Conf *config.Config
Log *logger.Logger
Sm session.Manager
Render render.Renderer
MenuService v1.MenuService
Conf *config.Config
Log *logger.Logger
Sm session.Manager
Render render.Renderer
TaskDistributor tasks.TaskDistributor
MenuService v1.MenuService
}
func Routes(r chi.Router, cfg Config) {
@@ -24,9 +26,9 @@ func Routes(r chi.Router, cfg Config) {
r.Route("/upload", func(r chi.Router) {
r.Use(mid.Authorize(cfg.Sm, cfg.MenuService))
r.Use(mid.Audit(cfg.Sm, cfg.Log))
r.Get("/img", app.img)
r.Get("/file", app.file)
r.Get("/multi_files", app.multiFiles)
r.Use(mid.Audit(cfg.Sm, cfg.Log, cfg.TaskDistributor))
r.Post("/img", app.img)
r.Post("/file", app.file)
r.Post("/multi_files", app.multiFiles)
})
}

View File

@@ -13,28 +13,25 @@ import (
type AuditLogRepository interface {
Create(ctx context.Context, obj *AuditLog) error
BatchCreate(ctx context.Context, objs []*AuditLog) error
List(ctx context.Context, q dto.SearchDto) ([]*AuditLog, int64, error)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, q dto.SearchDto) ([]*AuditLog, 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"
ID int64 `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Email string `db:"email" json:"email"`
StartAt time.Time `db:"start_at" json:"start_at"`
EndAt time.Time `db:"end_at" json:"end_at"`
Duration string `db:"duration" json:"duration"`
Url string `db:"url" json:"url"`
Method string `db:"method" json:"method"`
Parameters string `db:"parameters" json:"parameters"`
RefererUrl string `db:"referer_url" json:"referer_url"`
Os string `db:"os" json:"os"`
Ip string `db:"ip" json:"ip"`
Browser string `db:"browser" json:"browser"`
Remark string `db:"remark" json:"remark"`
}
func NewAuditLog(r *http.Request, email, os, browser string, start, end time.Time) *AuditLog {

View File

@@ -1,10 +0,0 @@
package system
type Category struct {
ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"`
Name string `json:"name" gorm:"type:varchar(200);not null;uniqueIndex"`
}
func (Category) TableName() string {
return "categories"
}

View File

@@ -15,18 +15,14 @@ type ConfigRepository interface {
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)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, filter dto.SearchDto) ([]*Config, 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 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 {
return "sys_config"
ID int32 `db:"id" json:"id"`
Key string `db:"key" json:"key"`
Value datatypes.JSON `db:"value" json:"value"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -13,21 +13,18 @@ type DepartmentRepository interface {
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)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, filter dto.SearchDto) ([]*Department, 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"
ID int32 `db:"id" json:"id"`
Name string `db:"name" json:"name"`
ParentID int32 `db:"parent_id" json:"parent_id"`
ParentPath string `db:"parent_path" json:"parent_path"`
Status int32 `db:"status" json:"status"`
Sort int32 `db:"sort" json:"sort"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -10,25 +10,21 @@ import (
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)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, filter dto.SearchDto) ([]*LoginLog, 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"
ID int64 `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Email string `db:"email" json:"email"`
IsSuccess bool `db:"is_success" json:"is_success"`
Message string `db:"message" json:"message"`
RefererUrl string `db:"referer_url" json:"referer_url"`
Url string `db:"url" json:"url"`
Os string `db:"os" json:"os"`
Ip string `db:"ip" json:"ip"`
Browser string `db:"browser" json:"browser"`
}
func NewLoginLog(email, os, ip, browser, url, referer string) *LoginLog {

View File

@@ -12,27 +12,24 @@ type MenuRepository interface {
Get(ctx context.Context, id int32) (*Menu, error)
GetByUrl(ctx context.Context, url string) (*Menu, error)
All(ctx context.Context) ([]*Menu, error)
Count(ctx context.Context) (int64, 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"
ID int32 `db:"id" json:"id"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
Url string `db:"url" json:"url"`
Type string `db:"type" json:"type"`
ParentID int32 `db:"parent_id" json:"parent_id"`
ParentPath string `db:"parent_path" json:"parent_path"`
Avatar string `db:"avatar" json:"avatar"`
Style string `db:"style" json:"style"`
Visible bool `db:"visible" json:"visible"`
IsList bool `db:"is_list" json:"is_list"`
Status int32 `db:"status" json:"status"`
Sort int32 `db:"sort" json:"sort"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -12,24 +12,22 @@ 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)
GetByVip(ctx context.Context, vip bool) (*Role, error)
All(ctx context.Context) ([]*Role, error)
List(ctx context.Context, q dto.SearchDto) ([]*Role, int64, error)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, filter dto.SearchDto) ([]*Role, 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"
ID int32 `db:"id" json:"id"`
Name string `db:"name" json:"name"`
DisplayName string `db:"display_name" json:"display_name"`
ParentID int32 `db:"parent_id" json:"parent_id"`
ParentPath string `db:"parent_path" json:"parent_path"`
Vip bool `db:"vip" json:"-"`
Status int32 `db:"status" json:"status"`
Sort int32 `db:"sort" json:"sort"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

View File

@@ -9,10 +9,6 @@ type RoleMenuRepository interface {
}
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"
RoleID int32 `db:"role_id" json:"role_id"`
MenuID int32 `db:"menu_id" json:"menu_id"`
}

View File

@@ -16,29 +16,26 @@ type UserRepository interface {
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)
Count(ctx context.Context, filter dto.SearchDto) (int64, error)
List(ctx context.Context, filter dto.SearchDto) ([]*User, error)
}
type User struct {
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';"`
ID int32 `db:"id" json:"id"`
Uuid uuid.UUID `db:"uuid" json:"uuid"`
Email string `db:"email" json:"email"`
Username string `db:"username" json:"username"`
HashedPassword []byte `db:"hashed_password" json:"-"`
Salt string `db:"salt" json:"-"`
Avatar string `db:"avatar" json:"avatar"`
Gender int32 `db:"gender" json:"gender"`
DepartmentID int32 `db:"department_id" json:"department_id"`
RoleID int32 `db:"role_id" json:"role_id"`
Status int32 `db:"status" json:"status"`
ChangePasswordAt time.Time `db:"change_password_at" json:"-"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Role *Role `json:"role" gorm:"ForeignKey:RoleID"`
Department *Department `json:"department" gorm:"ForeignKey:DepartmentID"`
}
func (User) TableName() string {
return "sys_user"
Role *Role `json:"role"`
Department *Department `json:"department"`
}

View File

@@ -1,6 +1,6 @@
-- SQL dump generated using DBML (dbml.dbdiagram.io)
-- Database: PostgreSQL
-- Generated at: 2025-06-12T01:21:05.629Z
-- Generated at: 2025-06-18T02:08:22.726Z
CREATE TABLE "sys_user" (
"id" SERIAL NOT NULL,

View File

@@ -2,163 +2,55 @@ package repository
import (
"context"
"fmt"
"time"
"management/internal/pkg/config"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
"gorm.io/driver/postgres"
"gorm.io/gorm"
gl "gorm.io/gorm/logger"
"github.com/jmoiron/sqlx"
)
var core *sqlx.DB
type txCtxKey struct{}
type Repository struct {
db *gorm.DB
logger *logger.Logger
type Store struct {
db sqlx.ExtContext
}
func NewRepository(db *gorm.DB, logger *logger.Logger) *Repository {
return &Repository{
db: db,
logger: logger,
func NewStore(db *sqlx.DB) *Store {
if core == nil {
core = db
}
return &Store{
db: db,
}
}
type Transaction interface {
Transaction(ctx context.Context, fn func(ctx context.Context) error) error
}
func NewTransaction(r *Repository) Transaction {
return r
}
func (r *Repository) DB(ctx context.Context) *gorm.DB {
v := ctx.Value(txCtxKey{})
if v != nil {
if tx, ok := v.(*gorm.DB); ok {
return tx
func (s *Store) DB(ctx context.Context) sqlx.ExtContext {
if tx, ok := ctx.Value(txCtxKey{}).(sqldb.CommitRollbacker); ok {
if res, err := sqldb.GetExtContext(tx); err == nil {
return res
}
}
return r.db.WithContext(ctx)
return s.db
}
func (r *Repository) Transaction(ctx context.Context, fn func(ctx context.Context) error) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctx = context.WithValue(ctx, txCtxKey{}, tx)
return fn(ctx)
})
}
func NewDB(config *config.Config, log *logger.Logger) (*gorm.DB, func(), error) {
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
config.DB.Host,
config.DB.Username,
config.DB.Password,
config.DB.DBName,
config.DB.Port,
)
pgConfig := postgres.Config{
DSN: dsn,
PreferSimpleProtocol: true, // disables implicit prepared statement usage
}
db, err := gorm.Open(postgres.New(pgConfig), &gorm.Config{
Logger: getGormLogger(config),
})
func Transaction(ctx context.Context, log *logger.Logger, fn func(c context.Context) error) error {
beginner := sqldb.NewBeginner(core)
tx, err := beginner.Begin()
if err != nil {
return nil, nil, err
log.Error("begin transaction error", err)
return err
}
// db.Debug 会默认显示日志
//db = db.Debug()
// Connection Pool config
sqlDB, err := db.DB()
if err != nil {
return nil, nil, err
}
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 {
log.Error("sql db close error", err)
ctx = context.WithValue(ctx, txCtxKey{}, tx)
if err := fn(ctx); err != nil {
if err := tx.Rollback(); err != nil {
log.Error("rollback transaction error", err)
return err
}
return err
}
return db, cleanup, nil
return tx.Commit()
}
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 实例.
// 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 是 Store 的具体实现.
//type datastore struct {
// core *gorm.DB
//}
//
//// NewStore 创建一个 Store 类型的实例.
//func NewStore(db *gorm.DB) Store {
// // 确保 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)
// },
// )
//}

View File

@@ -0,0 +1,117 @@
package audit
import (
"bytes"
"context"
"fmt"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.AuditLogRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Create(ctx context.Context, obj *system.AuditLog) error {
//goland:noinspection ALL
const q = `
INSERT INTO sys_audit_log(
email, start_at, end_at, duration, url, method, parameters,
referer_url, os, ip, browser, remark)
VALUES (
:email, :start_at, :end_at, :duration, :url, :method, :parameters,
:referer_url, :os, :ip, :browser, :remark)`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) BatchCreate(ctx context.Context, objs []*system.AuditLog) error {
if len(objs) == 0 {
return nil
}
//goland:noinspection ALL
const q = `
INSERT INTO sys_audit_log(
email, start_at, end_at, duration, url, method, parameters,
referer_url, os, ip, browser, remark)
VALUES (
:email, :start_at, :end_at, :duration, :url, :method, :parameters,
:referer_url, :os, :ip, :browser, :remark)`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, objs)
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_audit_log`
data := map[string]any{}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), buf.String(), data, &count)
if err != nil {
return 0, fmt.Errorf("select count audit: %w", err)
}
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.AuditLog, error) {
//goland:noinspection ALL
const q = `
SELECT
id, created_at, email, start_at, end_at, duration, url,
method, parameters, referer_url, os, ip, browser, remark
FROM
sys_audit_log`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
buf.WriteString(" ORDER BY id DESC")
buf.WriteString(" LIMIT :rows_per_page OFFSET :offset")
var audits []system.AuditLog
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), buf.String(), data, &audits)
if err != nil {
return nil, err
}
return toPointer(audits), nil
}
func toPointer(data []system.AuditLog) []*system.AuditLog {
var res []*system.AuditLog
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -0,0 +1,28 @@
package audit
import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if filter.SearchEmail != "" {
data["email"] = filter.SearchEmail
wc = append(wc, "email LIKE :email")
}
if len(wc) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(wc, " AND "))
}
}

View File

@@ -1,54 +0,0 @@
package system
import (
"context"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
)
type auditLogRepository struct {
repo *repository.Repository
}
func NewAuditLogRepository(repo *repository.Repository) system.AuditLogRepository {
return &auditLogRepository{
repo: repo,
}
}
func (s *auditLogRepository) Create(ctx context.Context, obj *system.AuditLog) error {
return s.repo.DB(ctx).Create(obj).Error
}
func (s *auditLogRepository) BatchCreate(ctx context.Context, objs []*system.AuditLog) error {
return s.repo.DB(ctx).Create(objs).Error
}
func (s *auditLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) {
query := s.repo.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
}

View File

@@ -1,113 +0,0 @@
package system
import (
"context"
"encoding/json"
"fmt"
"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 {
repo *repository.Repository
}
func NewConfigRepository(repo *repository.Repository) system.ConfigRepository {
return &configRepository{
repo: repo,
}
}
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
}
func (r *configRepository) Update(ctx context.Context, obj *system.Config) error {
return r.repo.DB(ctx).Save(obj).Error
}
func (r *configRepository) Get(ctx context.Context, id int32) (*system.Config, error) {
var obj system.Config
err := r.repo.DB(ctx).First(&obj, id).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("config %d not found: %w", id, err)
}
return nil, err
}
return &obj, nil
}
func (r *configRepository) GetByKey(ctx context.Context, key string) (*system.Config, error) {
var obj system.Config
err := r.repo.DB(ctx).Where("key = ?", key).First(&obj).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("config key %s not found: %w", key, err)
}
return nil, err
}
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 {
if database.IsNoRows(err) {
return nil, fmt.Errorf("config key value %s not found: %w", key, err)
}
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{}).
Where("created_at BETWEEN ? AND ?", q.SearchTimeBegin, q.SearchTimeEnd)
var count int64
err := query.Count(&count).Error
if err != nil {
return nil, 0, err
}
var configs []*system.Config
err = query.
Order("id DESC").
Offset((q.Page - 1) * q.Rows).
Limit(q.Rows).
Find(&configs).
Error
if err != nil {
return nil, 0, err
}
return configs, count, nil
}

View File

@@ -0,0 +1,177 @@
package config
import (
"bytes"
"context"
"encoding/json"
"fmt"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/database"
"management/internal/pkg/know/pearadmin"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
"gorm.io/datatypes"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.ConfigRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Initialize(ctx context.Context) error {
_, err := s.GetByKey(ctx, pearadmin.PearKey)
if err != nil {
if database.IsNoRows(err) {
b, e := json.Marshal(pearadmin.PearJson)
if e != nil {
return e
}
return s.Create(ctx, &system.Config{
Key: pearadmin.PearKey,
Value: datatypes.JSON(b),
})
}
return err
}
return nil
}
func (s *store) Create(ctx context.Context, obj *system.Config) error {
//goland:noinspection ALL
const q = `
INSERT INTO sys_config (
key, value
) VALUES (
:key, :value
);`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Update(ctx context.Context, obj *system.Config) error {
const q = `
UPDATE sys_config
SET key = :key,
value = :value,
updated_at = :updated_at
WHERE id = :id;`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Get(ctx context.Context, id int32) (*system.Config, error) {
//goland:noinspection ALL
const q = `
SELECT
id, key, value, created_at, updated_at
FROM
sys_config
WHERE
id = :id;`
data := map[string]any{
"id": id,
}
var config system.Config
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &config)
if err != nil {
return nil, fmt.Errorf("select id config: %w", err)
}
return &config, nil
}
func (s *store) GetByKey(ctx context.Context, key string) (*system.Config, error) {
//goland:noinspection ALL
const q = `
SELECT
id, key, value, created_at, updated_at
FROM
sys_config
WHERE
key = :key;`
data := map[string]any{
"key": key,
}
var config system.Config
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &config)
if err != nil {
return nil, fmt.Errorf("select key config: %w", err)
}
return &config, nil
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_config`
data := map[string]any{}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), buf.String(), data, &count)
if err != nil {
return 0, fmt.Errorf("select count config: %w", err)
}
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.Config, error) {
//goland:noinspection ALL
const q = `
SELECT
id, key, value, created_at, updated_at
FROM
sys_config`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
buf.WriteString(" ORDER BY id DESC")
buf.WriteString(" LIMIT :rows_per_page OFFSET :offset")
var configs []system.Config
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), buf.String(), data, &configs)
if err != nil {
return nil, err
}
return toPointer(configs), nil
}
func toPointer(data []system.Config) []*system.Config {
var res []*system.Config
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -0,0 +1,23 @@
package config
import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if len(wc) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(wc, " AND "))
}
}

View File

@@ -1,124 +0,0 @@
package system
import (
"context"
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/database"
)
type departmentRepository struct {
repo *repository.Repository
}
func NewDepartmentRepository(repo *repository.Repository) system.DepartmentRepository {
return &departmentRepository{
repo: repo,
}
}
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
}
func (r *departmentRepository) Update(ctx context.Context, obj *system.Department) error {
return r.repo.DB(ctx).Save(obj).Error
}
func (r *departmentRepository) Get(ctx context.Context, id int32) (*system.Department, error) {
var obj system.Department
err := r.repo.DB(ctx).First(&obj, id).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("department %d not found: %w", id, err)
}
return nil, err
}
return &obj, nil
}
func (r *departmentRepository) All(ctx context.Context) ([]*system.Department, error) {
var departs []*system.Department
err := r.repo.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.repo.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.repo.DB(ctx).Exec(query).Error
}

View File

@@ -0,0 +1,195 @@
package department
import (
"bytes"
"context"
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.DepartmentRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Initialize(ctx context.Context) error {
count, err := s.Count(ctx, dto.SearchDto{})
if 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 s.Create(ctx, &obj)
}
return nil
}
func (s *store) Create(ctx context.Context, obj *system.Department) error {
//goland:noinspection ALL
const q = `
INSERT INTO sys_department (
name, parent_id, parent_path, status, sort
) VALUES (
:name, :parent_id, :parent_path, :status, :sort
);`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Update(ctx context.Context, obj *system.Department) error {
const q = `
UPDATE sys_department
SET name = :name,
parent_id = :parent_id,
parent_path = :parent_path,
status = :status,
sort = :sort,
updated_at = :updated_at
WHERE id = :id;`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Get(ctx context.Context, id int32) (*system.Department, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, parent_id, parent_path, status, sort, created_at, updated_at
FROM
sys_department
WHERE
id = :id;`
data := map[string]any{
"id": id,
}
var depart system.Department
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &depart)
if err != nil {
return nil, fmt.Errorf("select id department: %w", err)
}
return &depart, nil
}
func (s *store) All(ctx context.Context) ([]*system.Department, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, parent_id, parent_path, status, sort, created_at, updated_at
FROM
sys_department;`
data := map[string]any{}
var departs []system.Department
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), q, data, &departs)
if err != nil {
return nil, fmt.Errorf("select all department: %w", err)
}
return toPointer(departs), nil
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_department`
data := map[string]any{}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), buf.String(), data, &count)
if err != nil {
return 0, fmt.Errorf("select count department: %w", err)
}
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.Department, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, parent_id, parent_path, status, sort, created_at, updated_at
FROM
sys_department`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
buf.WriteString(" ORDER BY id DESC")
buf.WriteString(" LIMIT :rows_per_page OFFSET :offset")
var departs []system.Department
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), buf.String(), data, &departs)
if err != nil {
return nil, err
}
return toPointer(departs), nil
}
func (s *store) 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 sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), query, nil)
}
func toPointer(data []system.Department) []*system.Department {
var res []*system.Department
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -0,0 +1,48 @@
package department
import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if filter.SearchEmail != "" {
data["email"] = filter.SearchEmail
wc = append(wc, "email LIKE :email")
}
if filter.SearchID != 0 {
data["id"] = filter.SearchID
wc = append(wc, "id = :id")
}
if filter.SearchParentID != 0 && filter.SearchParentID != 1 {
data["parent_id"] = filter.SearchParentID
wc = append(wc, "parent_id = :parent_id")
}
if filter.SearchName != "" {
data["name"] = filter.SearchName
wc = append(wc, "name LIKE :name")
}
if filter.SearchStatus != 9999 {
data["status"] = filter.SearchStatus
wc = append(wc, "status = :status")
}
if len(wc) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(wc, " AND "))
}
}

View File

@@ -1,77 +0,0 @@
package system
import (
"context"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
)
type loginLogRepository struct {
repo *repository.Repository
}
func NewLoginLogRepository(repo *repository.Repository) system.LoginLogRepository {
return &loginLogRepository{
repo: repo,
}
}
func (s *loginLogRepository) Create(ctx context.Context, obj *system.LoginLog) error {
return s.repo.DB(ctx).Create(obj).Error
}
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").
Limit(2).
Find(&logs).
Error
if err != nil {
return nil, err
}
return logs, nil
}
func (s *loginLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) {
query := s.repo.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.repo.DB(ctx).
Model(&system.LoginLog{}).
Where("email = ?", email).
Count(&count).
Error
if err != nil {
return 0, err
}
return count, nil
}

View File

@@ -0,0 +1,28 @@
package loginlog
import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if filter.SearchEmail != "" {
data["email"] = filter.SearchEmail
wc = append(wc, "email LIKE :email")
}
if len(wc) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(wc, " AND "))
}
}

View File

@@ -0,0 +1,123 @@
package loginlog
import (
"bytes"
"context"
"fmt"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.LoginLogRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Create(ctx context.Context, obj *system.LoginLog) error {
//goland:noinspection ALL
const q = `
INSERT INTO sys_user_login_log (
email, is_success, message, referer_url, url, os, ip, browser
) VALUES (
:email, :is_success, :message, :referer_url, :url, :os, :ip, :browser
)`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) GetLatest(ctx context.Context, email string) ([]*system.LoginLog, error) {
//goland:noinspection ALL
const q = `
SELECT
id, created_at, email, is_success, message, referer_url, url, os, ip, browser
FROM
sys_user_login_log
WHERE
email = :email
ORDER BY
id DESC
LIMIT 2;`
data := map[string]any{
"email": email,
}
var logs []system.LoginLog
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), q, data, &logs)
if err != nil {
return nil, err
}
return toPointer(logs), nil
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_user_login_log`
data := map[string]any{}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), buf.String(), data, &count)
if err != nil {
return 0, fmt.Errorf("select count login log: %w", err)
}
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.LoginLog, error) {
//goland:noinspection ALL
const q = `
SELECT
id, created_at, email, is_success, message, referer_url, url, os, ip, browser
FROM
sys_user_login_log`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
buf.WriteString(" ORDER BY id DESC")
buf.WriteString(" LIMIT :rows_per_page OFFSET :offset")
var logs []system.LoginLog
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), buf.String(), data, &logs)
if err != nil {
return nil, err
}
return toPointer(logs), nil
}
func toPointer(data []system.LoginLog) []*system.LoginLog {
var res []*system.LoginLog
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -1,83 +0,0 @@
package system
import (
"context"
"fmt"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/database"
)
type menuRepository struct {
repo *repository.Repository
}
func NewMenuRepository(repo *repository.Repository) system.MenuRepository {
return &menuRepository{
repo: repo,
}
}
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 {
return r.repo.DB(ctx).Save(obj).Error
}
func (r *menuRepository) Get(ctx context.Context, id int32) (*system.Menu, error) {
var menu system.Menu
err := r.repo.DB(ctx).Where("id = ?", id).First(&menu).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("menu %d not found: %w", id, err)
}
return nil, err
}
return &menu, nil
}
func (r *menuRepository) GetByUrl(ctx context.Context, url string) (*system.Menu, error) {
var menu system.Menu
err := r.repo.DB(ctx).Where("url = ?", url).First(&menu).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("menu by url %s not found: %w", url, err)
}
return nil, err
}
return &menu, nil
}
func (r *menuRepository) All(ctx context.Context) ([]*system.Menu, error) {
var menus []*system.Menu
err := r.repo.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.repo.DB(ctx).Exec(query).Error
}

View File

@@ -0,0 +1,179 @@
package menu
import (
"context"
"fmt"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.MenuRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Create(ctx context.Context, obj *system.Menu) (*system.Menu, error) {
//goland:noinspection ALL
const q = `
INSERT INTO sys_menu (
name, display_name, url, type, parent_id, parent_path, avatar, style, visible, is_list, status, sort
) VALUES (
:name, :display_name, :url, :type, :parent_id, :parent_path, :avatar, :style, :visible, :is_list, :status, :sort
);`
err := sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
if err != nil {
return nil, err
}
return obj, nil
}
func (s *store) Update(ctx context.Context, obj *system.Menu) error {
const q = `
UPDATE sys_menu
SET name = :name,
display_name = :display_name,
url = :url,
type = :type,
parent_id = :parent_id,
parent_path = :parent_path,
avatar = :avatar,
style = :style,
visible = :visible,
is_list = :is_list,
status = :status,
sort = :sort,
updated_at = :updated_at
WHERE id = :id;`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Get(ctx context.Context, id int32) (*system.Menu, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, url, type, parent_id, parent_path, avatar, style,
visible, is_list, status, sort, created_at, updated_at
FROM
sys_menu
WHERE
id = :id;`
data := map[string]any{
"id": id,
}
var menu system.Menu
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &menu)
if err != nil {
return nil, fmt.Errorf("select id menu: %w", err)
}
return &menu, nil
}
func (s *store) GetByUrl(ctx context.Context, url string) (*system.Menu, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, url, type, parent_id, parent_path, avatar, style,
visible, is_list, status, sort, created_at, updated_at
FROM
sys_menu
WHERE
url = :url;`
data := map[string]any{
"url": url,
}
var menu system.Menu
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &menu)
if err != nil {
return nil, fmt.Errorf("select id menu: %w", err)
}
return &menu, nil
}
func (s *store) All(ctx context.Context) ([]*system.Menu, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, url, type, parent_id, parent_path, avatar, style,
visible, is_list, status, sort, created_at, updated_at
FROM
sys_menu;`
data := map[string]any{}
var menus []system.Menu
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), q, data, &menus)
if err != nil {
return nil, fmt.Errorf("select all menu: %w", err)
}
return toPointer(menus), nil
}
func (s *store) Count(ctx context.Context) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_menu`
data := map[string]any{}
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &count)
if err != nil {
return 0, fmt.Errorf("select count menu: %w", err)
}
return count.Count, nil
}
func (s *store) 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 sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), query, nil)
}
func toPointer(data []system.Menu) []*system.Menu {
var res []*system.Menu
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -1,4 +1,4 @@
package system
package menu
import (
"context"
@@ -10,16 +10,17 @@ import (
"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 {
func (s *store) Initialize(ctx context.Context) error {
count, err := s.Count(ctx)
if err != nil {
return err
}
if count > 0 {
return nil
}
sys, err := r.Create(ctx, &system.Menu{
sys, err := s.Create(ctx, &system.Menu{
Name: "系统管理",
DisplayName: "系统管理",
Url: uuid.Must(uuid.NewRandom()).String(),
@@ -39,7 +40,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
accountPermission, err := r.Create(ctx, &system.Menu{
accountPermission, err := s.Create(ctx, &system.Menu{
Name: "账户权限",
DisplayName: "账户权限",
Url: uuid.Must(uuid.NewRandom()).String(),
@@ -59,7 +60,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
systemMenu, err := r.Create(ctx, &system.Menu{
systemMenu, err := s.Create(ctx, &system.Menu{
Name: "菜单管理",
DisplayName: "菜单管理",
Url: "/system/menu/list",
@@ -79,7 +80,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
systemRole, err := r.Create(ctx, &system.Menu{
systemRole, err := s.Create(ctx, &system.Menu{
Name: "角色管理",
DisplayName: "角色管理",
Url: "/system/role/list",
@@ -99,7 +100,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
systemDepartment, err := r.Create(ctx, &system.Menu{
systemDepartment, err := s.Create(ctx, &system.Menu{
Name: "部门管理",
DisplayName: "部门管理",
Url: "/system/department/list",
@@ -119,7 +120,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
systemUser, err := r.Create(ctx, &system.Menu{
systemUser, err := s.Create(ctx, &system.Menu{
Name: "用户管理",
DisplayName: "用户管理",
Url: "/system/user/list",
@@ -139,7 +140,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "登陆日志",
DisplayName: "登陆日志",
Url: "/system/login_log/list",
@@ -159,7 +160,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "审计日志",
DisplayName: "审计日志",
Url: "/system/audit_log/list",
@@ -180,7 +181,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
}
// 菜单
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增",
DisplayName: "新增",
Url: "/system/menu/add",
@@ -200,7 +201,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增子菜单",
DisplayName: "新增子菜单",
Url: "/system/menu/add_children",
@@ -220,7 +221,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "编辑",
DisplayName: "编辑",
Url: "/system/menu/edit",
@@ -240,7 +241,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "保存",
DisplayName: "保存",
Url: "/system/menu/save",
@@ -260,7 +261,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "数据",
DisplayName: "数据",
Url: "/system/menu/data",
@@ -280,7 +281,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "刷新",
DisplayName: "刷新",
Url: "/system/menu/refresh_cache",
@@ -301,7 +302,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
}
// 角色
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增",
DisplayName: "新增",
Url: "/system/role/add",
@@ -321,7 +322,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "编辑",
DisplayName: "编辑",
Url: "/system/role/edit",
@@ -341,7 +342,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "保存",
DisplayName: "保存",
Url: "/system/role/save",
@@ -361,7 +362,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "数据",
DisplayName: "数据",
Url: "/system/role/data",
@@ -381,7 +382,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "刷新",
DisplayName: "刷新",
Url: "/system/role/refresh_cache",
@@ -401,7 +402,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "重建父路径",
DisplayName: "重建父路径",
Url: "/system/role/rebuild_parent_path",
@@ -421,7 +422,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "权限刷新",
DisplayName: "权限刷新",
Url: "/system/role/refresh_role_menus",
@@ -441,7 +442,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "设置权限",
DisplayName: "设置权限",
Url: "/system/role/set_menu",
@@ -462,7 +463,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
}
// 部门
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增",
DisplayName: "新增",
Url: "/system/department/add",
@@ -482,7 +483,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增子部门",
DisplayName: "新增子部门",
Url: "/system/department/add_children",
@@ -502,7 +503,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "编辑",
DisplayName: "编辑",
Url: "/system/department/edit",
@@ -522,7 +523,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "保存",
DisplayName: "保存",
Url: "/system/department/save",
@@ -542,7 +543,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "数据",
DisplayName: "数据",
Url: "/system/department/data",
@@ -562,7 +563,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "刷新",
DisplayName: "刷新",
Url: "/system/department/refresh_cache",
@@ -582,7 +583,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "重建父路径",
DisplayName: "重建父路径",
Url: "/system/department/rebuild_parent_path",
@@ -603,7 +604,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
}
// 用户
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增",
DisplayName: "新增",
Url: "/system/user/add",
@@ -623,7 +624,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "编辑",
DisplayName: "编辑",
Url: "/system/user/edit",
@@ -643,7 +644,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "基本资料",
DisplayName: "基本资料",
Url: "/system/user/profile",
@@ -663,7 +664,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "保存",
DisplayName: "保存",
Url: "/system/user/save",
@@ -684,7 +685,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
}
// 基础数据
basicData, err := r.Create(ctx, &system.Menu{
basicData, err := s.Create(ctx, &system.Menu{
Name: "基础数据",
DisplayName: "基础数据",
Url: uuid.Must(uuid.NewRandom()).String(),
@@ -705,7 +706,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
}
// 系统配置
systemConfig, err := r.Create(ctx, &system.Menu{
systemConfig, err := s.Create(ctx, &system.Menu{
Name: "系统属性",
DisplayName: "系统属性",
Url: "/system/config/list",
@@ -725,7 +726,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "新增",
DisplayName: "新增",
Url: "/system/config/add",
@@ -745,7 +746,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "编辑",
DisplayName: "编辑",
Url: "/system/config/edit",
@@ -765,7 +766,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "保存",
DisplayName: "保存",
Url: "/system/config/save",
@@ -785,7 +786,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "重置Pear",
DisplayName: "重置Pear",
Url: "/system/config/reset_pear",
@@ -805,7 +806,7 @@ func (r *menuRepository) Initialize(ctx context.Context) error {
return err
}
_, err = r.Create(ctx, &system.Menu{
_, err = s.Create(ctx, &system.Menu{
Name: "刷新",
DisplayName: "刷新",
Url: "/system/config/refresh_cache",

View File

@@ -1,148 +0,0 @@
package system
import (
"context"
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/database"
)
type roleRepository struct {
repo *repository.Repository
}
func NewRoleRepository(repo *repository.Repository) system.RoleRepository {
return &roleRepository{
repo: repo,
}
}
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
}
func (r *roleRepository) Update(ctx context.Context, obj *system.Role) error {
return r.repo.DB(ctx).Save(obj).Error
}
func (r *roleRepository) Get(ctx context.Context, id int32) (*system.Role, error) {
var role system.Role
err := r.repo.DB(ctx).Where("id = ?", id).First(&role).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("role %d not found: %w", id, err)
}
return nil, err
}
return &role, nil
}
func (r *roleRepository) All(ctx context.Context) ([]*system.Role, error) {
var roles []*system.Role
err := r.repo.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.repo.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.repo.DB(ctx).Exec(query).Error
}

View File

@@ -0,0 +1,48 @@
package role
import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if filter.SearchEmail != "" {
data["email"] = filter.SearchEmail
wc = append(wc, "email LIKE :email")
}
if filter.SearchID != 0 {
data["id"] = filter.SearchID
wc = append(wc, "id = :id")
}
if filter.SearchParentID != 0 && filter.SearchParentID != 1 {
data["parent_id"] = filter.SearchParentID
wc = append(wc, "parent_id = :parent_id")
}
if filter.SearchName != "" {
data["name"] = filter.SearchName
wc = append(wc, "name LIKE :name")
}
if filter.SearchStatus != 9999 {
data["status"] = filter.SearchStatus
wc = append(wc, "status = :status")
}
if len(wc) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(wc, " AND "))
}
}

View File

@@ -0,0 +1,236 @@
package role
import (
"bytes"
"context"
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.RoleRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Initialize(ctx context.Context) (*system.Role, error) {
count, err := s.Count(ctx, dto.SearchDto{})
if 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 := s.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 := s.Create(ctx, &obj1); err != nil {
return nil, err
}
return &obj1, nil
}
return s.GetByVip(ctx, true)
}
func (s *store) Create(ctx context.Context, obj *system.Role) error {
//goland:noinspection ALL
const q = `
INSERT INTO sys_role (
name, display_name, parent_id, parent_path, vip, status, sort
) VALUES (
:name, :display_name, :parent_id, :parent_path, :vip, :status, :sort
)`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Update(ctx context.Context, obj *system.Role) error {
const q = `
UPDATE sys_role
SET name = :name,
display_name = :display_name,
parent_id = :parent_id,
parent_path = :parent_path,
status = :status,
sort = :sort,
updated_at = :updated_at
WHERE id = :id;`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) GetByVip(ctx context.Context, vip bool) (*system.Role, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, parent_id, parent_path, vip, status, sort, created_at, updated_at
FROM
sys_role
WHERE
vip = :vip;`
data := map[string]any{
"vip": vip,
}
var role system.Role
if err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &role); err != nil {
return nil, fmt.Errorf("select vip role: %w", err)
}
return &role, nil
}
func (s *store) Get(ctx context.Context, id int32) (*system.Role, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, parent_id, parent_path, vip, status, sort, created_at, updated_at
FROM
sys_role
WHERE
id = :id;`
data := map[string]any{
"id": id,
}
var role system.Role
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &role)
if err != nil {
return nil, fmt.Errorf("select id role: %w", err)
}
return &role, nil
}
func (s *store) All(ctx context.Context) ([]*system.Role, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, parent_id, parent_path, vip, status, sort, created_at, updated_at
FROM
sys_role;`
data := map[string]any{}
var roles []system.Role
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), q, data, &roles)
if err != nil {
return nil, fmt.Errorf("select all role: %w", err)
}
return toPointer(roles), nil
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_role`
data := map[string]any{}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), buf.String(), data, &count)
if err != nil {
return 0, fmt.Errorf("select count role: %w", err)
}
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.Role, error) {
//goland:noinspection ALL
const q = `
SELECT
id, name, display_name, parent_id, parent_path, vip, status, sort, created_at, updated_at
FROM
sys_role`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
buf.WriteString(" ORDER BY id DESC")
buf.WriteString(" LIMIT :rows_per_page OFFSET :offset")
var roles []system.Role
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), buf.String(), data, &roles)
if err != nil {
return nil, err
}
return toPointer(roles), nil
}
func (s *store) 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 sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), query, nil)
}
func toPointer(data []system.Role) []*system.Role {
var res []*system.Role
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -1,35 +0,0 @@
package system
import (
"context"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
)
type roleMenuRepository struct {
repo *repository.Repository
}
func NewRoleMenuRepository(repo *repository.Repository) system.RoleMenuRepository {
return &roleMenuRepository{
repo: repo,
}
}
func (r *roleMenuRepository) Create(ctx context.Context, obj []*system.RoleMenu) error {
return r.repo.DB(ctx).Create(obj).Error
}
func (r *roleMenuRepository) DeleteByRoleID(ctx context.Context, roleID int32) error {
return r.repo.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.repo.DB(ctx).Where("role_id = ?", roleID).Find(&roleMenus).Error
if err != nil {
return nil, err
}
return roleMenus, nil
}

View File

@@ -0,0 +1,86 @@
package rolemenu
import (
"context"
"fmt"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.RoleMenuRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Create(ctx context.Context, obj []*system.RoleMenu) error {
if len(obj) == 0 {
return nil
}
//goland:noinspection ALL
const q = `
INSERT INTO sys_role_menu (
role_id, menu_id
) VALUES (
:role_id, :menu_id
);`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) DeleteByRoleID(ctx context.Context, roleID int32) error {
//goland:noinspection ALL
const q = `
DELETE FROM
sys_role_menu
WHERE
role_id = :role_id;`
data := map[string]any{
"role_id": roleID,
}
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, data)
}
func (s *store) ListByRoleID(ctx context.Context, roleID int32) ([]*system.RoleMenu, error) {
//goland:noinspection ALL
const q = `
SELECT
role_id, menu_id
FROM
sys_role_menu
WHERE
role_id = :role_id;`
data := map[string]any{
"role_id": roleID,
}
var roleMenus []system.RoleMenu
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), q, data, &roleMenus)
if err != nil {
return nil, fmt.Errorf("select role menu by role id: %w", err)
}
return toPointer(roleMenus), nil
}
func toPointer(data []system.RoleMenu) []*system.RoleMenu {
var res []*system.RoleMenu
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -1,147 +0,0 @@
package system
import (
"context"
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/crypto"
"management/internal/pkg/database"
"management/internal/pkg/rand"
"github.com/google/uuid"
)
type userRepository struct {
repo *repository.Repository
}
func NewUserRepository(repo *repository.Repository) system.UserRepository {
return &userRepository{
repo: repo,
}
}
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: "/assets/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
}
func (s *userRepository) Update(ctx context.Context, obj *system.User) error {
return s.repo.DB(ctx).Save(obj).Error
}
func (s *userRepository) Get(ctx context.Context, id int32) (*system.User, error) {
var user system.User
err := s.repo.DB(ctx).Where("id = ?", id).First(&user).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("user %d not found: %w", id, err)
}
return nil, err
}
return &user, nil
}
func (s *userRepository) GetByEmail(ctx context.Context, email string) (*system.User, error) {
var user system.User
err := s.repo.DB(ctx).Where("email = ?", email).First(&user).Error
if err != nil {
if database.IsNoRows(err) {
return nil, fmt.Errorf("user by email %s not found: %w", email, err)
}
return nil, err
}
return &user, nil
}
func (s *userRepository) All(ctx context.Context) ([]*system.User, error) {
var users []*system.User
err := s.repo.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.repo.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
}

View File

@@ -0,0 +1,43 @@
package user
import (
"bytes"
"strings"
"management/internal/erpserver/model/dto"
)
func applyFilter(filter dto.SearchDto, data map[string]any, buf *bytes.Buffer) {
var wc []string
if filter.SearchTimeBegin != "" && filter.SearchTimeEnd == "" {
data["start_at"] = filter.SearchTimeBegin
data["end_at"] = filter.SearchTimeEnd
wc = append(wc, "created_at BETWEEN :start_at AND :end_at")
}
if filter.SearchID != 0 {
data["id"] = filter.SearchID
wc = append(wc, "id = :id")
}
if filter.SearchName != "" {
data["username"] = filter.SearchName
wc = append(wc, "username LIKE :username")
}
if filter.SearchEmail != "" {
data["email"] = filter.SearchEmail
wc = append(wc, "email LIKE :email")
}
if filter.SearchStatus != 9999 {
data["status"] = filter.SearchStatus
wc = append(wc, "status = :status")
}
if len(wc) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(wc, " AND "))
}
}

View File

@@ -0,0 +1,234 @@
package user
import (
"bytes"
"context"
"fmt"
"time"
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/repository"
"management/internal/pkg/crypto"
"management/internal/pkg/rand"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
"github.com/google/uuid"
)
type store struct {
db *repository.Store
log *logger.Logger
}
func NewStore(db *repository.Store, log *logger.Logger) system.UserRepository {
return &store{
db: db,
log: log,
}
}
func (s *store) Initialize(ctx context.Context, departId, roleId int32) error {
count, err := s.Count(ctx, dto.SearchDto{})
if 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: "/assets/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 *store) Create(ctx context.Context, obj *system.User) error {
//goland:noinspection ALL
const q = `
INSERT INTO sys_user (
uuid, email, username, hashed_password, salt, avatar, gender, department_id, role_id, status
) VALUES (
:uuid, :email, :username, :hashed_password, :salt, :avatar, :gender, :department_id, :role_id, :status
);`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Update(ctx context.Context, obj *system.User) error {
//goland:noinspection ALL
const q = `
UPDATE sys_user
SET email = :email,
username = :username,
hashed_password = :hashed_password,
avatar = :avatar,
gender = :gender,
department_id = :department_id,
role_id = :role_id,
status = :status,
change_password_at = :change_password_at,
updated_at = :updated_at
WHERE id = :id;`
return sqldb.NamedExecContext(ctx, s.log, s.db.DB(ctx), q, obj)
}
func (s *store) Get(ctx context.Context, id int32) (*system.User, error) {
//goland:noinspection ALL
const q = `
SELECT
id, uuid, email, username, hashed_password, salt, avatar, gender, department_id,
role_id, status, change_password_at, created_at, updated_at
FROM
sys_user
WHERE
id = :id;`
data := map[string]any{
"id": id,
}
var user system.User
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &user)
if err != nil {
return nil, fmt.Errorf("select id user: %w", err)
}
return &user, nil
}
func (s *store) GetByEmail(ctx context.Context, email string) (*system.User, error) {
//goland:noinspection ALL
const q = `
SELECT
id, uuid, email, username, hashed_password, salt, avatar, gender, department_id,
role_id, status, change_password_at, created_at, updated_at
FROM
sys_user
WHERE
email = :email;`
data := map[string]any{
"email": email,
}
var user system.User
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), q, data, &user)
if err != nil {
return nil, fmt.Errorf("select email user: %w", err)
}
return &user, nil
}
func (s *store) All(ctx context.Context) ([]*system.User, error) {
//goland:noinspection ALL
const q = `
SELECT
id, uuid, email, username, hashed_password, salt, avatar, gender, department_id,
role_id, status, change_password_at, created_at, updated_at
FROM
sys_user;`
data := map[string]any{}
var users []system.User
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), q, data, &users)
if err != nil {
return nil, fmt.Errorf("select all role: %w", err)
}
return toPointer(users), nil
}
func (s *store) Count(ctx context.Context, filter dto.SearchDto) (int64, error) {
//goland:noinspection ALL
const q = `
SELECT
COUNT(1)
FROM
sys_user`
data := map[string]any{}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
var count struct {
Count int64 `db:"count"`
}
err := sqldb.NamedQueryStruct(ctx, s.log, s.db.DB(ctx), buf.String(), data, &count)
if err != nil {
return 0, fmt.Errorf("select count user: %w", err)
}
return count.Count, nil
}
func (s *store) List(ctx context.Context, filter dto.SearchDto) ([]*system.User, error) {
//goland:noinspection ALL
const q = `
SELECT
id, uuid, email, username, hashed_password, salt, avatar, gender, department_id,
role_id, status, change_password_at, created_at, updated_at
FROM
sys_user`
data := map[string]any{
"offset": (filter.Page - 1) * filter.Rows,
"rows_per_page": filter.Rows,
}
buf := bytes.NewBufferString(q)
applyFilter(filter, data, buf)
buf.WriteString(" ORDER BY id DESC")
buf.WriteString(" LIMIT :rows_per_page OFFSET :offset")
var users []system.User
err := sqldb.NamedQuerySlice(ctx, s.log, s.db.DB(ctx), buf.String(), data, &users)
if err != nil {
return nil, err
}
return toPointer(users), nil
}
func toPointer(data []system.User) []*system.User {
var res []*system.User
for _, v := range data {
res = append(res, &v)
}
return res
}

View File

@@ -7,7 +7,6 @@ import (
"management/internal/erpserver/model/form"
"management/internal/erpserver/model/system"
"management/internal/erpserver/model/view"
"management/internal/erpserver/repository"
"management/internal/pkg/cache"
"management/internal/pkg/session"
@@ -16,20 +15,17 @@ import (
type Service struct {
Log *logger.Logger
Tx repository.Transaction
Session session.Manager
Redis cache.Cache
}
func NewService(
log *logger.Logger,
tx repository.Transaction,
session session.Manager,
redis cache.Cache,
) *Service {
return &Service{
Log: log,
Tx: tx,
Session: session,
Redis: redis,
}

View File

@@ -29,5 +29,15 @@ func (b *auditLogService) BatchCreate(ctx context.Context, objs []*system.AuditL
}
func (b *auditLogService) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) {
return b.repo.List(ctx, q)
count, err := b.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
}
res, err := b.repo.List(ctx, q)
if err != nil {
return nil, 0, err
}
return res, count, nil
}

View File

@@ -41,7 +41,17 @@ func (s *configService) Get(ctx context.Context, id int32) (*system.Config, erro
}
func (s *configService) List(ctx context.Context, q dto.SearchDto) ([]*system.Config, int64, error) {
return s.repo.List(ctx, q)
count, err := s.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
}
res, err := s.repo.List(ctx, q)
if err != nil {
return nil, 0, err
}
return res, count, nil
}
func (s *configService) Pear(ctx context.Context) (*dto.PearConfig, error) {

View File

@@ -115,7 +115,17 @@ func (s *departmentService) All(ctx context.Context) ([]*system.Department, erro
}
func (s *departmentService) List(ctx context.Context, q dto.SearchDto) ([]*system.Department, int64, error) {
return s.repo.List(ctx, q)
count, err := s.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
}
res, err := s.repo.List(ctx, q)
if err != nil {
return nil, 0, err
}
return res, count, nil
}
func (s *departmentService) RefreshCache(ctx context.Context) error {

View File

@@ -25,7 +25,17 @@ func (s *loginLogService) Create(ctx context.Context, req *system.LoginLog) erro
}
func (s *loginLogService) List(ctx context.Context, q dto.SearchDto) ([]*system.LoginLog, int64, error) {
return s.repo.List(ctx, q)
count, err := s.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
}
res, err := s.repo.List(ctx, q)
if err != nil {
return nil, 0, err
}
return res, count, nil
}
func (s *loginLogService) LoginTime(ctx context.Context, email string) (dto.LoginTimeDto, error) {
@@ -45,7 +55,9 @@ func (s *loginLogService) LoginTime(ctx context.Context, email string) (dto.Logi
}
func (s *loginLogService) LoginCount(ctx context.Context, email string) int64 {
count, err := s.repo.Count(ctx, email)
count, err := s.repo.Count(ctx, dto.SearchDto{
SearchEmail: email,
})
if err != nil {
return 0
}

View File

@@ -9,6 +9,7 @@ import (
"management/internal/erpserver/model/dto"
"management/internal/erpserver/model/system"
"management/internal/erpserver/model/view"
"management/internal/erpserver/repository"
"management/internal/erpserver/service/util"
"management/internal/erpserver/service/v1"
"management/internal/pkg/know"
@@ -178,15 +179,15 @@ func (s *menuService) MenuViewData(ctx context.Context, roleID int32) ([]*dto.Se
func (s *menuService) SetRoleMenu(ctx context.Context, roleID int32, rms []*system.RoleMenu) error {
// 开启事务
return s.Tx.Transaction(ctx, func(ctx context.Context) error {
return repository.Transaction(ctx, s.Log, func(c context.Context) error {
// 先删除该角色的所有权限
err := s.roleMenuService.DeleteByRoleID(ctx, roleID)
err := s.roleMenuService.DeleteByRoleID(c, roleID)
if err != nil {
return err
}
// 再添加该角色的所有权限
return s.roleMenuService.Create(ctx, rms)
return s.roleMenuService.Create(c, rms)
})
}

View File

@@ -118,7 +118,17 @@ func (s *roleService) All(ctx context.Context) ([]*system.Role, error) {
}
func (s *roleService) List(ctx context.Context, q dto.SearchDto) ([]*system.Role, int64, error) {
return s.repo.List(ctx, q)
count, err := s.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
}
res, err := s.repo.List(ctx, q)
if err != nil {
return nil, 0, err
}
return res, count, nil
}
func (s *roleService) RefreshCache(ctx context.Context) error {

View File

@@ -22,22 +22,25 @@ import (
type userService struct {
*v1.Service
repo system.UserRepository
roleService v1.RoleService
loginLogService v1.LoginLogService
repo system.UserRepository
roleService v1.RoleService
departmentService v1.DepartmentService
loginLogService v1.LoginLogService
}
func NewUserService(
service *v1.Service,
repo system.UserRepository,
roleService v1.RoleService,
departmentService v1.DepartmentService,
loginLogService v1.LoginLogService,
) v1.UserService {
return &userService{
Service: service,
repo: repo,
roleService: roleService,
loginLogService: loginLogService,
Service: service,
repo: repo,
roleService: roleService,
departmentService: departmentService,
loginLogService: loginLogService,
}
}
@@ -111,7 +114,22 @@ func (s *userService) All(ctx context.Context) ([]*system.User, error) {
}
func (s *userService) List(ctx context.Context, q dto.SearchDto) ([]*system.User, int64, error) {
return s.repo.List(ctx, q)
count, err := s.repo.Count(ctx, q)
if err != nil {
return nil, 0, err
}
res, err := s.repo.List(ctx, q)
if err != nil {
return nil, 0, err
}
for _, user := range res {
user.Role, _ = s.roleService.Get(ctx, user.RoleID)
user.Department, _ = s.departmentService.Get(ctx, user.DepartmentID)
}
return res, count, nil
}
func (s *userService) Get(ctx context.Context, id int32) (*system.User, error) {

View File

@@ -1,196 +1,198 @@
package mid
import (
"context"
"errors"
"net/http"
"sync"
"time"
//import (
// "context"
// "errors"
// "net/http"
// "sync"
// "time"
//
// systemmodel "management/internal/erpserver/model/system"
// v1 "management/internal/erpserver/service/v1"
// "management/internal/pkg/know"
// "management/internal/pkg/session"
//
// "github.com/drhin/logger"
// "go.uber.org/zap"
//)
//
//// AuditBuffer 审计日志缓冲器
//type AuditBuffer struct {
// auditLogService v1.AuditLogService
// log *logger.Logger
// buffer chan *systemmodel.AuditLog
// stopCh chan struct{}
// wg sync.WaitGroup
// batchSize int
// flushInterval time.Duration
//}
//
//// NewAuditBuffer 创建审计日志缓冲器
//func NewAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) *AuditBuffer {
// return &AuditBuffer{
// auditLogService: auditLogService,
// log: log,
// buffer: make(chan *systemmodel.AuditLog, 10000), // 缓冲区大小
// stopCh: make(chan struct{}),
// batchSize: 50, // 批量大小
// flushInterval: 3 * time.Second, // 刷新间隔
// }
//}
//
//// Start 启动缓冲器
//func (ab *AuditBuffer) Start() {
// ab.wg.Add(1)
// go ab.processBuffer()
//}
//
//// Stop 停止缓冲器
//func (ab *AuditBuffer) Stop() {
// close(ab.stopCh)
// ab.wg.Wait()
// close(ab.buffer)
//}
//
//// Add 添加审计日志到缓冲区
//func (ab *AuditBuffer) Add(auditLog *systemmodel.AuditLog) {
// select {
// case ab.buffer <- auditLog:
// // 成功添加到缓冲区
// default:
// // 缓冲区满,记录警告但不阻塞
// ab.log.Warn("审计日志缓冲区已满,丢弃日志")
// }
//}
//
//// processBuffer 处理缓冲区中的日志
//func (ab *AuditBuffer) processBuffer() {
// defer ab.wg.Done()
//
// ticker := time.NewTicker(ab.flushInterval)
// defer ticker.Stop()
//
// batch := make([]*systemmodel.AuditLog, 0, ab.batchSize)
//
// flushBatch := func() {
// if len(batch) == 0 {
// return
// }
//
// ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
// defer cancel()
//
// // 批量插入
// if err := ab.batchInsert(ctx, batch); err != nil {
// ab.log.Error("批量插入审计日志失败", err, zap.Int("count", len(batch)))
// } else {
// ab.log.Debug("批量插入审计日志成功", zap.Int("count", len(batch)))
// }
//
// // 清空批次
// batch = batch[:0]
// }
//
// for {
// select {
// case <-ab.stopCh:
// // 停止信号,处理剩余的日志
// for len(ab.buffer) > 0 {
// select {
// case auditLog := <-ab.buffer:
// batch = append(batch, auditLog)
// if len(batch) >= ab.batchSize {
// flushBatch()
// }
// default:
// break
// }
// }
// flushBatch() // 处理最后一批
// return
//
// case <-ticker.C:
// // 定时刷新
// flushBatch()
//
// case auditLog := <-ab.buffer:
// // 收到新的审计日志
// batch = append(batch, auditLog)
// if len(batch) >= ab.batchSize {
// flushBatch()
// }
// }
// }
//}
//
//// batchInsert 批量插入数据库
//func (ab *AuditBuffer) batchInsert(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
// maxRetries := 3
// for i := 0; i < maxRetries; i++ {
// // 假设你的服务有批量创建方法,如果没有,需要添加
// if err := ab.auditLogService.BatchCreate(ctx, auditLogs); err != nil {
// if i == maxRetries-1 {
// return err
// }
// ab.log.Error("批量插入失败,准备重试", err, zap.Int("retry", i+1))
// time.Sleep(time.Duration(i+1) * time.Second)
// continue
// }
// return nil
// }
// return nil
//}
//
//// 全局缓冲器实例
//var globalAuditBuffer *AuditBuffer
//
//// InitAuditBuffer 初始化全局缓冲器
//func InitAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) {
// globalAuditBuffer = NewAuditBuffer(auditLogService, log)
// globalAuditBuffer.Start()
//}
//
//// StopAuditBuffer 停止全局缓冲器
//func StopAuditBuffer() {
// if globalAuditBuffer != nil {
// globalAuditBuffer.Stop()
// }
//}
//
//// Audit 优化后的中间件
//func Audit(sess session.Manager, log *logger.Logger) func(http.Handler) http.Handler {
// 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
// }
//
// // 处理请求
// next.ServeHTTP(w, r)
//
// // 异步添加到缓冲区
// go func() {
// if user.ID == 0 {
// log.Error("用户信息为空", errors.New("user is empty"))
// return
// }
//
// auditLog := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
//
// // 添加到缓冲区,不会阻塞
// if globalAuditBuffer != nil {
// globalAuditBuffer.Add(auditLog)
// }
// }()
// })
// }
//}
systemmodel "management/internal/erpserver/model/system"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/know"
"management/internal/pkg/session"
"github.com/drhin/logger"
"go.uber.org/zap"
)
// AuditBuffer 审计日志缓冲器
type AuditBuffer struct {
auditLogService v1.AuditLogService
log *logger.Logger
buffer chan *systemmodel.AuditLog
stopCh chan struct{}
wg sync.WaitGroup
batchSize int
flushInterval time.Duration
}
// NewAuditBuffer 创建审计日志缓冲器
func NewAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) *AuditBuffer {
return &AuditBuffer{
auditLogService: auditLogService,
log: log,
buffer: make(chan *systemmodel.AuditLog, 10000), // 缓冲区大小
stopCh: make(chan struct{}),
batchSize: 50, // 批量大小
flushInterval: 3 * time.Second, // 刷新间隔
}
}
// Start 启动缓冲器
func (ab *AuditBuffer) Start() {
ab.wg.Add(1)
go ab.processBuffer()
}
// Stop 停止缓冲器
func (ab *AuditBuffer) Stop() {
close(ab.stopCh)
ab.wg.Wait()
close(ab.buffer)
}
// Add 添加审计日志到缓冲区
func (ab *AuditBuffer) Add(auditLog *systemmodel.AuditLog) {
select {
case ab.buffer <- auditLog:
// 成功添加到缓冲区
default:
// 缓冲区满,记录警告但不阻塞
ab.log.Warn("审计日志缓冲区已满,丢弃日志")
}
}
// processBuffer 处理缓冲区中的日志
func (ab *AuditBuffer) processBuffer() {
defer ab.wg.Done()
ticker := time.NewTicker(ab.flushInterval)
defer ticker.Stop()
batch := make([]*systemmodel.AuditLog, 0, ab.batchSize)
flushBatch := func() {
if len(batch) == 0 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 批量插入
if err := ab.batchInsert(ctx, batch); err != nil {
ab.log.Error("批量插入审计日志失败", err, zap.Int("count", len(batch)))
} else {
ab.log.Debug("批量插入审计日志成功", zap.Int("count", len(batch)))
}
// 清空批次
batch = batch[:0]
}
for {
select {
case <-ab.stopCh:
// 停止信号,处理剩余的日志
for len(ab.buffer) > 0 {
select {
case auditLog := <-ab.buffer:
batch = append(batch, auditLog)
if len(batch) >= ab.batchSize {
flushBatch()
}
default:
break
}
}
flushBatch() // 处理最后一批
return
case <-ticker.C:
// 定时刷新
flushBatch()
case auditLog := <-ab.buffer:
// 收到新的审计日志
batch = append(batch, auditLog)
if len(batch) >= ab.batchSize {
flushBatch()
}
}
}
}
// batchInsert 批量插入数据库
func (ab *AuditBuffer) batchInsert(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
// 假设你的服务有批量创建方法,如果没有,需要添加
if err := ab.auditLogService.BatchCreate(ctx, auditLogs); err != nil {
if i == maxRetries-1 {
return err
}
ab.log.Error("批量插入失败,准备重试", err, zap.Int("retry", i+1))
time.Sleep(time.Duration(i+1) * time.Second)
continue
}
return nil
}
return nil
}
// 全局缓冲器实例
var globalAuditBuffer *AuditBuffer
// InitAuditBuffer 初始化全局缓冲器
func InitAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) {
globalAuditBuffer = NewAuditBuffer(auditLogService, log)
globalAuditBuffer.Start()
}
// StopAuditBuffer 停止全局缓冲器
func StopAuditBuffer() {
if globalAuditBuffer != nil {
globalAuditBuffer.Stop()
}
}
// Audit 优化后的中间件
func Audit(sess session.Manager, log *logger.Logger) func(http.Handler) http.Handler {
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
}
// 处理请求
next.ServeHTTP(w, r)
// 异步添加到缓冲区
go func() {
if user.ID == 0 {
log.Error("用户信息为空", errors.New("user is empty"))
return
}
auditLog := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
// 添加到缓冲区,不会阻塞
if globalAuditBuffer != nil {
globalAuditBuffer.Add(auditLog)
}
}()
})
}
}
// ======================================================
// 如果你的AuditLogService没有BatchCreate方法需要添加这个接口
// 在你的service接口中添加

View File

@@ -0,0 +1,57 @@
package mid
import (
"context"
"net/http"
"time"
"management/internal/erpserver/model/system"
"management/internal/pkg/know"
"management/internal/pkg/session"
"management/internal/tasks"
"github.com/drhin/logger"
"github.com/hibiken/asynq"
"go.uber.org/zap"
)
// Audit 改造后的中间件
func Audit(sess session.Manager, log *logger.Logger, task tasks.TaskDistributor) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
start := time.Now()
user, err := sess.GetUser(ctx, know.StoreName)
if err != nil {
log.Error("获取用户会话失败", err)
}
next.ServeHTTP(w, r)
if user.ID == 0 {
return
}
payload := &tasks.PayloadConsumeAuditLog{
AuditLog: system.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now()),
}
opts := []asynq.Option{
asynq.MaxRetry(10),
asynq.ProcessIn(1 * time.Second),
asynq.Queue(tasks.QueueCritical),
}
c, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
if err := task.DistributeTaskConsumeAuditLog(c, payload, opts...); err != nil {
log.Error("distribute task failed", err,
zap.String("type", "audit"),
zap.Any("payload", payload),
)
}
})
}
}

View File

@@ -7,41 +7,39 @@ import (
"net/http"
"sync"
"time"
"unsafe"
"management/internal/erpserver/model/dto"
v1 "management/internal/erpserver/service/v1"
"management/internal/pkg/know"
"management/internal/pkg/session"
"github.com/json-iterator/go"
"github.com/patrickmn/go-cache"
"golang.org/x/sync/singleflight"
)
// 高性能JSON库全局初始化
var json = jsoniter.ConfigFastest
// 使用jsoniter优化菜单结构体序列化
func init() {
jsoniter.RegisterTypeEncoderFunc("dto.OwnerMenuDto",
func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
m := (*dto.OwnerMenuDto)(ptr)
stream.WriteObjectStart()
stream.WriteObjectField("id")
stream.WriteUint(uint(m.ID))
stream.WriteMore()
stream.WriteObjectField("url")
stream.WriteString(m.Url)
stream.WriteMore()
stream.WriteObjectField("parentId")
stream.WriteUint(uint(m.ParentID))
stream.WriteMore()
stream.WriteObjectField("isList")
stream.WriteBool(m.IsList)
stream.WriteObjectEnd()
}, nil)
}
//// 高性能JSON库全局初始化
//var json = jsoniter.ConfigFastest
//
//// 使用jsoniter优化菜单结构体序列化
//func init() {
// jsoniter.RegisterTypeEncoderFunc("dto.OwnerMenuDto",
// func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
// m := (*dto.OwnerMenuDto)(ptr)
// stream.WriteObjectStart()
// stream.WriteObjectField("id")
// stream.WriteUint(uint(m.ID))
// stream.WriteMore()
// stream.WriteObjectField("url")
// stream.WriteString(m.Url)
// stream.WriteMore()
// stream.WriteObjectField("parentId")
// stream.WriteUint(uint(m.ParentID))
// stream.WriteMore()
// stream.WriteObjectField("isList")
// stream.WriteBool(m.IsList)
// stream.WriteObjectEnd()
// }, nil)
//}
var publicRoutes = map[string]bool{
"/home.html": true,

View File

@@ -0,0 +1,95 @@
package mid
//var publicRoutes = map[string]bool{
// "/home.html": true,
// "/dashboard": true,
// "/system/menus": true,
// "/upload/img": true,
// "/upload/file": true,
// "/upload/multi_files": true,
// "/system/pear.json": true,
// "/logout": true,
//}
//
//var m sync.Map
//
//func Authorize(
// sess session.Manager,
// menuService v1.MenuService,
//) func(http.Handler) http.Handler {
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// path := r.URL.Path
//
// // 登陆检查
// user, err := sess.GetUser(ctx, know.StoreName)
// if err != nil || user.ID == 0 {
// http.Redirect(w, r, "/", http.StatusFound)
// return
// }
//
// // 公共路由放行
// if publicRoutes[path] {
// ctx = setUser(ctx, user)
// next.ServeHTTP(w, r.WithContext(ctx))
// return
// }
//
// n1 := time.Now()
// // 权限检查
// var menus map[string]*dto.OwnerMenuDto
// cacheKey := fmt.Sprintf("user_menus:%d", user.RoleID)
// if value, ok := m.Load(cacheKey); ok {
// menus = value.(map[string]*dto.OwnerMenuDto)
// log.Printf("map (from cache): %s", time.Since(n1).String())
// } else {
// menus, err = menuService.ListByRoleIDToMap(ctx, user.RoleID)
// if err == nil {
// m.Store(cacheKey, menus)
// }
// log.Printf("map (from DB, then cached): %s", time.Since(n1).String())
// }
//
// if !hasPermission(menus, path) {
// http.Error(w, "Forbidden", http.StatusForbidden)
// return
// }
//
// cur := getCurrentMenus(menus, path)
//
// ctx = setUser(ctx, user)
// ctx = setCurMenus(ctx, cur)
//
// next.ServeHTTP(w, r.WithContext(ctx))
// })
// }
//}
//
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
// _, ok := menus[path]
// return ok
//}
//
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
// var res []dto.OwnerMenuDto
//
// menu, ok := data[path]
// if !ok {
// return res
// }
//
// for _, item := range data {
// if menu.IsList {
// if item.ParentID == menu.ID || item.ID == menu.ID {
// res = append(res, *item)
// }
// } else {
// if item.ParentID == menu.ParentID {
// res = append(res, *item)
// }
// }
// }
//
// return res
//}

View File

@@ -2,8 +2,10 @@ package mid
import (
"context"
"errors"
"management/internal/erpserver/model/dto"
"management/internal/pkg/sqldb"
"github.com/a-h/templ"
)
@@ -73,3 +75,19 @@ func GetHtmlCsrfToken(ctx context.Context) templ.Component {
return v
}
type trkey struct{}
func setTran(ctx context.Context, tx sqldb.CommitRollbacker) context.Context {
return context.WithValue(ctx, trkey{}, tx)
}
// GetTran retrieves the value that can manage a transaction.
func GetTran(ctx context.Context) (sqldb.CommitRollbacker, error) {
v, ok := ctx.Value(trkey{}).(sqldb.CommitRollbacker)
if !ok {
return nil, errors.New("transaction not found in context")
}
return v, nil
}

View File

@@ -0,0 +1,57 @@
package mid
import (
"database/sql"
"errors"
"net/http"
"management/internal/pkg/sqldb"
"github.com/drhin/logger"
"go.uber.org/zap"
)
func BeginCommitRollback(log *logger.Logger, bgn sqldb.Beginner) func(http.Handler) http.Handler {
m := func(next http.Handler) http.Handler {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hasCommitted := false
log.Info("BEGIN TRANSACTION")
tx, err := bgn.Begin()
if err != nil {
log.Error("BEGIN TRANSACTION", err)
return
}
defer func() {
if !hasCommitted {
log.Info("ROLLBACK TRANSACTION")
}
if err := tx.Rollback(); err != nil {
if errors.Is(err, sql.ErrTxDone) {
return
}
log.Info("ROLLBACK TRANSACTION", zap.Error(err))
}
}()
ctx := r.Context()
ctx = setTran(ctx, tx)
next.ServeHTTP(w, r.WithContext(ctx))
log.Info("COMMIT TRANSACTION")
if err := tx.Commit(); err != nil {
log.Error("COMMIT TRANSACTION", err)
return
}
hasCommitted = true
})
return h
}
return m
}

214
internal/pkg/sqldb/sqldb.go Normal file
View File

@@ -0,0 +1,214 @@
package sqldb
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"time"
"management/internal/pkg/config"
"github.com/drhin/logger"
"github.com/jackc/pgx/v5/pgconn"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
const (
uniqueViolation = "23505"
undefinedTable = "42P01"
)
var (
ErrDBNotFound = sql.ErrNoRows
ErrDBDuplicatedEntry = errors.New("duplicated entry")
ErrUndefinedTable = errors.New("undefined table")
)
type Config struct {
User string
Password string
Host string
Port int
Name string
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
}
func NewDB(config *config.Config, log *logger.Logger) (*sqlx.DB, func(), error) {
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
config.DB.Username,
config.DB.Password,
config.DB.Host,
config.DB.Port,
config.DB.DBName,
)
db, err := sqlx.Open("pgx", dsn)
if err != nil {
return nil, nil, fmt.Errorf("sqlx open db: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
return nil, nil, err
}
// 设置最大空闲连接数(默认 2)
db.SetMaxIdleConns(config.DB.MaxIdleConns)
// 设置最大打开连接数(默认 0 无限制)
db.SetMaxOpenConns(config.DB.MaxOpenConns)
// 设置连接最大存活时间
db.SetConnMaxLifetime(config.DB.ConnMaxLifetime)
// 设置连接最大空闲时间
db.SetConnMaxIdleTime(config.DB.ConnMaxIdleTime)
cleanup := func() {
if err := db.Close(); err != nil {
log.Error("sql db close error", err)
}
}
return db, cleanup, nil
}
func NamedExecContext(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any) (err error) {
defer func() {
if err != nil {
switch data.(type) {
case struct{}:
log.Error("database.NamedExecContext (data is struct)", err,
zap.String("query", query),
zap.Any("ERROR", err))
default:
log.Error("database.NamedExecContext", err,
zap.String("query", query),
zap.Any("ERROR", err))
}
}
}()
if _, err := sqlx.NamedExecContext(ctx, db, query, data); err != nil {
var pgError *pgconn.PgError
if errors.As(err, &pgError) {
switch pgError.Code {
case undefinedTable:
return ErrUndefinedTable
case uniqueViolation:
return ErrDBDuplicatedEntry
}
}
return err
}
return nil
}
func NamedQueryStruct(ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest any) (err error) {
q := queryString(query, data)
rows, err := sqlx.NamedQueryContext(ctx, db, q, data)
if err != nil {
var pqErr *pgconn.PgError
if errors.As(err, &pqErr) && pqErr.Code == undefinedTable {
return ErrUndefinedTable
}
log.Error("NamedQueryStruct NamedQueryContext error", err,
zap.String("query", q),
zap.Any("data", data),
)
return err
}
defer func(rows *sqlx.Rows) {
err := rows.Close()
if err != nil {
log.Error("rows close error", err)
}
}(rows)
if !rows.Next() {
return ErrDBNotFound
}
if err := rows.StructScan(dest); err != nil {
log.Error("NamedQueryStruct StructScan error", err,
zap.String("query", q),
zap.Any("data", data),
)
return err
}
return nil
}
func NamedQuerySlice[T any](ctx context.Context, log *logger.Logger, db sqlx.ExtContext, query string, data any, dest *[]T) (err error) {
q := queryString(query, data)
rows, err := sqlx.NamedQueryContext(ctx, db, q, data)
if err != nil {
var pqErr *pgconn.PgError
if errors.As(err, &pqErr) && pqErr.Code == undefinedTable {
return ErrUndefinedTable
}
log.Error("NamedQueryStruct NamedQueryContext error", err,
zap.String("query", q),
zap.Any("data", data),
)
return err
}
defer func(rows *sqlx.Rows) {
err := rows.Close()
if err != nil {
log.Error("rows close error", err)
}
}(rows)
var slice []T
for rows.Next() {
v := new(T)
if err := rows.StructScan(v); err != nil {
log.Error("NamedQuerySlice StructScan error", err,
zap.String("query", q),
zap.Any("data", data),
)
return err
}
slice = append(slice, *v)
}
*dest = slice
return nil
}
func queryString(query string, args any) string {
query, params, err := sqlx.Named(query, args)
if err != nil {
return err.Error()
}
for _, param := range params {
var value string
switch v := param.(type) {
case string:
value = fmt.Sprintf("'%s'", v)
case []byte:
value = fmt.Sprintf("'%s'", string(v))
default:
value = fmt.Sprintf("%v", v)
}
query = strings.Replace(query, "?", value, 1)
}
query = strings.ReplaceAll(query, "\t", "")
query = strings.ReplaceAll(query, "\n", " ")
return strings.Trim(query, " ")
}

View File

@@ -0,0 +1,49 @@
package sqldb
import (
"fmt"
"github.com/jmoiron/sqlx"
)
// Beginner represents a value that can begin a transaction.
type Beginner interface {
Begin() (CommitRollbacker, error)
}
// CommitRollbacker represents a value that can commit or rollback a transaction.
type CommitRollbacker interface {
Commit() error
Rollback() error
}
// =============================================================================
// DBBeginner implements the Beginner interface,
type DBBeginner struct {
sqlxDB *sqlx.DB
}
// NewBeginner constructs a value that implements the beginner interface.
func NewBeginner(sqlxDB *sqlx.DB) *DBBeginner {
return &DBBeginner{
sqlxDB: sqlxDB,
}
}
// Begin implements the Beginner interface and returns a concrete value that
// implements the CommitRollbacker interface.
func (db *DBBeginner) Begin() (CommitRollbacker, error) {
return db.sqlxDB.Beginx()
}
// GetExtContext is a helper function that extracts the sqlx value
// from the domain transactor interface for transactional use.
func GetExtContext(tx CommitRollbacker) (sqlx.ExtContext, error) {
ec, ok := tx.(sqlx.ExtContext)
if !ok {
return nil, fmt.Errorf("Transactor(%T) not of a type *sql.Tx", tx)
}
return ec, nil
}

21
internal/tasks/asynq.go Normal file
View File

@@ -0,0 +1,21 @@
package tasks
import "github.com/redis/go-redis/v9"
// RedisClientConnector 是一个适配器,它包装了现有的 redis.Client
// 并实现了 asynq.RedisConnOpt 接口。
type RedisClientConnector struct {
Client *redis.Client
}
func NewRedisClientConnector(c *redis.Client) *RedisClientConnector {
return &RedisClientConnector{
Client: c,
}
}
// MakeRedisClient 实现了 asynq.RedisConnOpt 接口。
// 它直接返回已存在的客户端实例。
func (c *RedisClientConnector) MakeRedisClient() interface{} {
return c.Client
}

59
internal/tasks/audit.go Normal file
View File

@@ -0,0 +1,59 @@
package tasks
import (
"context"
"encoding/json"
"fmt"
"management/internal/erpserver/model/system"
"github.com/hibiken/asynq"
)
const TaskConsumeAuditLog = "task:audit"
type PayloadConsumeAuditLog struct {
*system.AuditLog
}
func (d *RedisTaskDistributor) DistributeTaskConsumeAuditLog(
ctx context.Context,
payload *PayloadConsumeAuditLog,
opts ...asynq.Option,
) error {
jsonPayload, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal task payload: %w", err)
}
task := asynq.NewTask(TaskConsumeAuditLog, jsonPayload, opts...)
_, err = d.client.EnqueueContext(ctx, task)
if err != nil {
return fmt.Errorf("failed to enqueue task: %w", err)
}
//d.log.Info("enqueued task",
// zap.String("type", task.Type()),
// zap.Binary("payload", task.Payload()),
// zap.String("queue", info.Queue),
// zap.Int("max_retry", info.MaxRetry),
//)
return nil
}
func (p *RedisTaskProcessor) ProcessTaskConsumeAuditLog(ctx context.Context, task *asynq.Task) error {
var payload PayloadConsumeAuditLog
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
}
if err := p.auditService.Create(ctx, payload.AuditLog); err != nil {
return fmt.Errorf("failed to process task: %w", err)
}
//p.log.Info("processed task",
// zap.String("type", task.Type()),
// zap.Binary("payload", task.Payload()),
//)
return nil
}

View File

@@ -0,0 +1,25 @@
package tasks
import (
"context"
"github.com/drhin/logger"
"github.com/hibiken/asynq"
)
type TaskDistributor interface {
DistributeTaskConsumeAuditLog(ctx context.Context, payload *PayloadConsumeAuditLog, opts ...asynq.Option) error
}
type RedisTaskDistributor struct {
log *logger.Logger
client *asynq.Client
}
func NewRedisTaskDistributor(log *logger.Logger, opt *RedisClientConnector) TaskDistributor {
client := asynq.NewClient(opt)
return &RedisTaskDistributor{
log: log,
client: client,
}
}

View File

@@ -0,0 +1,65 @@
package tasks
import (
"context"
v1 "management/internal/erpserver/service/v1"
"github.com/drhin/logger"
"github.com/hibiken/asynq"
"go.uber.org/zap"
)
const (
QueueCritical = "critical"
QueueDefault = "default"
)
type TaskProcessor interface {
Start() error
Shutdown()
ProcessTaskConsumeAuditLog(ctx context.Context, task *asynq.Task) error
}
type RedisTaskProcessor struct {
log *logger.Logger
server *asynq.Server
auditService v1.AuditLogService
}
func NewRedisTaskProcessor(log *logger.Logger, opt *RedisClientConnector, auditService v1.AuditLogService) TaskProcessor {
server := asynq.NewServer(
opt,
asynq.Config{
Queues: map[string]int{
QueueCritical: 10,
QueueDefault: 5,
},
ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
log.Error("process task failed", err,
zap.String("type", task.Type()),
zap.Binary("payload", task.Payload()),
)
}),
},
)
return &RedisTaskProcessor{
log: log,
server: server,
auditService: auditService,
}
}
func (p *RedisTaskProcessor) Start() error {
mux := asynq.NewServeMux()
mux.HandleFunc(TaskConsumeAuditLog, p.ProcessTaskConsumeAuditLog)
return p.server.Start(mux)
}
func (p *RedisTaskProcessor) Shutdown() {
p.server.Shutdown()
}