优化结构

This commit is contained in:
Yvan
2026-08-28 11:51:37 +08:00
parent 5b04998731
commit 33ba777137
25 changed files with 163 additions and 125 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ func waitForDataScopeAuditCount(t *testing.T, db *gorm.DB, want int64) {
func TestDataScopeAuditWriterFlushesBatch(t *testing.T) {
db := openDataScopeAuditTestDB(t, "data-scope-audit-batch")
d := &Data{gormDB: newReloadableDB(db, nil)}
d := &Data{gormDB: newReloadableDB(db)}
w := newDataScopeAuditWriterWithOptions(d, slog.New(slog.NewTextHandler(io.Discard, nil)), 8, 2, time.Hour)
d.auditLog = w
t.Cleanup(w.Close)
@@ -59,7 +59,7 @@ func TestDataScopeAuditWriterFlushesBatch(t *testing.T) {
func TestDataScopeAuditWriterUsesReloadedDatabase(t *testing.T) {
first := openDataScopeAuditTestDB(t, "data-scope-audit-first")
second := openDataScopeAuditTestDB(t, "data-scope-audit-second")
d := &Data{gormDB: newReloadableDB(first, nil)}
d := &Data{gormDB: newReloadableDB(first)}
w := newDataScopeAuditWriterWithOptions(d, slog.New(slog.NewTextHandler(io.Discard, nil)), 8, 100, 10*time.Millisecond)
d.auditLog = w
t.Cleanup(w.Close)
+2 -2
View File
@@ -16,11 +16,11 @@ import (
func TestGORMLoggerUsesApplicationCategories(t *testing.T) {
root := t.TempDir()
appLogger, cleanup := logging.NewZapLogger(root, "application.log", logging.Options{Level: "info", Format: "json"})
appLogger, control := logging.NewReloadableZapLogger(root, "application.log", logging.Options{Level: "info", Format: "json"})
databaseLogger := gormkit.NewLogger(appLogger, logger.Info)
databaseLogger.Trace(context.Background(), time.Now(), func() (string, int64) { return "SELECT 1", 1 }, nil)
databaseLogger.Trace(context.Background(), time.Now(), func() (string, int64) { return "SELECT missing", 0 }, errors.New("database failure"))
cleanup()
control.Close()
date := time.Now().Format("2006-01-02")
for _, name := range []string{"application.log", filepath.Join("sql", "application.log"), filepath.Join("error", "error.log")} {
+1 -1
View File
@@ -247,7 +247,7 @@ func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
}
d := &Data{
runtime: config.NewStore(&config.Config{Data: &config.Data{}, Admin: currentAdmin}),
gormDB: newReloadableDB(db, nil),
gormDB: newReloadableDB(db),
storage: currentStorage,
}
d.databaseReady.Store(true)
+1 -1
View File
@@ -165,7 +165,7 @@ func (r *rollback) run() {
// newReloadableDB owns only the hot-swappable database handle. Domain-specific
// callbacks are attached by Data when a pool becomes active.
func newReloadableDB(db *gorm.DB, _ ...dataScopeAuditEnqueue) *reloadable[*gorm.DB] {
func newReloadableDB(db *gorm.DB) *reloadable[*gorm.DB] {
return newReloadable(db, closeGormDB)
}
+2 -2
View File
@@ -71,7 +71,7 @@ func (r *announcementRepo) List(ctx context.Context, filter system.AnnouncementF
if filter.StartCreatedAt != nil && filter.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", filter.StartCreatedAt, filter.EndCreatedAt)
}
return listRows(db.Order("id desc"), filter.Page, filter.PageSize, filter.PageSize > 0, func(po announcementPO) *system.Announcement {
return listRows(db.Order("id desc"), filter.Page, filter.PageSize, rowQueryOptions{Paginate: filter.PageSize > 0}, func(po announcementPO) *system.Announcement {
return announcementToBiz(po)
})
}
@@ -83,7 +83,7 @@ func (r *announcementRepo) UserOptions(ctx context.Context) ([]system.UserOption
}
// The generated data-source endpoint is best effort: return collected
// options even when the underlying scan reports an error.
_ = r.data.DB().WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Scan(&rows).Error
_ = r.data.DB().WithContext(ctx).Model(&userPO{}).Select("nick_name AS label, id AS value").Scan(&rows).Error
items := make([]system.UserOption, 0, len(rows))
for _, row := range rows {
items = append(items, system.UserOption{Label: row.Label, Value: row.Value})
+2 -5
View File
@@ -249,11 +249,8 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
}
value.CreatedAt, value.UpdatedAt = po.CreatedAt, po.UpdatedAt
value.DataScope, value.DefaultRouter = po.DataScope, po.DefaultRouter
copyLinks := func(table string, destination any, columns map[string]any) error {
return tx.Table(table).Where(columns).Find(destination).Error
}
var menus []authorityMenuPO
if err := copyLinks("sys_authority_menus", &menus, map[string]any{"sys_authority_authority_id": sourceID}); err != nil {
if err := tx.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", sourceID).Find(&menus).Error; err != nil {
return err
}
if strict && len(menus) > 0 {
@@ -316,7 +313,7 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
}
}
var buttons []authorityButtonPO
if err := copyLinks("sys_authority_btns", &buttons, map[string]any{"authority_id": sourceID}); err != nil {
if err := tx.Model(&authorityButtonPO{}).Where("authority_id = ?", sourceID).Find(&buttons).Error; err != nil {
return err
}
if strict && len(buttons) > 0 {
+1 -1
View File
@@ -35,7 +35,7 @@ func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *
db = db.Where("target_table LIKE ?", "%"+q.TargetTable+"%")
}
}
pos, total, err := queryRows[DataAccessLogPO](db.Order("id desc"), page, size, true, true)
pos, total, err := queryRows[DataAccessLogPO](db.Order("id desc"), page, size, rowQueryOptions{Paginate: true, Required: true})
if err != nil {
return nil, 0, err
}
+1 -1
View File
@@ -90,7 +90,7 @@ func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *syst
db = db.Where("info LIKE ?", "%"+*q.Info+"%")
}
}
return listRows(db.Order("created_at desc"), page, size, true, func(po errorRecordPO) *system.ErrorRecord {
return listRows(db.Order("created_at desc"), page, size, rowQueryOptions{Paginate: true}, func(po errorRecordPO) *system.ErrorRecord {
return errorFromPO(po)
})
}
+10 -5
View File
@@ -10,13 +10,18 @@ import (
// count the filtered query, apply the bounded page, load POs, and convert them
// at the data/biz boundary. Domain-specific filters and ordering stay in each
// repository.
func queryRows[PO any](db *gorm.DB, page, size int, paginate, required bool) ([]PO, int64, error) {
type rowQueryOptions struct {
Paginate bool
Required bool
}
func queryRows[PO any](db *gorm.DB, page, size int, options rowQueryOptions) ([]PO, int64, error) {
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
if paginate {
if required {
if options.Paginate {
if options.Required {
db = pagination.ApplyRequired(db, page, size, 100)
} else {
db = pagination.Apply(db, page, size, 100)
@@ -29,8 +34,8 @@ func queryRows[PO any](db *gorm.DB, page, size int, paginate, required bool) ([]
return pos, total, nil
}
func listRows[PO any, DO any](db *gorm.DB, page, size int, paginate bool, convert func(PO) *DO) ([]*DO, int64, error) {
pos, total, err := queryRows[PO](db, page, size, paginate, false)
func listRows[PO any, DO any](db *gorm.DB, page, size int, options rowQueryOptions, convert func(PO) *DO) ([]*DO, int64, error) {
pos, total, err := queryRows[PO](db, page, size, options)
if err != nil {
return nil, 0, err
}
+1 -1
View File
@@ -51,7 +51,7 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *syst
db = db.Where("created_at <= ?", *q.EndCreatedAt)
}
}
pos, total, err := queryRows[loginLogPO](db.Order("id desc"), page, size, true, true)
pos, total, err := queryRows[loginLogPO](db.Order("id desc"), page, size, rowQueryOptions{Paginate: true, Required: true})
if err != nil {
return nil, 0, err
}
+1 -1
View File
@@ -47,7 +47,7 @@ func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *
db = db.Where("status = ?", q.Status)
}
}
pos, total, err := queryRows[operationPO](db.Order("id desc"), page, size, true, true)
pos, total, err := queryRows[operationPO](db.Order("id desc"), page, size, rowQueryOptions{Paginate: true, Required: true})
if err != nil {
return nil, 0, err
}
+1 -1
View File
@@ -70,7 +70,7 @@ func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *s
db = db.Where(clause.Like{Column: clause.Column{Name: "key"}, Value: "%" + q.Key + "%"})
}
}
return listRows(db.Order("id desc"), page, size, true, func(po parameterPO) *system.SystemParameter {
return listRows(db.Order("id desc"), page, size, rowQueryOptions{Paginate: true}, func(po parameterPO) *system.SystemParameter {
return parameterFromPO(po)
})
}
+1 -1
View File
@@ -85,5 +85,5 @@ func (r *securityRepo) SaveSecurityConfig(ctx context.Context, v *system.Securit
}
func (r *securityRepo) BackfillPasswordUpdatedAt(ctx context.Context, at time.Time) error {
return r.data.DB().WithContext(ctx).Table("sys_users").Where("password_updated_at IS NULL").Update("password_updated_at", at).Error
return r.data.DB().WithContext(ctx).Model(&userPO{}).Where("password_updated_at IS NULL").Update("password_updated_at", at).Error
}
+5 -5
View File
@@ -56,7 +56,7 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*system.User, erro
return nil, err
}
var authorityPOs []authorityPO
if err := r.data.DB().WithContext(ctx).Table("sys_authorities").Joins("JOIN sys_user_authority ON sys_user_authority.sys_authority_authority_id = sys_authorities.authority_id").Where("sys_user_authority.sys_user_id = ? AND sys_authorities.deleted_at IS NULL", po.ID).Find(&authorityPOs).Error; err != nil {
if err := r.data.DB().WithContext(ctx).Model(&authorityPO{}).Joins("JOIN sys_user_authority ON sys_user_authority.sys_authority_authority_id = sys_authorities.authority_id").Where("sys_user_authority.sys_user_id = ? AND sys_authorities.deleted_at IS NULL", po.ID).Find(&authorityPOs).Error; err != nil {
return nil, err
}
authorities := make([]system.Authority, 0, len(authorityPOs))
@@ -68,7 +68,7 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*system.User, erro
return nil, err
}
var departmentPOs []departmentPO
if err := r.data.DB().WithContext(ctx).Table("sys_departments").Joins("JOIN sys_user_departments ON sys_user_departments.sys_department_id = sys_departments.id").Where("sys_user_departments.sys_user_id = ? AND sys_departments.deleted_at IS NULL", po.ID).Find(&departmentPOs).Error; err != nil {
if err := r.data.DB().WithContext(ctx).Model(&departmentPO{}).Joins("JOIN sys_user_departments ON sys_user_departments.sys_department_id = sys_departments.id").Where("sys_user_departments.sys_user_id = ? AND sys_departments.deleted_at IS NULL", po.ID).Find(&departmentPOs).Error; err != nil {
return nil, err
}
departments := make([]system.Department, 0, len(departmentPOs))
@@ -86,7 +86,7 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*system.User, erro
}
}
var positionPOs []positionPO
if err := r.data.DB().WithContext(ctx).Table("sys_positions").Joins("JOIN sys_user_positions ON sys_user_positions.sys_position_id = sys_positions.id").Where("sys_user_positions.sys_user_id = ? AND sys_positions.deleted_at IS NULL", po.ID).Find(&positionPOs).Error; err != nil {
if err := r.data.DB().WithContext(ctx).Model(&positionPO{}).Joins("JOIN sys_user_positions ON sys_user_positions.sys_position_id = sys_positions.id").Where("sys_user_positions.sys_user_id = ? AND sys_positions.deleted_at IS NULL", po.ID).Find(&positionPOs).Error; err != nil {
return nil, err
}
positions := make([]system.Position, 0, len(positionPOs))
@@ -238,7 +238,7 @@ func (r *userRepo) HasAuthorityMenu(ctx context.Context, authorityID uint, name
func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*system.Menu, error) {
var pos []menuPO
err := r.data.DB().WithContext(ctx).Table("sys_base_menus").
err := r.data.DB().WithContext(ctx).Model(&menuPO{}).
Joins("JOIN sys_authority_menus ON sys_authority_menus.sys_base_menu_id = sys_base_menus.id").
Where("sys_authority_menus.sys_authority_authority_id = ? AND sys_base_menus.deleted_at IS NULL", authorityID).
Order("sys_base_menus.sort").Scan(&pos).Error
@@ -266,7 +266,7 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*s
menu.Parameters = append(menu.Parameters, menuParameterFromPO(parameter))
}
var buttons []menuButtonPO
if err := r.data.DB().WithContext(ctx).Table("sys_base_menu_btns").Joins("JOIN sys_authority_btns ON sys_authority_btns.sys_base_menu_btn_id = sys_base_menu_btns.id").Where("sys_authority_btns.authority_id = ? AND sys_authority_btns.sys_menu_id IN ?", authorityID, menuIDs).Find(&buttons).Error; err != nil {
if err := r.data.DB().WithContext(ctx).Model(&menuButtonPO{}).Joins("JOIN sys_authority_btns ON sys_authority_btns.sys_base_menu_btn_id = sys_base_menu_btns.id").Where("sys_authority_btns.authority_id = ? AND sys_authority_btns.sys_menu_id IN ?", authorityID, menuIDs).Find(&buttons).Error; err != nil {
return nil, err
}
for _, button := range buttons {