优化结构
This commit is contained in:
parent
01b5c2f78a
commit
23bc29ce0f
|
|
@ -1,13 +0,0 @@
|
|||
package data
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type auditQueryRepo struct{ data *Data }
|
||||
type auditRecorderRepo struct{ data *Data }
|
||||
type logFileRepo struct{ data *Data }
|
||||
|
||||
func NewAuditRepo(data *Data) biz.AuditQueryRepo { return &auditQueryRepo{data: data} }
|
||||
|
||||
func NewAuditRecorderRepo(data *Data) biz.AuditRecordRepo { return &auditRecorderRepo{data: data} }
|
||||
|
||||
func NewLogFileRepo(data *Data) biz.LogFileRepo { return &logFileRepo{data: data} }
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/integration/storage"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
|
@ -375,7 +376,7 @@ func (d *Data) reloadConfig(ctx context.Context) error {
|
|||
return fmt.Errorf("reload email configuration: %w", err)
|
||||
}
|
||||
next.Admin.Email = emailConfig
|
||||
candidateStorage, err := buildFileStorage(next.Admin)
|
||||
candidateStorage, err := storage.New(next.Admin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reload storage: %w", err)
|
||||
}
|
||||
|
|
@ -410,7 +411,7 @@ func (d *Data) reloadConfig(ctx context.Context) error {
|
|||
}
|
||||
d.runtime.Replace(next.Data, next.Admin)
|
||||
if d.storage != nil {
|
||||
d.storage.replace(candidateStorage)
|
||||
d.storage.Replace(candidateStorage)
|
||||
}
|
||||
closeCandidate = false
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// dataAccessLogPO is the infrastructure-side write model used by GORM
|
||||
// callbacks. The system module owns the query repository for the same table.
|
||||
type dataAccessLogPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
EventType, TargetTable, Operation string
|
||||
UserID, AuthorityID uint
|
||||
Scope int
|
||||
RequestID, Method, Path, Detail string
|
||||
}
|
||||
|
||||
func (dataAccessLogPO) TableName() string { return "sys_data_access_logs" }
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
datastorage "kra/internal/data/storage"
|
||||
)
|
||||
|
||||
type reloadableStorage struct {
|
||||
mu sync.RWMutex
|
||||
current biz.FileStorage
|
||||
}
|
||||
|
||||
func NewFileStorage(data *Data) (biz.FileStorage, error) {
|
||||
storage, err := buildFileStorage(data.runtime.Admin())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrapper := &reloadableStorage{current: storage}
|
||||
data.storage = wrapper
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func buildFileStorage(config *conf.AdminBackend) (biz.FileStorage, error) {
|
||||
return datastorage.New(config)
|
||||
}
|
||||
func (s *reloadableStorage) replace(storage biz.FileStorage) {
|
||||
s.mu.Lock()
|
||||
s.current = storage
|
||||
s.mu.Unlock()
|
||||
}
|
||||
func (s *reloadableStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Put(ctx, name, reader)
|
||||
}
|
||||
func (s *reloadableStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Open(ctx, name)
|
||||
}
|
||||
func (s *reloadableStorage) Delete(ctx context.Context, name string) error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Delete(ctx, name)
|
||||
}
|
||||
func (s *reloadableStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Compose(ctx, names, destination)
|
||||
}
|
||||
func (s *reloadableStorage) DeletePrefix(ctx context.Context, prefix string) error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.DeletePrefix(ctx, prefix)
|
||||
}
|
||||
func (s *reloadableStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.List(ctx, prefix, cursor, limit)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package data
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type initializationRepo struct{ data *Data }
|
||||
|
||||
func NewInitializationRepo(data *Data) biz.InitializationRepo {
|
||||
return &initializationRepo{data: data}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/integration/storage"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
|
@ -239,7 +240,7 @@ func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
|
|||
Local: &conf.AdminBackend_Local{StorePath: oldRoot, PathPrefix: "old-files"},
|
||||
Storage: &conf.AdminBackend_Storage{Type: "local"},
|
||||
}
|
||||
currentStorage, err := buildFileStorage(currentAdmin)
|
||||
currentStorage, err := storage.NewReloadable(currentAdmin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -248,7 +249,7 @@ func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
|
|||
d := &Data{
|
||||
runtime: conf.NewRuntime(&conf.Data{}, currentAdmin),
|
||||
gormDB: reloadableDB,
|
||||
storage: &reloadableStorage{current: currentStorage},
|
||||
storage: currentStorage,
|
||||
}
|
||||
d.databaseReady.Store(true)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,5 +24,8 @@ func migrateAll(db *gorm.DB) error {
|
|||
}
|
||||
return datasystem.CurrentDataMigration(db)
|
||||
}},
|
||||
{ID: "202608200003_payment_admin_surface", Migrate: func(db *gorm.DB) error {
|
||||
return datasystem.EnsureAdminSurface(db, datapayment.AdminSurface())
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package payment
|
||||
|
||||
import (
|
||||
"kra/internal/data/system"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Migrate creates the persistence owned by the payment module.
|
||||
func Migrate(db *gorm.DB) error {
|
||||
return db.AutoMigrate(&integrationConfigPO{}, &paymentOrderPO{})
|
||||
}
|
||||
|
||||
// Reconcile seeds disabled configuration rows for all built-in providers.
|
||||
func Reconcile(db *gorm.DB) error {
|
||||
return ensurePaymentIntegrationConfigs(db)
|
||||
}
|
||||
|
||||
// AdminSurface describes the payment-owned entries shown in the system
|
||||
// administration UI. The system module persists these records because it owns
|
||||
// the menu/API/policy tables.
|
||||
func AdminSurface() system.AdminSurface {
|
||||
return system.AdminSurface{
|
||||
Menus: []system.AdminMenu{
|
||||
{Name: "paymentOrders", Path: "paymentOrders", ParentName: "extensions", Component: "view/systemTools/payment/orders.vue", Title: "支付订单", Icon: "wallet", Sort: 6},
|
||||
{Name: "paymentConfig", Path: "paymentConfig", ParentName: "extensions", Component: "view/systemTools/payment/config.vue", Title: "支付配置", Icon: "credit-card", Sort: 7},
|
||||
},
|
||||
APIs: []system.AdminAPI{
|
||||
{Path: "/payment/configs", Method: "GET", APIGroup: "支付", Description: "获取支付渠道配置"},
|
||||
{Path: "/payment/config", Method: "POST", APIGroup: "支付", Description: "保存支付渠道配置"},
|
||||
{Path: "/payment/orders", Method: "GET", APIGroup: "支付", Description: "分页查询支付订单"},
|
||||
{Path: "/payment/order", Method: "POST", APIGroup: "支付", Description: "查询支付订单"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package payment
|
||||
|
||||
import "time"
|
||||
|
||||
const integrationKindPayment = "payment"
|
||||
|
||||
type integrationConfigPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Kind string `gorm:"size:32;not null;uniqueIndex:idx_integration_kind_provider"`
|
||||
Provider string `gorm:"size:64;not null;uniqueIndex:idx_integration_kind_provider"`
|
||||
Enabled bool `gorm:"not null;default:false;index"`
|
||||
Config string `gorm:"type:text;not null"`
|
||||
}
|
||||
|
||||
func (integrationConfigPO) TableName() string { return "sys_integration_configs" }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -12,14 +12,14 @@ import (
|
|||
"strings"
|
||||
|
||||
"kra/internal/biz"
|
||||
datapayment "kra/internal/data/payment"
|
||||
datapayment "kra/internal/integration/payment"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type paymentRepo struct{ data *Data }
|
||||
type paymentRepo struct{ data Provider }
|
||||
|
||||
func NewPaymentRepo(data *Data) biz.PaymentRepo { return &paymentRepo{data: data} }
|
||||
func NewPaymentRepo(data Provider) biz.PaymentRepo { return &paymentRepo{data: data} }
|
||||
|
||||
func ensurePaymentIntegrationConfigs(db *gorm.DB) error {
|
||||
for _, provider := range biz.SupportedPaymentProviders {
|
||||
|
|
@ -38,7 +38,7 @@ func ensurePaymentIntegrationConfigs(db *gorm.DB) error {
|
|||
|
||||
func (r *paymentRepo) row(ctx context.Context, provider string) (*integrationConfigPO, map[string]any, error) {
|
||||
var row integrationConfigPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil, biz.ErrPaymentProviderNotFound
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ func (r *paymentRepo) row(ctx context.Context, provider string) (*integrationCon
|
|||
|
||||
func (r *paymentRepo) ListConfigs(ctx context.Context) ([]*biz.PaymentConfig, error) {
|
||||
var rows []integrationConfigPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("kind = ?", integrationKindPayment).Order("provider ASC").Find(&rows).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("kind = ?", integrationKindPayment).Order("provider ASC").Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.PaymentConfig, 0, len(rows))
|
||||
|
|
@ -84,7 +84,7 @@ func (r *paymentRepo) SaveConfig(ctx context.Context, config *biz.PaymentConfig)
|
|||
if err := json.Unmarshal(config.Values, &values); err != nil {
|
||||
return errors.New("支付配置必须是 JSON 对象")
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var row integrationConfigPO
|
||||
err := db.Where("kind = ? AND provider = ?", integrationKindPayment, config.Provider).First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package payment
|
||||
|
||||
import "kra/pkg/paymentkit"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
)
|
||||
|
||||
type paymentOrderPO struct {
|
||||
|
|
@ -64,9 +65,9 @@ type paymentOrderPO struct {
|
|||
|
||||
func (paymentOrderPO) TableName() string { return "pay_orders" }
|
||||
|
||||
type paymentOrderRepo struct{ data *Data }
|
||||
type paymentOrderRepo struct{ data Provider }
|
||||
|
||||
func NewPaymentOrderRepo(data *Data) biz.PaymentOrderRepo {
|
||||
func NewPaymentOrderRepo(data Provider) biz.PaymentOrderRepo {
|
||||
return &paymentOrderRepo{data: data}
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +135,7 @@ func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *biz.Pa
|
|||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var current paymentOrderPO
|
||||
err = db.Where("provider = ? AND trade_no = ?", po.Provider, po.TradeNo).First(¤t).Error
|
||||
if err == nil {
|
||||
|
|
@ -154,7 +155,7 @@ func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *biz.Pa
|
|||
|
||||
func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*biz.PaymentOrder, error) {
|
||||
var po paymentOrderPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrPaymentOrderNotFound
|
||||
}
|
||||
|
|
@ -163,6 +164,41 @@ func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, trade
|
|||
return toBizPaymentOrder(&po), nil
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter biz.PaymentOrderFilter) ([]*biz.PaymentOrder, int64, error) {
|
||||
db := r.data.DB().WithContext(ctx).Model(&paymentOrderPO{})
|
||||
if value := strings.TrimSpace(filter.Provider); value != "" {
|
||||
db = db.Where("provider = ?", value)
|
||||
}
|
||||
if value := strings.TrimSpace(filter.TradeNo); value != "" {
|
||||
db = db.Where("trade_no LIKE ?", "%"+value+"%")
|
||||
}
|
||||
if value := strings.TrimSpace(filter.BusinessType); value != "" {
|
||||
db = db.Where("business_type = ?", value)
|
||||
}
|
||||
if value := strings.TrimSpace(filter.BusinessID); value != "" {
|
||||
db = db.Where("business_id LIKE ?", "%"+value+"%")
|
||||
}
|
||||
if value := strings.TrimSpace(filter.PaymentStatus); value != "" {
|
||||
db = db.Where("payment_status = ?", value)
|
||||
}
|
||||
if value := strings.TrimSpace(filter.RefundStatus); value != "" {
|
||||
db = db.Where("refund_status = ?", value)
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var rows []paymentOrderPO
|
||||
if err := pagination.ApplyRequired(db.Order("id desc"), page, pageSize, 100).Find(&rows).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
items := make([]*biz.PaymentOrder, 0, len(rows))
|
||||
for i := range rows {
|
||||
items = append(items, toBizPaymentOrder(&rows[i]))
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *biz.PaymentProviderUpdate) (*biz.PaymentOrder, error) {
|
||||
return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error {
|
||||
if update == nil {
|
||||
|
|
@ -396,7 +432,7 @@ func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, t
|
|||
|
||||
func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*biz.PaymentOrder, error) {
|
||||
var result *biz.PaymentOrder
|
||||
err := r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var po paymentOrderPO
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -91,3 +91,27 @@ func TestPaymentOrderRepositoryRejectsProviderTradeReuse(t *testing.T) {
|
|||
t.Fatalf("provider trade reuse err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentOrderRepositoryListsWithFilters(t *testing.T) {
|
||||
repo := newPaymentOrderRepoForTest(t)
|
||||
ctx := context.Background()
|
||||
first := testPaymentOrder()
|
||||
if _, _, err := repo.CreatePaymentOrder(ctx, first); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second := testPaymentOrder()
|
||||
second.TradeNo = "wechat-order-2"
|
||||
second.Provider = biz.PaymentWechatV3
|
||||
second.BusinessID = "item-2"
|
||||
second.ConfirmationID = "33333333-3333-3333-3333-333333333333"
|
||||
if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
items, total, err := repo.ListPaymentOrders(ctx, 1, 10, biz.PaymentOrderFilter{Provider: biz.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if total != 1 || len(items) != 1 || items[0].TradeNo != second.TradeNo {
|
||||
t.Fatalf("filtered orders = %#v total=%d", items, total)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package payment
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Provider is the narrow persistence seam required by payment repositories.
|
||||
// Keeping it here lets payment remain an independent data module.
|
||||
type Provider interface {
|
||||
DB() *gorm.DB
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package payment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz"
|
||||
)
|
||||
|
||||
type Data struct{ gormDB *reloadableDB }
|
||||
|
||||
type reloadableDB struct{ db *gorm.DB }
|
||||
|
||||
func newReloadableDB(db *gorm.DB, _ any) *reloadableDB { return &reloadableDB{db: db} }
|
||||
func (r *reloadableDB) close() {
|
||||
if r == nil || r.db == nil {
|
||||
return
|
||||
}
|
||||
if sqlDB, err := r.db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
}
|
||||
func (d *Data) DB() *gorm.DB { return d.gormDB.db }
|
||||
|
||||
func openWithDriver(driver, dsn string) (*gorm.DB, error) {
|
||||
if driver != "sqlite" {
|
||||
return nil, fmt.Errorf("unsupported test database driver %q", driver)
|
||||
}
|
||||
return gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||
}
|
||||
|
||||
func openIntegrationConfigTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&integrationConfigPO{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
})
|
||||
return db
|
||||
}
|
||||
|
||||
func migrateAll(db *gorm.DB) error {
|
||||
if err := db.AutoMigrate(&integrationConfigPO{}, &paymentOrderPO{}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, provider := range biz.SupportedPaymentProviders {
|
||||
var count int64
|
||||
if err := db.Model(&integrationConfigPO{}).Where("kind = ? AND provider = ?", integrationKindPayment, provider).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: provider, Config: "{}"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/gormkit"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -18,17 +20,17 @@ type announcementPO struct {
|
|||
Title string
|
||||
Content string `gorm:"type:text"`
|
||||
UserID *int `gorm:"column:user_id"`
|
||||
Attachments jsonPO
|
||||
Attachments gormkit.JSON
|
||||
}
|
||||
|
||||
func (announcementPO) TableName() string { return "kra_announcements_info" }
|
||||
|
||||
type announcementRepo struct{ data *Data }
|
||||
type announcementRepo struct{ data Provider }
|
||||
|
||||
func NewAnnouncementRepo(data *Data) biz.AnnouncementRepo { return &announcementRepo{data: data} }
|
||||
func NewAnnouncementRepo(data Provider) biz.AnnouncementRepo { return &announcementRepo{data: data} }
|
||||
|
||||
func newAnnouncement(item *biz.Announcement) announcementPO {
|
||||
return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: jsonPO(item.Attachments)}
|
||||
return announcementPO{ID: item.ID, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: gormkit.JSON(item.Attachments)}
|
||||
}
|
||||
|
||||
func announcementToBiz(item announcementPO) *biz.Announcement {
|
||||
|
|
@ -37,7 +39,7 @@ func announcementToBiz(item announcementPO) *biz.Announcement {
|
|||
|
||||
func (r *announcementRepo) Create(ctx context.Context, item *biz.Announcement) error {
|
||||
po := newAnnouncement(item)
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
item.ID, item.CreatedAt, item.UpdatedAt = po.ID, po.CreatedAt, po.UpdatedAt
|
||||
|
|
@ -45,28 +47,28 @@ func (r *announcementRepo) Create(ctx context.Context, item *biz.Announcement) e
|
|||
}
|
||||
|
||||
func (r *announcementRepo) Delete(ctx context.Context, id string) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&announcementPO{}, "id = ?", id).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&announcementPO{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *announcementRepo) DeleteByIDs(ctx context.Context, ids []string) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&[]announcementPO{}, "id IN ?", ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&[]announcementPO{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
func (r *announcementRepo) Update(ctx context.Context, item *biz.Announcement) error {
|
||||
po := newAnnouncement(item)
|
||||
return r.data.gormDB.WithContext(ctx).Model(&announcementPO{}).Where("id = ?", item.ID).Updates(&po).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&announcementPO{}).Where("id = ?", item.ID).Updates(&po).Error
|
||||
}
|
||||
|
||||
func (r *announcementRepo) Find(ctx context.Context, id string) (*biz.Announcement, error) {
|
||||
var po announcementPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return announcementToBiz(po), nil
|
||||
}
|
||||
|
||||
func (r *announcementRepo) List(ctx context.Context, filter biz.AnnouncementFilter) ([]*biz.Announcement, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&announcementPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&announcementPO{})
|
||||
if filter.StartCreatedAt != nil && filter.EndCreatedAt != nil {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", filter.StartCreatedAt, filter.EndCreatedAt)
|
||||
}
|
||||
|
|
@ -75,7 +77,7 @@ func (r *announcementRepo) List(ctx context.Context, filter biz.AnnouncementFilt
|
|||
return nil, 0, err
|
||||
}
|
||||
if filter.PageSize > 0 {
|
||||
db = applyPagination(db, filter.Page, filter.PageSize, 100)
|
||||
db = pagination.Apply(db, filter.Page, filter.PageSize, 100)
|
||||
}
|
||||
var pos []announcementPO
|
||||
if err := db.Find(&pos).Error; err != nil {
|
||||
|
|
@ -95,7 +97,7 @@ func (r *announcementRepo) UserOptions(ctx context.Context) ([]biz.UserOption, e
|
|||
}
|
||||
// The generated data-source endpoint is best effort: return collected
|
||||
// options even when the underlying scan reports an error.
|
||||
_ = r.data.gormDB.WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Scan(&rows).Error
|
||||
_ = r.data.DB().WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Scan(&rows).Error
|
||||
items := make([]biz.UserOption, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, biz.UserOption{Label: row.Label, Value: row.Value})
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -7,13 +7,14 @@ import (
|
|||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type apiRepo struct{ data *Data }
|
||||
type apiRepo struct{ data Provider }
|
||||
|
||||
func NewAPIRepo(data *Data) biz.APIRepo { return &apiRepo{data: data} }
|
||||
func NewAPIRepo(data Provider) biz.APIRepo { return &apiRepo{data: data} }
|
||||
|
||||
type apiPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -52,20 +53,20 @@ func apiFromPO(po apiPO) *biz.API {
|
|||
func (r *apiRepo) CreateAPI(ctx context.Context, v *biz.API) error {
|
||||
po := apiPO{Path: v.Path, Description: v.Description, APIGroup: v.APIGroup, Method: v.Method}
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", po.Path, po.Method).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", po.Path, po.Method).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("存在相同api")
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID, v.CreatedAt, v.UpdatedAt, v.Method = po.ID, po.CreatedAt, po.UpdatedAt, po.Method
|
||||
return nil
|
||||
}
|
||||
func (r *apiRepo) UpdateAPI(ctx context.Context, v *biz.API) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var old apiPO
|
||||
if err := db.First(&old, v.ID).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -90,7 +91,7 @@ func (r *apiRepo) UpdateAPI(ctx context.Context, v *biz.API) error {
|
|||
return db.Model(&old).Updates(map[string]any{"path": v.Path, "description": v.Description, "api_group": v.APIGroup, "method": method}).Error
|
||||
}
|
||||
func (r *apiRepo) DeleteAPIs(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var apis []apiPO
|
||||
if err := tx.Where("id IN ?", ids).Find(&apis).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -110,18 +111,18 @@ func (r *apiRepo) DeleteAPIs(ctx context.Context, ids []uint) error {
|
|||
}
|
||||
func (r *apiRepo) FindAPI(ctx context.Context, id uint) (*biz.API, error) {
|
||||
var po apiPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apiFromPO(po), nil
|
||||
}
|
||||
func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]*biz.API, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&apiPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&apiPO{})
|
||||
if q != nil && q.StrictAll {
|
||||
config := r.data.runtime.Admin()
|
||||
config := r.data.Runtime().Admin()
|
||||
if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth {
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if authority.ParentID != nil && *authority.ParentID != 0 {
|
||||
|
|
@ -167,7 +168,7 @@ func (r *apiRepo) ListAPIs(ctx context.Context, page, size int, q *biz.API) ([]*
|
|||
var pos []apiPO
|
||||
query := db.Order(order)
|
||||
if q == nil || !q.StrictAll {
|
||||
query = applyRequiredPagination(query, page, size, 100)
|
||||
query = pagination.ApplyRequired(query, page, size, 100)
|
||||
}
|
||||
if err := query.Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
func (r *apiRepo) APIRoleIDs(ctx context.Context, path, method string) ([]uint, error) {
|
||||
rows := make([]casbinRulePO, 0)
|
||||
err := policyScope(r.data.gormDB.WithContext(ctx)).
|
||||
err := policyScope(r.data.DB().WithContext(ctx)).
|
||||
Where("v1 = ? AND v2 = ?", path, method).
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
|
|
@ -47,7 +47,7 @@ func (r *apiRepo) SetAPIRoles(ctx context.Context, path, method string, ids []ui
|
|||
return err
|
||||
}
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if strict {
|
||||
authorityIDs := make([]string, 0, len(allowedAuthorities))
|
||||
for authorityID := range allowedAuthorities {
|
||||
|
|
@ -74,11 +74,11 @@ func (r *apiRepo) SetAPIRoles(ctx context.Context, path, method string, ids []ui
|
|||
|
||||
func (r *apiRepo) CheckPolicyStore(ctx context.Context) error {
|
||||
var count int64
|
||||
return r.data.gormDB.WithContext(ctx).Model(&casbinRulePO{}).Count(&count).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&casbinRulePO{}).Count(&count).Error
|
||||
}
|
||||
|
||||
func (r *apiRepo) Authorize(ctx context.Context, aid uint, path, method string) (bool, error) {
|
||||
rows, err := policyRowsForAuthority(r.data.gormDB.WithContext(ctx), aid)
|
||||
rows, err := policyRowsForAuthority(r.data.DB().WithContext(ctx), aid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && r.act == p.act`)
|
|||
return enforcer.Enforce(subject, path, method)
|
||||
}
|
||||
func (r *apiRepo) PolicyPaths(ctx context.Context, aid uint) ([]*biz.API, error) {
|
||||
rows, err := policyRowsForAuthority(r.data.gormDB.WithContext(ctx), aid)
|
||||
rows, err := policyRowsForAuthority(r.data.DB().WithContext(ctx), aid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ func (r *apiRepo) SetPolicyPaths(ctx context.Context, aid uint, paths []*biz.API
|
|||
if err := r.checkPolicyPathsAuth(ctx, paths); err != nil {
|
||||
return err
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
// The reference enforcer removes the old authority policies before it
|
||||
// attempts to add the replacement set. Keep that ordering visible even
|
||||
// though Kra reads policies directly from the database rather than through
|
||||
|
|
@ -161,11 +161,11 @@ func (r *apiRepo) checkPolicyPathsAuth(ctx context.Context, paths []*biz.API) er
|
|||
return err
|
||||
}
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var registered []apiPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Find(®istered).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Find(®istered).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
allowedSet := make(map[string]bool, len(registered))
|
||||
|
|
@ -174,7 +174,7 @@ func (r *apiRepo) checkPolicyPathsAuth(ctx context.Context, paths []*biz.API) er
|
|||
allowedSet[item.Path+"\x00"+item.Method] = true
|
||||
}
|
||||
} else {
|
||||
policies, err := policyRowsForAuthority(r.data.gormDB.WithContext(ctx), actor.AuthorityID)
|
||||
policies, err := policyRowsForAuthority(r.data.DB().WithContext(ctx), actor.AuthorityID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
func (r *apiRepo) IgnoredAPIs(ctx context.Context) ([]*biz.API, error) {
|
||||
var pos []ignoredAPIPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.API, 0, len(pos))
|
||||
|
|
@ -24,12 +24,12 @@ func (r *apiRepo) SetAPIIgnored(ctx context.Context, path, method string, ignore
|
|||
if ignored {
|
||||
// The compatible endpoint creates an ignore row on every request (the table has no
|
||||
// path/method uniqueness constraint); retain that observable behavior.
|
||||
return r.data.gormDB.WithContext(ctx).Create(&po).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&po).Error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Where("path = ? AND method = ?", po.Path, po.Method).Delete(&ignoredAPIPO{}).Error
|
||||
return r.data.DB().WithContext(ctx).Unscoped().Where("path = ? AND method = ?", po.Path, po.Method).Delete(&ignoredAPIPO{}).Error
|
||||
}
|
||||
func (r *apiRepo) ApplyAPISync(ctx context.Context, added, deleted []*biz.API) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if len(added) > 0 {
|
||||
pos := make([]apiPO, 0, len(added))
|
||||
for _, item := range added {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -6,13 +6,14 @@ import (
|
|||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type apiTokenRepo struct{ data *Data }
|
||||
type apiTokenRepo struct{ data Provider }
|
||||
|
||||
func NewAPITokenRepo(data *Data) biz.APITokenRepo { return &apiTokenRepo{data: data} }
|
||||
func NewAPITokenRepo(data Provider) biz.APITokenRepo { return &apiTokenRepo{data: data} }
|
||||
|
||||
type apiTokenPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -41,14 +42,14 @@ func (jwtBlacklistPO) TableName() string { return "jwt_blacklists" }
|
|||
|
||||
func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID uint) (*biz.User, bool, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, userID).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, userID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, false, errors.New("用户不存在")
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
var count int64
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).
|
||||
err := r.data.DB().WithContext(ctx).Model(&userAuthorityPO{}).
|
||||
Joins("JOIN sys_authorities ON sys_authorities.authority_id = sys_user_authority.sys_authority_authority_id").
|
||||
Where("sys_user_authority.sys_user_id = ? AND sys_user_authority.sys_authority_authority_id = ? AND sys_authorities.deleted_at IS NULL", userID, authorityID).
|
||||
Count(&count).Error
|
||||
|
|
@ -60,7 +61,7 @@ func (r *apiTokenRepo) UserHasAuthority(ctx context.Context, userID, authorityID
|
|||
}
|
||||
func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) error {
|
||||
po := apiTokenPO{UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
|
|
@ -68,7 +69,7 @@ func (r *apiTokenRepo) CreateAPIToken(ctx context.Context, v *biz.APIToken) erro
|
|||
return nil
|
||||
}
|
||||
func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*biz.APIToken, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&apiTokenPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&apiTokenPO{})
|
||||
if userID != 0 {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
}
|
||||
|
|
@ -80,7 +81,7 @@ func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []apiTokenPO
|
||||
if err := applyRequiredPagination(db.Order("created_at desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.ApplyRequired(db.Order("created_at desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
userIDs := make([]uint, 0, len(pos))
|
||||
|
|
@ -89,7 +90,7 @@ func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID
|
|||
}
|
||||
var userPOs []userPO
|
||||
if len(userIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", userIDs).Find(&userPOs).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id IN ?", userIDs).Find(&userPOs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
|
@ -106,10 +107,10 @@ func (r *apiTokenRepo) ListAPITokens(ctx context.Context, page, size int, userID
|
|||
}
|
||||
func (r *apiTokenRepo) DisableAPIToken(ctx context.Context, id uint) (string, error) {
|
||||
var po apiTokenPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
return po.Token, r.data.gormDB.WithContext(ctx).Model(&po).Update("status", false).Error
|
||||
return po.Token, r.data.DB().WithContext(ctx).Model(&po).Update("status", false).Error
|
||||
}
|
||||
func (r *apiTokenRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint) error {
|
||||
// Persist the blacklist row first, then mark the
|
||||
|
|
@ -117,20 +118,20 @@ func (r *apiTokenRepo) DisableAndBlacklistAPIToken(ctx context.Context, id uint)
|
|||
// the reference implementation, so a storage error has the same observable
|
||||
// result here.
|
||||
var po apiTokenPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.BlacklistToken(ctx, po.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&po).Update("status", false).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&po).Update("status", false).Error
|
||||
}
|
||||
func (r *apiTokenRepo) BlacklistToken(ctx context.Context, token string) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&jwtBlacklistPO{JWT: token}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&jwtBlacklistPO{JWT: token}).Error
|
||||
}
|
||||
func (r *apiTokenRepo) IsTokenDisabled(ctx context.Context, token string) (bool, error) {
|
||||
var blacklistCount int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&jwtBlacklistPO{}).Where("jwt = ?", token).Count(&blacklistCount).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&jwtBlacklistPO{}).Where("jwt = ?", token).Count(&blacklistCount).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
// The JWT middleware consults only jwt_blacklists and lets JWT parsing
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package system
|
||||
|
||||
import "kra/internal/biz"
|
||||
|
||||
type auditQueryRepo struct{ data Provider }
|
||||
type auditRecorderRepo struct{ data Provider }
|
||||
type logFileRepo struct{ data Provider }
|
||||
|
||||
func NewAuditRepo(data Provider) biz.AuditQueryRepo { return &auditQueryRepo{data: data} }
|
||||
|
||||
func NewAuditRecorderRepo(data Provider) biz.AuditRecordRepo { return &auditRecorderRepo{data: data} }
|
||||
|
||||
func NewLogFileRepo(data Provider) biz.LogFileRepo { return &logFileRepo{data: data} }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -9,9 +9,11 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type authorityAccessRepo struct{ data *Data }
|
||||
var errInvalidDataScope = errors.New("数据权限范围不合法")
|
||||
|
||||
func NewAuthorityAccessRepo(data *Data) biz.AuthorityAccessRepo {
|
||||
type authorityAccessRepo struct{ data Provider }
|
||||
|
||||
func NewAuthorityAccessRepo(data Provider) biz.AuthorityAccessRepo {
|
||||
return &authorityAccessRepo{data: data}
|
||||
}
|
||||
|
||||
|
|
@ -21,11 +23,11 @@ func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID ui
|
|||
return allowed, nil
|
||||
}
|
||||
var actor authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", actorID).First(&actor).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actorID).First(&actor).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var authorities []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Find(&authorities).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Find(&authorities).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
children := make(map[uint][]uint)
|
||||
|
|
@ -57,7 +59,7 @@ func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID ui
|
|||
}
|
||||
|
||||
func (r *authorityAccessRepo) strictAuthorityAccess(ctx context.Context) (biz.Actor, map[uint]bool, bool, error) {
|
||||
config := r.data.runtime.Admin()
|
||||
config := r.data.Runtime().Admin()
|
||||
if config == nil || config.System == nil || !config.System.UseStrictAuth {
|
||||
return biz.Actor{}, nil, false, nil
|
||||
}
|
||||
|
|
@ -118,7 +120,7 @@ func (r *authorityAccessRepo) ensureAuthorityParentAcyclic(ctx context.Context,
|
|||
}
|
||||
visited[current] = struct{}{}
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("authority_id", "parent_id").Where("authority_id = ?", current).First(&authority).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("authority_id", "parent_id").Where("authority_id = ?", current).First(&authority).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if authority.ParentID == nil {
|
||||
|
|
@ -148,7 +150,7 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au
|
|||
return err
|
||||
}
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// The administration contract checks existence with First and treats every result other than
|
||||
// ErrRecordNotFound as a duplicate-role error. Keep that precedence so
|
||||
// duplicate IDs win over strict-tree validation and the API message stays
|
||||
|
|
@ -220,7 +222,7 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// Reject a duplicate target ID before performing the
|
||||
// hierarchy/Casbin checks that happen later in UpdateCasbin.
|
||||
var existing authorityPO
|
||||
|
|
@ -393,7 +395,7 @@ func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Au
|
|||
if err = r.ensureAuthorityParentAcyclic(ctx, value.AuthorityID, value.ParentID); err != nil {
|
||||
return err
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var current authorityPO
|
||||
if err := db.Where("authority_id = ?", value.AuthorityID).First(¤t).Error; err != nil {
|
||||
return errors.New("查询角色数据失败")
|
||||
|
|
@ -405,7 +407,7 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
|
|||
if err := r.checkAuthorityIDAuth(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var authority authorityPO
|
||||
if err := tx.Where("authority_id = ?", id).First(&authority).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
|
@ -463,9 +465,9 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
|
|||
})
|
||||
}
|
||||
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var allowed map[uint]bool
|
||||
config := r.data.runtime.Admin()
|
||||
config := r.data.Runtime().Admin()
|
||||
if actor, ok := biz.ActorFromContext(ctx); ok {
|
||||
// The current authority is loaded even when strict mode is disabled;
|
||||
// an invalid token authority therefore fails the list request instead of
|
||||
|
|
@ -504,7 +506,7 @@ func (r *authorityAccessRepo) SetAuthorityUsers(ctx context.Context, id uint, id
|
|||
if strict && !allowed[id] {
|
||||
return errors.New("您提交的角色ID不合法")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var oldIDs []uint
|
||||
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &oldIDs).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -609,7 +611,7 @@ func (r *authorityAccessRepo) checkUserIDAuth(ctx context.Context, id uint, allo
|
|||
if allowSelf && actor.UserID != 0 && actor.UserID == id {
|
||||
return nil
|
||||
}
|
||||
return checkManagedUserIDs(r.data.gormDB.WithContext(ctx), []uint{id}, allowed, true)
|
||||
return checkManagedUserIDs(r.data.DB().WithContext(ctx), []uint{id}, allowed, true)
|
||||
}
|
||||
|
||||
func (r *authorityAccessRepo) checkDepartmentIDsAuth(ctx context.Context, ids []uint) error {
|
||||
|
|
@ -621,7 +623,7 @@ func (r *authorityAccessRepo) checkDepartmentIDsAuth(ctx context.Context, ids []
|
|||
return nil
|
||||
}
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if authority.DataScope == 1 {
|
||||
|
|
@ -645,7 +647,7 @@ func (r *authorityAccessRepo) checkDepartmentIDsAuth(ctx context.Context, ids []
|
|||
|
||||
func (r *authorityAccessRepo) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptIDs []uint) error {
|
||||
|
|
@ -658,7 +660,7 @@ func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope i
|
|||
if err := r.checkDataScopeGrant(ctx, scope, deptIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", id).Update("data_scope", scope).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -693,7 +695,7 @@ func (r *authorityAccessRepo) checkDataScopeGrant(ctx context.Context, requested
|
|||
}
|
||||
if len(requestedDepartments) > 0 {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("id IN ?", departmentIDs).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("id IN ?", departmentIDs).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count != int64(len(requestedDepartments)) {
|
||||
|
|
@ -705,7 +707,7 @@ func (r *authorityAccessRepo) checkDataScopeGrant(ctx context.Context, requested
|
|||
return nil
|
||||
}
|
||||
var actorAuthority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&actorAuthority).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&actorAuthority).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
grantable := false
|
||||
|
|
@ -747,18 +749,18 @@ func (r *authorityAccessRepo) checkDataScopeGrant(ctx context.Context, requested
|
|||
|
||||
func (r *authorityAccessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityDepartmentPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_department_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&authorityDepartmentPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_department_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {
|
||||
identity := biz.DataScope{UserID: userID, AuthorityID: authorityID}
|
||||
var user userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("id", "dept_id").First(&user, userID).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("id", "dept_id").First(&user, userID).Error; err != nil {
|
||||
return identity, err
|
||||
}
|
||||
identity.PrimaryDeptID = user.DeptID
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("authority_id", "data_scope").First(&authority, "authority_id = ?", authorityID).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").First(&authority, "authority_id = ?", authorityID).Error; err != nil {
|
||||
return identity, err
|
||||
}
|
||||
identity.Scope = authority.DataScope
|
||||
|
|
@ -772,7 +774,7 @@ func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID,
|
|||
var ids []uint
|
||||
selected := make(map[uint]bool)
|
||||
if identity.Scope == 2 || identity.Scope == 3 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_user_id = ?", userID).Pluck("sys_department_id", &ids).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_user_id = ?", userID).Pluck("sys_department_id", &ids).Error; err != nil {
|
||||
return identity, err
|
||||
}
|
||||
selected = make(map[uint]bool, len(ids)+1)
|
||||
|
|
@ -792,7 +794,7 @@ func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID,
|
|||
// parent_id edges at read time, rather than trusting the denormalized
|
||||
// ancestors string (which may be stale after a department move).
|
||||
var departments []departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("id", "parent_id").Find(&departments).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("id", "parent_id").Find(&departments).Error; err != nil {
|
||||
return identity, err
|
||||
}
|
||||
children := make(map[uint][]uint, len(departments))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -10,6 +10,31 @@ type IgnoredAPI struct {
|
|||
Path string
|
||||
}
|
||||
|
||||
// AdminMenu describes a menu entry contributed by a module. ParentName is
|
||||
// resolved by the system persistence layer so feature modules do not need to
|
||||
// know the menu PO shape.
|
||||
type AdminMenu struct {
|
||||
Name string
|
||||
Path string
|
||||
ParentName string
|
||||
Component string
|
||||
Title string
|
||||
Icon string
|
||||
Sort int
|
||||
}
|
||||
|
||||
type AdminAPI struct {
|
||||
Path string
|
||||
Method string
|
||||
APIGroup string
|
||||
Description string
|
||||
}
|
||||
|
||||
type AdminSurface struct {
|
||||
Menus []AdminMenu
|
||||
APIs []AdminAPI
|
||||
}
|
||||
|
||||
func DefaultIgnoredAPIs(staticPath string) []IgnoredAPI {
|
||||
staticRoute := "/" + strings.Trim(staticPath, "/") + "/*filepath"
|
||||
return []IgnoredAPI{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -23,13 +24,13 @@ type dataAccessLogPO struct {
|
|||
func (dataAccessLogPO) TableName() string { return "sys_data_access_logs" }
|
||||
|
||||
func (r *auditRecorderRepo) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&dataAccessLogPO{EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&dataAccessLogPO{EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail}).Error
|
||||
}
|
||||
func dataAccessFromPO(v dataAccessLogPO) *biz.DataAccessLog {
|
||||
return &biz.DataAccessLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail}
|
||||
}
|
||||
func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*biz.DataAccessLog, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dataAccessLogPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&dataAccessLogPO{})
|
||||
if q != nil {
|
||||
if q.EventType != "" {
|
||||
db = db.Where("event_type = ?", q.EventType)
|
||||
|
|
@ -43,7 +44,7 @@ func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []dataAccessLogPO
|
||||
if err := applyRequiredPagination(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.DataAccessLog, 0, len(pos))
|
||||
|
|
@ -53,5 +54,5 @@ func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *
|
|||
return out, total, nil
|
||||
}
|
||||
func (r *auditQueryRepo) DeleteDataAccess(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&dataAccessLogPO{}, "id IN ?", ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&dataAccessLogPO{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -12,9 +12,9 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type departmentRepo struct{ data *Data }
|
||||
type departmentRepo struct{ data Provider }
|
||||
|
||||
func NewDepartmentRepo(data *Data) biz.DepartmentRepo { return &departmentRepo{data: data} }
|
||||
func NewDepartmentRepo(data Provider) biz.DepartmentRepo { return &departmentRepo{data: data} }
|
||||
|
||||
type departmentPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -62,7 +62,7 @@ func (r *departmentRepo) attachDepartmentLeaders(ctx context.Context, department
|
|||
return nil
|
||||
}
|
||||
var leaders []userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", leaderIDs).Find(&leaders).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id IN ?", leaderIDs).Find(&leaders).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
leaderByID := make(map[uint]*biz.User, len(leaders))
|
||||
|
|
@ -78,7 +78,7 @@ func (r *departmentRepo) CreateDepartment(ctx context.Context, v *biz.Department
|
|||
v.Ancestors = "0"
|
||||
if v.ParentID != 0 {
|
||||
var parent departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&parent, v.ParentID).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&parent, v.ParentID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("父部门不存在")
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ func (r *departmentRepo) CreateDepartment(ctx context.Context, v *biz.Department
|
|||
}
|
||||
v.Ancestors = strings.Trim(parent.Ancestors+","+strconv.FormatUint(uint64(parent.ID), 10), ",")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error
|
||||
}
|
||||
func (r *departmentRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error {
|
||||
if v.ParentID == v.ID {
|
||||
|
|
@ -95,7 +95,7 @@ func (r *departmentRepo) UpdateDepartment(ctx context.Context, v *biz.Department
|
|||
v.Ancestors = "0"
|
||||
if v.ParentID != 0 {
|
||||
var parent departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&parent, v.ParentID).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&parent, v.ParentID).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("父部门不存在")
|
||||
}
|
||||
|
|
@ -103,36 +103,36 @@ func (r *departmentRepo) UpdateDepartment(ctx context.Context, v *biz.Department
|
|||
}
|
||||
v.Ancestors = strings.Trim(parent.Ancestors+","+strconv.FormatUint(uint64(parent.ID), 10), ",")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error
|
||||
}
|
||||
func (r *departmentRepo) DeleteDepartment(ctx context.Context, id uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("部门ID不能为空")
|
||||
}
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&departmentPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("存在子部门,不允许删除")
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("dept_id = ?", id).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&userPO{}).Where("dept_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该部门下存在用户,不允许删除")
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该部门下存在用户,不允许删除")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&departmentPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&departmentPO{}, id).Error
|
||||
}
|
||||
func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) {
|
||||
var po departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
department := deptFromPO(po)
|
||||
|
|
@ -143,7 +143,7 @@ func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Depa
|
|||
}
|
||||
func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
||||
var pos []departmentPO
|
||||
db := r.data.gormDB.WithContext(ctx).Order("sort")
|
||||
db := r.data.DB().WithContext(ctx).Order("sort")
|
||||
if name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
|
|
@ -182,7 +182,7 @@ func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*b
|
|||
}
|
||||
func (r *departmentRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *departmentRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
|
|
@ -192,7 +192,7 @@ func (r *departmentRepo) replaceUserDepartments(ctx context.Context, id uint, id
|
|||
if id == 0 {
|
||||
return errors.New("部门ID不能为空")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var current []uint
|
||||
if err := tx.Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", ¤t).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -233,7 +233,7 @@ func (r *departmentRepo) replaceUserDepartments(ctx context.Context, id uint, id
|
|||
})
|
||||
}
|
||||
func (r *departmentRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var user userPO
|
||||
if err := tx.First(&user, uid).Error; err != nil {
|
||||
return errors.New("查询用户数据失败")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -8,13 +8,14 @@ import (
|
|||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type dictionaryRepo struct{ data *Data }
|
||||
type dictionaryRepo struct{ data Provider }
|
||||
|
||||
func NewDictionaryRepo(data *Data) biz.DictionaryRepo { return &dictionaryRepo{data: data} }
|
||||
func NewDictionaryRepo(data Provider) biz.DictionaryRepo { return &dictionaryRepo{data: data} }
|
||||
|
||||
type dictionaryPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -60,13 +61,13 @@ func parameterFromPO(po parameterPO) *biz.SystemParameter {
|
|||
|
||||
func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary) error {
|
||||
var existing dictionaryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("type = ?", v.Type).First(&existing).Error; err == nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("type = ?", v.Type).First(&existing).Error; err == nil {
|
||||
return errors.New("存在相同的type,不允许创建")
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
po := dictionaryPO{Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
|
|
@ -76,10 +77,10 @@ func (r *dictionaryRepo) CreateDictionary(ctx context.Context, v *biz.Dictionary
|
|||
|
||||
func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *biz.Dictionary, details []*biz.DictionaryDetail) error {
|
||||
var existing dictionaryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("type = ?", dictionary.Type).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err := r.data.DB().WithContext(ctx).Where("type = ?", dictionary.Type).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("存在相同的type,不允许导入")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := dictionaryPO{Name: dictionary.Name, Type: dictionary.Type, Status: dictionary.Status, Desc: dictionary.Desc, ParentID: dictionary.ParentID}
|
||||
if err := tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -119,7 +120,7 @@ func (r *dictionaryRepo) ImportDictionary(ctx context.Context, dictionary *biz.D
|
|||
})
|
||||
}
|
||||
func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var current dictionaryPO
|
||||
if err := db.Where("id = ?", v.ID).First(¤t).Error; err != nil {
|
||||
return errors.New("查询字典数据失败")
|
||||
|
|
@ -148,7 +149,7 @@ func (r *dictionaryRepo) checkDictionaryCircularReference(ctx context.Context, c
|
|||
return errors.New("不能将字典设置为自己的父级")
|
||||
}
|
||||
var parent dictionaryPO
|
||||
err := r.data.gormDB.WithContext(ctx).Where("id = ?", parentID).First(&parent).Error
|
||||
err := r.data.DB().WithContext(ctx).Where("id = ?", parentID).First(&parent).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -161,7 +162,7 @@ func (r *dictionaryRepo) checkDictionaryCircularReference(ctx context.Context, c
|
|||
return nil
|
||||
}
|
||||
func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var dictionary dictionaryPO
|
||||
if err := db.First(&dictionary, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
|
@ -176,7 +177,7 @@ func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
|||
}
|
||||
func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
|
||||
var po dictionaryPO
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
active := true
|
||||
if status != nil {
|
||||
active = *status
|
||||
|
|
@ -201,12 +202,12 @@ func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string
|
|||
|
||||
func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Dictionary, error) {
|
||||
var po dictionaryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := dictionaryFromPO(po)
|
||||
var detailPOs []dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_dictionary_id = ?", id).Order("sort").Find(&detailPOs).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ?", id).Order("sort").Find(&detailPOs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value.Details = make([]*biz.DictionaryDetail, 0, len(detailPOs))
|
||||
|
|
@ -216,7 +217,7 @@ func (r *dictionaryRepo) ExportDictionary(ctx context.Context, id uint) (*biz.Di
|
|||
return value, nil
|
||||
}
|
||||
func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*biz.Dictionary, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&dictionaryPO{})
|
||||
if name != "" {
|
||||
like := "%" + name + "%"
|
||||
db = db.Where("name LIKE ? OR type LIKE ?", like, like)
|
||||
|
|
@ -245,7 +246,7 @@ func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, n
|
|||
childrenByParent := make(map[uint][]dictionaryPO)
|
||||
if !details && len(parentIDs) > 0 {
|
||||
var children []dictionaryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("parent_id IN ?", parentIDs).Find(&children).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("parent_id IN ?", parentIDs).Find(&children).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, child := range children {
|
||||
|
|
@ -257,7 +258,7 @@ func (r *dictionaryRepo) ListDictionaries(ctx context.Context, page, size int, n
|
|||
detailsByDictionary := make(map[uint][]dictionaryDetailPO)
|
||||
if details && len(dictionaryIDs) > 0 {
|
||||
var values []dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_dictionary_id IN ?", dictionaryIDs).Order("sort").Find(&values).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id IN ?", dictionaryIDs).Order("sort").Find(&values).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, value := range values {
|
||||
|
|
@ -289,13 +290,13 @@ func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *biz.Dict
|
|||
po.Path = ""
|
||||
if v.ParentID != nil {
|
||||
var parent dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&parent, *v.ParentID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
po.Level = parent.Level + 1
|
||||
po.Path = strings.Trim(strings.Join([]string{parent.Path, fmt.Sprint(parent.ID)}, ","), ",")
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
|
|
@ -305,7 +306,7 @@ func (r *dictionaryRepo) CreateDictionaryDetail(ctx context.Context, v *biz.Dict
|
|||
}
|
||||
func (r *dictionaryRepo) UpdateDictionaryDetail(ctx context.Context, v *biz.DictionaryDetail) error {
|
||||
var po dictionaryDetailPO
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
if err := db.First(&po, v.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -350,23 +351,23 @@ func (r *dictionaryRepo) updateDictionaryDetailChildren(db *gorm.DB, parentID ui
|
|||
}
|
||||
func (r *dictionaryRepo) DeleteDictionaryDetail(ctx context.Context, id uint) error {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&dictionaryDetailPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该字典详情下还有子项,无法删除")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&dictionaryDetailPO{}, id).Error
|
||||
}
|
||||
func (r *dictionaryRepo) FindDictionaryDetail(ctx context.Context, id uint) (*biz.DictionaryDetail, error) {
|
||||
var po dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return detailFromPO(po), nil
|
||||
}
|
||||
func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*biz.DictionaryDetail, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&dictionaryDetailPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&dictionaryDetailPO{})
|
||||
if filter.DictionaryID != 0 {
|
||||
db = db.Where("sys_dictionary_id = ?", filter.DictionaryID)
|
||||
}
|
||||
|
|
@ -390,7 +391,7 @@ func (r *dictionaryRepo) ListDictionaryDetails(ctx context.Context, page, size i
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []dictionaryDetailPO
|
||||
if err := applyRequiredPagination(db.Order("sort,id"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.ApplyRequired(db.Order("sort,id"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.DictionaryDetail, 0, len(pos))
|
||||
|
|
@ -405,13 +406,13 @@ func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID
|
|||
// public dictionary lookup, it does not require the dictionary itself to
|
||||
// be enabled.
|
||||
var dictionary dictionaryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("type = ?", typ).First(&dictionary).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("type = ?", typ).First(&dictionary).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dictionaryID = dictionary.ID
|
||||
}
|
||||
var pos []dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_dictionary_id = ? AND parent_id IS NULL", dictionaryID).Order("sort").Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ? AND parent_id IS NULL", dictionaryID).Order("sort").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roots := make([]*biz.DictionaryDetail, 0, len(pos))
|
||||
|
|
@ -426,7 +427,7 @@ func (r *dictionaryRepo) DictionaryDetailTree(ctx context.Context, dictionaryID
|
|||
}
|
||||
|
||||
func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*biz.DictionaryDetail, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Where("sys_dictionary_id = ?", dictionaryID)
|
||||
db := r.data.DB().WithContext(ctx).Where("sys_dictionary_id = ?", dictionaryID)
|
||||
if parentID == nil {
|
||||
db = db.Where("parent_id IS NULL")
|
||||
} else {
|
||||
|
|
@ -451,7 +452,7 @@ func (r *dictionaryRepo) DictionaryDetailsByParent(ctx context.Context, dictiona
|
|||
|
||||
func (r *dictionaryRepo) loadDictionaryDetailChildren(ctx context.Context, parent *biz.DictionaryDetail) error {
|
||||
var pos []dictionaryDetailPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("parent_id = ?", parent.ID).Order("sort").Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("parent_id = ?", parent.ID).Order("sort").Find(&pos).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
parent.Children = make([]*biz.DictionaryDetail, 0, len(pos))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -29,14 +30,14 @@ func errorFromPO(v errorRecordPO) *biz.ErrorRecord {
|
|||
return &biz.ErrorRecord{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}
|
||||
}
|
||||
func (r *auditRecorderRepo) CreateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
if !r.data.databaseReady.Load() {
|
||||
if !r.data.DatabaseReady() {
|
||||
// Silently ignore error records before the database is initialized.
|
||||
return nil
|
||||
}
|
||||
if v.Status == "" {
|
||||
v.Status = "未处理"
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&errorRecordPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&errorRecordPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status}).Error
|
||||
}
|
||||
func (r *auditQueryRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) error {
|
||||
updates := make(map[string]any, 9)
|
||||
|
|
@ -67,20 +68,20 @@ func (r *auditQueryRepo) UpdateError(ctx context.Context, v *biz.ErrorRecord) er
|
|||
if v.Status != "" {
|
||||
updates["status"] = v.Status
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&errorRecordPO{}).Where("id = ?", v.ID).Updates(updates).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&errorRecordPO{}).Where("id = ?", v.ID).Updates(updates).Error
|
||||
}
|
||||
func (r *auditQueryRepo) DeleteErrors(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&errorRecordPO{}, ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&errorRecordPO{}, ids).Error
|
||||
}
|
||||
func (r *auditQueryRepo) FindError(ctx context.Context, id uint) (*biz.ErrorRecord, error) {
|
||||
var po errorRecordPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return errorFromPO(po), nil
|
||||
}
|
||||
func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*biz.ErrorRecord, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&errorRecordPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&errorRecordPO{})
|
||||
if q != nil {
|
||||
if len(q.CreatedAtRange) == 2 {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", q.CreatedAtRange[0], q.CreatedAtRange[1])
|
||||
|
|
@ -97,7 +98,7 @@ func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *biz.
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []errorRecordPO
|
||||
if err := applyPagination(db.Order("created_at desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.Apply(db.Order("created_at desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.ErrorRecord, 0, len(pos))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -57,9 +57,9 @@ type exportJoinPO struct {
|
|||
|
||||
func (exportJoinPO) TableName() string { return "sys_export_template_join" }
|
||||
|
||||
type exportRepo struct{ data *Data }
|
||||
type exportRepo struct{ data Provider }
|
||||
|
||||
func NewExportRepo(data *Data) biz.ExportRepo { return &exportRepo{data: data} }
|
||||
func NewExportRepo(data Provider) biz.ExportRepo { return &exportRepo{data: data} }
|
||||
func exportFromPO(po exportTemplatePO, conditions []exportConditionPO, joins []exportJoinPO) *biz.ExportTemplate {
|
||||
v := &biz.ExportTemplate{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DBName: po.DBName, Name: po.Name, TableName: po.DBTableName, TemplateID: po.TemplateID, TemplateInfo: po.TemplateInfo, SQL: po.SQL, ImportSQL: po.ImportSQL, Limit: po.Limit, Order: po.Order}
|
||||
if conditions != nil {
|
||||
|
|
@ -138,7 +138,7 @@ func (r *exportRepo) saveRelations(tx *gorm.DB, v *biz.ExportTemplate, resetIDs,
|
|||
return nil
|
||||
}
|
||||
func (r *exportRepo) CreateExportTemplate(ctx context.Context, v *biz.ExportTemplate) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := exportToPO(v)
|
||||
if err := tx.Create(&po).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -148,7 +148,7 @@ func (r *exportRepo) CreateExportTemplate(ctx context.Context, v *biz.ExportTemp
|
|||
})
|
||||
}
|
||||
func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemplate) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
po := exportToPO(v)
|
||||
if err := tx.Model(&exportTemplatePO{}).Where("id = ?", v.ID).Updates(&po).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -157,11 +157,11 @@ func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemp
|
|||
})
|
||||
}
|
||||
func (r *exportRepo) DeleteExportTemplates(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&[]exportTemplatePO{}, "id IN ?", ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&[]exportTemplatePO{}, "id IN ?", ids).Error
|
||||
}
|
||||
func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*biz.ExportTemplate, error) {
|
||||
var po exportTemplatePO
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var err error
|
||||
if tid != "" {
|
||||
err = db.Where("template_id = ?", tid).First(&po).Error
|
||||
|
|
@ -182,7 +182,7 @@ func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string
|
|||
return exportFromPO(po, conditions, joins), nil
|
||||
}
|
||||
func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*biz.ExportTemplate, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&exportTemplatePO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&exportTemplatePO{})
|
||||
if q != nil {
|
||||
if q.StartCreatedAt != nil && q.EndCreatedAt != nil {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", q.StartCreatedAt, q.EndCreatedAt)
|
||||
|
|
@ -218,7 +218,7 @@ func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q
|
|||
}
|
||||
|
||||
func (r *exportRepo) QueryExport(ctx context.Context, t *biz.ExportTemplate, params map[string]string) ([]map[string]any, string, error) {
|
||||
selected, err := r.data.database(t.DBName)
|
||||
selected, err := r.data.Database(t.DBName)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
@ -355,7 +355,7 @@ func (r *exportRepo) PreviewExport(ctx context.Context, t *biz.ExportTemplate, p
|
|||
wheres := make([]string, 0)
|
||||
if params["filterDeleted"] == "true" {
|
||||
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", t.TableName))
|
||||
selected, err := r.data.database(t.DBName)
|
||||
selected, err := r.data.Database(t.DBName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -442,7 +442,7 @@ func (r *exportRepo) ImportExportRows(ctx context.Context, t *biz.ExportTemplate
|
|||
// is still treated as custom SQL and is allowed to return the driver's
|
||||
// native error instead of silently falling back to GORM insertion.
|
||||
sql := t.ImportSQL
|
||||
selected, err := r.data.database(t.DBName)
|
||||
selected, err := r.data.Database(t.DBName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -24,7 +24,7 @@ const (
|
|||
)
|
||||
|
||||
func (r *logFileRepo) configuredLogRoot() (root string, exists bool, err error) {
|
||||
admin := r.data.runtime.Admin()
|
||||
admin := r.data.Runtime().Admin()
|
||||
if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" {
|
||||
return "", false, biz.ErrLogRootUnavailable
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -23,18 +24,18 @@ type loginLogPO struct {
|
|||
func (loginLogPO) TableName() string { return "sys_login_logs" }
|
||||
|
||||
func (r *auditRecorderRepo) RecordLogin(ctx context.Context, v *biz.LoginLog) error {
|
||||
if !r.data.databaseReady.Load() {
|
||||
if !r.data.DatabaseReady() {
|
||||
// The login endpoint remains reachable before database initialization;
|
||||
// skip the audit write until storage is ready.
|
||||
return nil
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&loginLogPO{Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&loginLogPO{Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}).Error
|
||||
}
|
||||
func loginFromPO(v loginLogPO) *biz.LoginLog {
|
||||
return &biz.LoginLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID}
|
||||
}
|
||||
func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*biz.LoginLog, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&loginLogPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&loginLogPO{})
|
||||
if q != nil {
|
||||
if q.Username != "" {
|
||||
db = db.Where("username LIKE ?", "%"+q.Username+"%")
|
||||
|
|
@ -48,7 +49,7 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz.
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []loginLogPO
|
||||
if err := applyRequiredPagination(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
ids := make([]uint, 0, len(pos))
|
||||
|
|
@ -57,7 +58,7 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz.
|
|||
ids = append(ids, value.UserID)
|
||||
}
|
||||
}
|
||||
users := auditUsers(ctx, r.data.gormDB.WithContext(ctx), ids)
|
||||
users := auditUsers(ctx, r.data.DB().WithContext(ctx), ids)
|
||||
out := make([]*biz.LoginLog, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
value := loginFromPO(po)
|
||||
|
|
@ -68,14 +69,14 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *biz.
|
|||
}
|
||||
func (r *auditQueryRepo) FindLogin(ctx context.Context, id uint) (*biz.LoginLog, error) {
|
||||
var po loginLogPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loginFromPO(po), nil
|
||||
}
|
||||
func (r *auditQueryRepo) DeleteLogins(ctx context.Context, ids []int) error {
|
||||
if len(ids) == 1 && ids[0] == 0 {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&loginLogPO{}).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&loginLogPO{}).Error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&loginLogPO{}, "id IN ?", ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&loginLogPO{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -39,9 +39,9 @@ type categoryPO struct {
|
|||
|
||||
func (categoryPO) TableName() string { return "media_attachment_category" }
|
||||
|
||||
type mediaRepo struct{ data *Data }
|
||||
type mediaRepo struct{ data Provider }
|
||||
|
||||
func NewMediaRepo(data *Data) biz.MediaRepo { return &mediaRepo{data: data} }
|
||||
func NewMediaRepo(data Provider) biz.MediaRepo { return &mediaRepo{data: data} }
|
||||
func mediaFromPO(v mediaPO) *biz.MediaFile {
|
||||
return &biz.MediaFile{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, CategoryID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ func mediaToPO(v *biz.MediaFile) mediaPO {
|
|||
}
|
||||
func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error {
|
||||
po := mediaToPO(v)
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
|
|
@ -60,20 +60,20 @@ func (r *mediaRepo) CreateMedia(ctx context.Context, v *biz.MediaFile) error {
|
|||
}
|
||||
func (r *mediaRepo) FindMedia(ctx context.Context, id uint) (*biz.MediaFile, error) {
|
||||
var po mediaPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mediaFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) FindMediaByHash(ctx context.Context, userID uint, hash string) (*biz.MediaFile, error) {
|
||||
var po mediaPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND md5 = ?", userID, hash).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND md5 = ?", userID, hash).First(&po).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mediaFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) ListMedia(ctx context.Context, filter biz.MediaFilter) ([]*biz.MediaFile, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&mediaPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&mediaPO{})
|
||||
if filter.Keyword != "" {
|
||||
db = db.Where("name LIKE ?", "%"+filter.Keyword+"%")
|
||||
}
|
||||
|
|
@ -129,17 +129,17 @@ func (r *mediaRepo) UpdateMediaName(ctx context.Context, id uint, name string) e
|
|||
// The compatible flow performs a First before Update, so a missing record returns a
|
||||
// not-found error instead of silently succeeding with RowsAffected=0.
|
||||
var po mediaPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&po).Update("name", name).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&po).Update("name", name).Error
|
||||
}
|
||||
func (r *mediaRepo) DeleteMedia(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&mediaPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Unscoped().Delete(&mediaPO{}, id).Error
|
||||
}
|
||||
func (r *mediaRepo) MediaKeyReferences(ctx context.Context, key string) (int64, error) {
|
||||
var count int64
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&mediaPO{}).Where(map[string]any{"key": key}).Count(&count).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&mediaPO{}).Where(map[string]any{"key": key}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*biz.MediaFile) error {
|
||||
|
|
@ -147,10 +147,10 @@ func (r *mediaRepo) CreateMediaBatch(ctx context.Context, items []*biz.MediaFile
|
|||
for _, v := range items {
|
||||
pos = append(pos, mediaToPO(v))
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Create(&pos).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&pos).Error
|
||||
}
|
||||
func (r *mediaRepo) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var count int64
|
||||
query := db.Model(&categoryPO{}).Where("name = ? AND pid = ?", v.Name, v.ParentID)
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
|
|
@ -171,17 +171,17 @@ func (r *mediaRepo) SaveCategory(ctx context.Context, v *biz.AttachmentCategory)
|
|||
}
|
||||
func (r *mediaRepo) DeleteCategory(ctx context.Context, id uint) error {
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&categoryPO{}).Where("pid = ?", id).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&categoryPO{}).Where("pid = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("请先删除子级")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Unscoped().Delete(&categoryPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Unscoped().Delete(&categoryPO{}, id).Error
|
||||
}
|
||||
func (r *mediaRepo) ListCategories(ctx context.Context) ([]*biz.AttachmentCategory, error) {
|
||||
var pos []categoryPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byID := map[uint]*biz.AttachmentCategory{}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -47,7 +47,7 @@ func uploadFromPO(v uploadSessionPO) *biz.UploadSession {
|
|||
}
|
||||
func (r *mediaRepo) FindCompletedSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) {
|
||||
var po uploadSessionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "completed").First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "completed").First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrUploadSessionNotFound
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ func (r *mediaRepo) FindCompletedSession(ctx context.Context, userID uint, hash
|
|||
}
|
||||
func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash string) (*biz.UploadSession, error) {
|
||||
var po uploadSessionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "uploading").First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("user_id = ? AND file_hash = ? AND status = ?", userID, hash, "uploading").First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrUploadSessionNotFound
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ func (r *mediaRepo) FindUploadingSession(ctx context.Context, userID uint, hash
|
|||
}
|
||||
func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *biz.UploadSession) error {
|
||||
po := uploadSessionPO{UserID: v.UserID, FileName: v.FileName, FileHash: v.FileHash, FileSize: v.FileSize, ChunkSize: v.ChunkSize, ChunkTotal: v.ChunkTotal, Status: v.Status}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
|
|
@ -75,36 +75,36 @@ func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *biz.UploadSessio
|
|||
}
|
||||
func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*biz.UploadSession, error) {
|
||||
var po uploadSessionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uploadFromPO(po), nil
|
||||
}
|
||||
func (r *mediaRepo) ClaimUploadSession(ctx context.Context, id uint) (bool, error) {
|
||||
result := r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ? AND status = ?", id, "uploading").Update("status", "merging")
|
||||
result := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ? AND status = ?", id, "uploading").Update("status", "merging")
|
||||
return result.RowsAffected == 1, result.Error
|
||||
}
|
||||
func (r *mediaRepo) FailUploadSession(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Update("status", "failed").Error
|
||||
return r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Update("status", "failed").Error
|
||||
}
|
||||
func (r *mediaRepo) CompleteUploadSession(ctx context.Context, id uint, key string, mediaID uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("id = ?", id).Updates(map[string]any{"status": "completed", "storage_key": key, "media_id": mediaID}).Error
|
||||
}
|
||||
func (r *mediaRepo) DeleteUploadSession(ctx context.Context, id uint) error {
|
||||
// The compatible flow uses GORM's normal Delete here, retaining the soft-deleted session
|
||||
// for audit/recovery rather than physically removing it.
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&uploadSessionPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&uploadSessionPO{}, id).Error
|
||||
}
|
||||
func (r *mediaRepo) UpsertChunk(ctx context.Context, uploadID uint, v *biz.UploadChunk) error {
|
||||
po := uploadChunkPO{UploadID: uploadID, ChunkIndex: v.Index, ChunkHash: v.Hash, Size: v.Size}
|
||||
return r.data.gormDB.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
return r.data.DB().WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "upload_id"}, {Name: "chunk_index"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"chunk_hash", "size", "updated_at", "deleted_at"}),
|
||||
}).Create(&po).Error
|
||||
}
|
||||
func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.UploadChunk, error) {
|
||||
var pos []uploadChunkPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("upload_id = ?", uploadID).Order("chunk_index").Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("upload_id = ?", uploadID).Order("chunk_index").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.UploadChunk, 0, len(pos))
|
||||
|
|
@ -114,15 +114,15 @@ func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*biz.Uploa
|
|||
return out, nil
|
||||
}
|
||||
func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error
|
||||
return r.data.DB().WithContext(ctx).Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error
|
||||
}
|
||||
func (r *mediaRepo) StaleUploadSessionIDs(ctx context.Context, before time.Time) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&uploadSessionPO{}).Where("status = ? AND updated_at < ?", "uploading", before).Pluck("id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("status = ? AND updated_at < ?", "uploading", before).Pluck("id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *mediaRepo) DeleteUploadData(ctx context.Context, uploadID uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -10,9 +10,9 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type menuRepo struct{ data *Data }
|
||||
type menuRepo struct{ data Provider }
|
||||
|
||||
func NewMenuRepo(data *Data) biz.MenuRepo { return &menuRepo{data: data} }
|
||||
func NewMenuRepo(data Provider) biz.MenuRepo { return &menuRepo{data: data} }
|
||||
|
||||
func menuFromPO(po menuPO) *biz.Menu {
|
||||
var deletedAt *time.Time
|
||||
|
|
@ -58,7 +58,7 @@ func createMenuRelations(tx *gorm.DB, menu *biz.Menu) error {
|
|||
}
|
||||
|
||||
func (r *menuRepo) Create(ctx context.Context, v *biz.Menu) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var count int64
|
||||
if err := tx.Model(&menuPO{}).Where("name = ?", v.Name).Count(&count).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -99,7 +99,7 @@ func (r *menuRepo) Create(ctx context.Context, v *biz.Menu) error {
|
|||
}
|
||||
|
||||
func (r *menuRepo) Update(ctx context.Context, v *biz.Menu) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var old menuPO
|
||||
if err := tx.First(&old, v.ID).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -122,7 +122,7 @@ func (r *menuRepo) Update(ctx context.Context, v *biz.Menu) error {
|
|||
}
|
||||
|
||||
func (r *menuRepo) Delete(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var count int64
|
||||
if err := tx.Model(&menuPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -158,7 +158,7 @@ func (r *menuRepo) Delete(ctx context.Context, id uint) error {
|
|||
|
||||
func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error {
|
||||
var parameters []menuParameterPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Find(¶meters).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Find(¶meters).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
menu.Parameters = make([]*biz.MenuParameter, 0, len(parameters))
|
||||
|
|
@ -166,7 +166,7 @@ func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error {
|
|||
menu.Parameters = append(menu.Parameters, menuParameterFromPO(parameter))
|
||||
}
|
||||
var buttons []menuButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Find(&buttons).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menu.ID).Find(&buttons).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
menu.Buttons = make([]*biz.MenuButton, 0, len(buttons))
|
||||
|
|
@ -178,7 +178,7 @@ func (r *menuRepo) loadRelations(ctx context.Context, menu *biz.Menu) error {
|
|||
|
||||
func (r *menuRepo) Find(ctx context.Context, id uint) (*biz.Menu, error) {
|
||||
var po menuPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menu := menuFromPO(po)
|
||||
|
|
@ -190,15 +190,15 @@ func (r *menuRepo) Find(ctx context.Context, id uint) (*biz.Menu, error) {
|
|||
|
||||
func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) {
|
||||
var pos []menuPO
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&menuPO{}).Order("sort")
|
||||
config := r.data.runtime.Admin()
|
||||
db := r.data.DB().WithContext(ctx).Model(&menuPO{}).Order("sort")
|
||||
config := r.data.Runtime().Admin()
|
||||
if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth {
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if authority.ParentID != nil && *authority.ParentID != 0 {
|
||||
db = db.Where("id IN (?)", r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Select("sys_base_menu_id").Where("sys_authority_authority_id = ?", actor.AuthorityID))
|
||||
db = db.Where("id IN (?)", r.data.DB().WithContext(ctx).Model(&authorityMenuPO{}).Select("sys_base_menu_id").Where("sys_authority_authority_id = ?", actor.AuthorityID))
|
||||
}
|
||||
}
|
||||
if err := db.Find(&pos).Error; err != nil {
|
||||
|
|
@ -220,7 +220,7 @@ func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) {
|
|||
menu.Buttons = []*biz.MenuButton{}
|
||||
}
|
||||
var parameters []menuParameterPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Find(¶meters).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Find(¶meters).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, parameter := range parameters {
|
||||
|
|
@ -228,7 +228,7 @@ func (r *menuRepo) List(ctx context.Context) ([]*biz.Menu, error) {
|
|||
menu.Parameters = append(menu.Parameters, menuParameterFromPO(parameter))
|
||||
}
|
||||
var buttons []menuButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Find(&buttons).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", ids).Find(&buttons).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, button := range buttons {
|
||||
|
|
@ -260,13 +260,13 @@ func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) e
|
|||
if err := (&authorityAccessRepo{data: r.data}).checkAuthorityIDAuth(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ?", id).First(&authorityPO{}).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", id).First(&authorityPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.checkMenuAssignmentAuth(ctx, ids); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -283,7 +283,7 @@ func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) e
|
|||
|
||||
func (r *menuRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_base_menu_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
|
|
@ -294,8 +294,8 @@ func (r *menuRepo) AuthorityMenuIDs(ctx context.Context, id uint) ([]uint, error
|
|||
// menus when an administrator inspects a different (child) authority.
|
||||
func (r *menuRepo) ListAuthorityMenus(ctx context.Context, authorityID uint) ([]*biz.Menu, error) {
|
||||
var pos []menuPO
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&menuPO{}).
|
||||
Where("id IN (?)", r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).
|
||||
db := r.data.DB().WithContext(ctx).Model(&menuPO{}).
|
||||
Where("id IN (?)", r.data.DB().WithContext(ctx).Model(&authorityMenuPO{}).
|
||||
Select("sys_base_menu_id").Where("sys_authority_authority_id = ?", authorityID)).
|
||||
Order("sort")
|
||||
if err := db.Find(&pos).Error; err != nil {
|
||||
|
|
@ -312,17 +312,17 @@ func (r *menuRepo) ListAuthorityMenus(ctx context.Context, authorityID uint) ([]
|
|||
|
||||
func (r *menuRepo) MenuRoleIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_base_menu_id = ?", id).Pluck("sys_authority_authority_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&authorityMenuPO{}).Where("sys_base_menu_id = ?", id).Pluck("sys_authority_authority_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
func (r *menuRepo) DefaultRouterRoleIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var menu menuPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&menu, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&menu, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityPO{}).Where("default_router = ?", menu.Name).Pluck("authority_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&authorityPO{}).Where("default_router = ?", menu.Name).Pluck("authority_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +342,7 @@ func (r *menuRepo) SetMenuRoles(ctx context.Context, id uint, ids []uint) error
|
|||
}
|
||||
}
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if strict {
|
||||
if len(allowedAuthorities) > 0 {
|
||||
if err := tx.Where("sys_base_menu_id = ? AND sys_authority_authority_id IN ?", id, authorityIDs(allowedAuthorities)).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
|
|
@ -376,7 +376,7 @@ func (r *menuRepo) checkMenuAssignmentAuth(ctx context.Context, menuIDs []uint)
|
|||
if err != nil || !strict {
|
||||
return err
|
||||
}
|
||||
return checkMenuAssignment(r.data.gormDB.WithContext(ctx), actor.AuthorityID, allowedAuthorities[actor.AuthorityID], menuIDs)
|
||||
return checkMenuAssignment(r.data.DB().WithContext(ctx), actor.AuthorityID, allowedAuthorities[actor.AuthorityID], menuIDs)
|
||||
}
|
||||
|
||||
func checkMenuAssignment(db *gorm.DB, actorID uint, root bool, menuIDs []uint) error {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -0,0 +1,507 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// legacySchemaMigration creates the complete schema and repairs legacy table
|
||||
// shapes. It is intentionally called only by the one-time baseline migration.
|
||||
func LegacySchemaMigration(db *gorm.DB) error {
|
||||
if err := migrateLegacyIgnoreAPITable(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateLegacyAuthorityDepartmentColumns(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.AutoMigrate(
|
||||
&userPO{}, &authorityPO{}, &menuPO{}, &userAuthorityPO{}, &authorityMenuPO{}, &menuParameterPO{},
|
||||
&apiPO{}, &ignoredAPIPO{}, &authorityAPIPO{}, &casbinRulePO{}, &menuButtonPO{}, &authorityButtonPO{},
|
||||
&departmentPO{}, &positionPO{}, &userDepartmentPO{}, &userPositionPO{}, &authorityDepartmentPO{},
|
||||
&dictionaryPO{}, &dictionaryDetailPO{}, ¶meterPO{}, &apiTokenPO{}, &jwtBlacklistPO{}, &SecurityConfigPO{},
|
||||
&versionPO{}, &exportTemplatePO{}, &exportConditionPO{}, &exportJoinPO{},
|
||||
&operationPO{}, &loginLogPO{}, &dataAccessLogPO{}, &errorRecordPO{},
|
||||
&taskPO{}, &taskLogPO{}, &mediaPO{}, &categoryPO{}, &uploadSessionPO{}, &uploadChunkPO{},
|
||||
&announcementPO{},
|
||||
)
|
||||
}
|
||||
|
||||
// CurrentDataMigration contains idempotent system data and authorization
|
||||
// reconciliation that belongs to an explicit gormigrate version.
|
||||
func CurrentDataMigration(db *gorm.DB) error {
|
||||
if err := migrateLegacyAuthorityAPIsToCasbinRules(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeErrorRecordStatuses(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := reconcileRootAuthorityAPIs(db); err != nil {
|
||||
return err
|
||||
}
|
||||
return reconcileReferenceIndexes(db)
|
||||
}
|
||||
|
||||
// EnsureAdminSurface upgrades an existing database with menu/API metadata
|
||||
// contributed by a feature module. The operation is idempotent and safe to run
|
||||
// before the root authority exists; bootstrap seeding links the full menu tree
|
||||
// when the first administrator is created.
|
||||
func EnsureAdminSurface(db *gorm.DB, surface AdminSurface) error {
|
||||
clean := db.Session(&gorm.Session{NewDB: true})
|
||||
return clean.Transaction(func(tx *gorm.DB) error {
|
||||
menus := make([]menuPO, 0, len(surface.Menus))
|
||||
for _, item := range surface.Menus {
|
||||
parentID := uint(0)
|
||||
if item.ParentName != "" {
|
||||
var parent menuPO
|
||||
if err := tx.Where("name = ?", item.ParentName).First(&parent).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
parent = menuPO{Path: item.ParentName, Name: item.ParentName, Component: "view/routerHolder.vue", Title: item.ParentName, Sort: item.Sort}
|
||||
if err := tx.Create(&parent).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
parentID = parent.ID
|
||||
}
|
||||
menu := menuPO{MenuLevel: 1, ParentID: parentID, Path: item.Path, Name: item.Name, Component: item.Component, Title: item.Title, Icon: item.Icon, Sort: item.Sort}
|
||||
if item.ParentName == "" {
|
||||
menu.MenuLevel = 0
|
||||
}
|
||||
menus = append(menus, menu)
|
||||
}
|
||||
for _, item := range menus {
|
||||
var current menuPO
|
||||
err := tx.Where("name = ?", item.Name).First(¤t).Error
|
||||
switch {
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if err := tx.Model(¤t).Updates(map[string]any{
|
||||
"menu_level": item.MenuLevel, "parent_id": parent.ID, "path": item.Path,
|
||||
"component": item.Component, "title": item.Title, "icon": item.Icon, "sort": item.Sort,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apis := make([]apiPO, 0, len(surface.APIs))
|
||||
for _, item := range surface.APIs {
|
||||
apis = append(apis, apiPO{Path: item.Path, Method: item.Method, APIGroup: item.APIGroup, Description: item.Description})
|
||||
}
|
||||
for _, item := range apis {
|
||||
var current apiPO
|
||||
err := tx.Where("path = ? AND method = ?", item.Path, item.Method).First(¤t).Error
|
||||
switch {
|
||||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if err := tx.Model(¤t).Updates(map[string]any{"api_group": item.APIGroup, "description": item.Description}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var authorityCount int64
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", 888).Count(&authorityCount).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if authorityCount == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, item := range menus {
|
||||
var current menuPO
|
||||
if err := tx.Where("name = ?", item.Name).First(¤t).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", 888, current.ID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
if err := tx.Create(&authorityMenuPO{SysAuthorityAuthorityID: 888, SysBaseMenuID: current.ID}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range apis {
|
||||
var current apiPO
|
||||
if err := tx.Where("path = ? AND method = ?", item.Path, item.Method).First(¤t).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
exists, err := policyExists(tx, 888, current.Path, current.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := tx.Create(&casbinRulePO{Ptype: "p", V0: "888", V1: current.Path, V2: current.Method}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// migrateLegacyAuthorityDepartmentColumns preserves data created by early
|
||||
// Kra builds, which used shortened join-column names and a composite primary
|
||||
// key. The administration connection model has neither a primary key nor a
|
||||
// uniqueness constraint, so rebuild the small table before AutoMigrate.
|
||||
func migrateLegacyAuthorityDepartmentColumns(db *gorm.DB) error {
|
||||
clean := db.Session(&gorm.Session{NewDB: true})
|
||||
const (
|
||||
table = "sys_authority_departments"
|
||||
backup = "sys_authority_departments_kra_legacy"
|
||||
)
|
||||
// MySQL and Oracle auto-commit DDL. If a prior process stopped between the
|
||||
// rename and cleanup steps, restore the untouched backup first and retry the
|
||||
// migration from a known state.
|
||||
if clean.Migrator().HasTable(backup) {
|
||||
if clean.Migrator().HasTable(table) {
|
||||
if err := clean.Migrator().DropTable(table); err != nil {
|
||||
return fmt.Errorf("remove incomplete authority-department table: %w", err)
|
||||
}
|
||||
}
|
||||
if err := clean.Migrator().RenameTable(backup, table); err != nil {
|
||||
return fmt.Errorf("restore authority-department backup: %w", err)
|
||||
}
|
||||
}
|
||||
if !clean.Migrator().HasTable(table) {
|
||||
return nil
|
||||
}
|
||||
|
||||
authorityColumn := "sys_authority_authority_id"
|
||||
if !tableHasColumn(clean, table, authorityColumn) {
|
||||
if !tableHasColumn(clean, table, "authority_id") {
|
||||
return fmt.Errorf("authority-department table has no authority column")
|
||||
}
|
||||
authorityColumn = "authority_id"
|
||||
}
|
||||
departmentColumn := "sys_department_id"
|
||||
if !tableHasColumn(clean, table, departmentColumn) {
|
||||
if !tableHasColumn(clean, table, "department_id") {
|
||||
return fmt.Errorf("authority-department table has no department column")
|
||||
}
|
||||
departmentColumn = "department_id"
|
||||
}
|
||||
hasPrimaryKey, err := tableHasPrimaryKey(clean, table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if authorityColumn == "sys_authority_authority_id" && departmentColumn == "sys_department_id" && !hasPrimaryKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
type relation struct {
|
||||
AuthorityID uint `gorm:"column:authority_id"`
|
||||
DepartmentID uint `gorm:"column:department_id"`
|
||||
}
|
||||
var rows []relation
|
||||
selectColumns := authorityColumn + " AS authority_id, " + departmentColumn + " AS department_id"
|
||||
if err := clean.Table(table).Select(selectColumns).Scan(&rows).Error; err != nil {
|
||||
return fmt.Errorf("read legacy authority-department rows: %w", err)
|
||||
}
|
||||
|
||||
rebuild := func(tx *gorm.DB) error {
|
||||
if err := tx.Migrator().RenameTable(table, backup); err != nil {
|
||||
return fmt.Errorf("rename legacy authority-department table: %w", err)
|
||||
}
|
||||
if err := tx.AutoMigrate(&authorityDepartmentPO{}); err != nil {
|
||||
return fmt.Errorf("create authority-department table: %w", err)
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
items := make([]authorityDepartmentPO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, authorityDepartmentPO{AuthorityID: row.AuthorityID, DepartmentID: row.DepartmentID})
|
||||
}
|
||||
if err := tx.Create(&items).Error; err != nil {
|
||||
return fmt.Errorf("copy authority-department rows: %w", err)
|
||||
}
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&authorityDepartmentPO{}).Count(&count).Error; err != nil {
|
||||
return fmt.Errorf("verify authority-department rows: %w", err)
|
||||
}
|
||||
if count != int64(len(rows)) {
|
||||
return fmt.Errorf("verify authority-department rows: got %d want %d", count, len(rows))
|
||||
}
|
||||
if err := tx.Migrator().DropTable(backup); err != nil {
|
||||
return fmt.Errorf("drop legacy authority-department table: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch clean.Dialector.Name() {
|
||||
case "mysql", "oracle":
|
||||
if err := rebuild(clean); err != nil {
|
||||
restoreErr := restoreAuthorityDepartmentBackup(clean, table, backup)
|
||||
if restoreErr != nil {
|
||||
return fmt.Errorf("%v; restore authority-department backup: %w", err, restoreErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return clean.Transaction(rebuild)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreAuthorityDepartmentBackup(db *gorm.DB, table, backup string) error {
|
||||
if db.Migrator().HasTable(table) {
|
||||
if err := db.Migrator().DropTable(table); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if db.Migrator().HasTable(backup) {
|
||||
return db.Migrator().RenameTable(backup, table)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tableHasPrimaryKey(db *gorm.DB, table string) (bool, error) {
|
||||
columns, err := db.Migrator().ColumnTypes(table)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, column := range columns {
|
||||
if primary, ok := column.PrimaryKey(); ok && primary {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Older builds used a status label outside the administration page's supported
|
||||
// state set, so normalize existing rows during migration.
|
||||
func normalizeErrorRecordStatuses(db *gorm.DB) error {
|
||||
return db.Session(&gorm.Session{NewDB: true}).Model(&errorRecordPO{}).Where("status = ?", "未解决").Update("status", "未处理").Error
|
||||
}
|
||||
|
||||
// migrateLegacyAuthorityAPIsToCasbinRules upgrades the early Kra join-table
|
||||
// representation to the independent Casbin policy table. Keep the legacy
|
||||
// table in place for backwards compatibility, but make casbin_rule the sole
|
||||
// live policy source. Existing policy rows are not duplicated.
|
||||
func migrateLegacyAuthorityAPIsToCasbinRules(db *gorm.DB) error {
|
||||
clean := db.Session(&gorm.Session{NewDB: true})
|
||||
if !clean.Migrator().HasTable(&authorityAPIPO{}) || !clean.Migrator().HasTable(&casbinRulePO{}) {
|
||||
return nil
|
||||
}
|
||||
type legacyPolicy struct {
|
||||
AuthorityID uint
|
||||
Path string
|
||||
Method string
|
||||
}
|
||||
baseQuery := func() *gorm.DB {
|
||||
return clean.Table("sys_authority_apis sa").
|
||||
Select("sa.authority_id, a.path, a.method").
|
||||
Joins("JOIN sys_apis a ON a.id = sa.api_id")
|
||||
}
|
||||
query := baseQuery()
|
||||
// Early Kra schemas stored sys_apis without soft-delete timestamps. The
|
||||
// legacy-policy migration must run before assuming that column exists;
|
||||
// otherwise an upgrade from those schemas cannot start on MySQL.
|
||||
if tableHasColumn(clean, "sys_apis", "deleted_at") {
|
||||
query = query.Where("a.deleted_at IS NULL")
|
||||
}
|
||||
var rows []legacyPolicy
|
||||
if err := query.Find(&rows).Error; err != nil {
|
||||
// A few MySQL-compatible drivers report stale/incomplete metadata from
|
||||
// INFORMATION_SCHEMA during startup. If the optional soft-delete column
|
||||
// was reported present but the join still rejects it, retry using only
|
||||
// columns shared by every legacy schema. This migration must never block
|
||||
// startup of an older database solely because deleted_at is absent.
|
||||
if strings.Contains(strings.ToLower(err.Error()), "unknown column") && strings.Contains(strings.ToLower(err.Error()), "deleted_at") {
|
||||
if retryErr := baseQuery().Find(&rows).Error; retryErr != nil {
|
||||
return retryErr
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, row := range rows {
|
||||
exists, err := policyExists(clean, row.AuthorityID, row.Path, row.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
if err := clean.Create(&casbinRulePO{Ptype: "p", V0: fmt.Sprint(row.AuthorityID), V1: row.Path, V2: row.Method}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tableHasColumn deliberately inspects the physical table rather than the
|
||||
// model schema. Legacy databases may predate soft-delete columns even though
|
||||
// the current PO includes gorm.DeletedAt. Metadata inspection failures are
|
||||
// treated as "unknown" so callers use the portable query shape.
|
||||
func tableHasColumn(db *gorm.DB, table, column string) bool {
|
||||
columns, err := db.Migrator().ColumnTypes(table)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, item := range columns {
|
||||
if strings.EqualFold(item.Name(), column) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// migrateLegacyIgnoreAPITable upgrades the early Kra-only composite-key
|
||||
// shape (path, method) to the compatible model shape (ID/timestamps/soft
|
||||
// delete). AutoMigrate can add columns but cannot replace an existing
|
||||
// composite primary key portably, so rebuild the small table once while
|
||||
// preserving every existing ignore rule.
|
||||
func migrateLegacyIgnoreAPITable(db *gorm.DB) error {
|
||||
clean := db.Session(&gorm.Session{NewDB: true})
|
||||
if !clean.Migrator().HasTable(&ignoredAPIPO{}) || clean.Migrator().HasColumn(&ignoredAPIPO{}, "id") {
|
||||
return nil
|
||||
}
|
||||
legacyTable := fmt.Sprintf("sys_ignore_apis_legacy_%d", time.Now().UnixNano())
|
||||
type legacyIgnoredAPI struct {
|
||||
Path string
|
||||
Method string
|
||||
}
|
||||
return clean.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Migrator().RenameTable(ignoredAPIPO{}.TableName(), legacyTable); err != nil {
|
||||
return fmt.Errorf("rename legacy ignore API table: %w", err)
|
||||
}
|
||||
if err := tx.AutoMigrate(&ignoredAPIPO{}); err != nil {
|
||||
return fmt.Errorf("create compatible ignore API table: %w", err)
|
||||
}
|
||||
var rows []legacyIgnoredAPI
|
||||
if err := tx.Table(legacyTable).Find(&rows).Error; err != nil {
|
||||
return fmt.Errorf("read legacy ignore API rows: %w", err)
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
items := make([]ignoredAPIPO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, ignoredAPIPO{Path: row.Path, Method: row.Method})
|
||||
}
|
||||
if err := tx.Create(&items).Error; err != nil {
|
||||
return fmt.Errorf("copy legacy ignore API rows: %w", err)
|
||||
}
|
||||
}
|
||||
if err := tx.Migrator().DropTable(legacyTable); err != nil {
|
||||
return fmt.Errorf("drop legacy ignore API table: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// reconcileRootAuthorityAPIs is a one-time upgrade path from the former Kra
|
||||
// implementation where authority 888 bypassed policy storage entirely. The compatible behavior
|
||||
// grants its root role through persisted Casbin policies, so when a legacy
|
||||
// database has the root role but no stored API links, materialize the same
|
||||
// policy set and let normal authorization read it thereafter.
|
||||
func reconcileRootAuthorityAPIs(db *gorm.DB) error {
|
||||
clean := db.Session(&gorm.Session{NewDB: true})
|
||||
var authorityCount int64
|
||||
if err := clean.Session(&gorm.Session{NewDB: true}).Model(&authorityPO{}).Where("authority_id = ?", 888).Count(&authorityCount).Error; err != nil || authorityCount == 0 {
|
||||
return err
|
||||
}
|
||||
var policyCount int64
|
||||
if err := policyScope(clean).Where("v0 = ?", "888").Count(&policyCount).Error; err != nil || policyCount != 0 {
|
||||
return err
|
||||
}
|
||||
var ignored []ignoredAPIPO
|
||||
if err := clean.Session(&gorm.Session{NewDB: true}).Find(&ignored).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
ignoreSet := make(map[string]struct{}, len(ignored))
|
||||
for _, item := range ignored {
|
||||
ignoreSet[item.Method+"\x00"+item.Path] = struct{}{}
|
||||
}
|
||||
var apis []apiPO
|
||||
if err := clean.Session(&gorm.Session{NewDB: true}).Find(&apis).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
rules := make([]casbinRulePO, 0, len(apis))
|
||||
for _, api := range apis {
|
||||
if _, ok := ignoreSet[api.Method+"\x00"+api.Path]; ok {
|
||||
continue
|
||||
}
|
||||
rules = append(rules, newPolicyRule(888, api.Path, api.Method))
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
return clean.Session(&gorm.Session{NewDB: true}).Create(&rules).Error
|
||||
}
|
||||
|
||||
// reconcileReferenceIndexes removes constraints created by older Kra builds
|
||||
// that are not part of the administration data model. Business services own
|
||||
// duplicate checks and their user-facing error messages.
|
||||
func reconcileReferenceIndexes(db *gorm.DB) error {
|
||||
clean := db.Session(&gorm.Session{NewDB: true})
|
||||
obsolete := []struct {
|
||||
model any
|
||||
name string
|
||||
}{
|
||||
{&apiPO{}, "idx_api_path_method"},
|
||||
{&dictionaryPO{}, "idx_sys_dictionaries_type"},
|
||||
{¶meterPO{}, "idx_sys_params_key"},
|
||||
{&apiTokenPO{}, "idx_sys_api_tokens_token"},
|
||||
{&exportTemplatePO{}, "idx_sys_export_templates_template_id"},
|
||||
}
|
||||
for _, item := range obsolete {
|
||||
migrator := clean.Session(&gorm.Session{NewDB: true}).Migrator()
|
||||
if migrator.HasIndex(item.model, item.name) {
|
||||
if err := migrator.DropIndex(item.model, item.name); err != nil {
|
||||
return fmt.Errorf("drop obsolete index %s: %w", item.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range []struct {
|
||||
name string
|
||||
field string
|
||||
}{{"idx_sys_users_uuid", "UUID"}, {"idx_sys_users_username", "Username"}} {
|
||||
unique, err := indexIsUnique(clean.Session(&gorm.Session{NewDB: true}), &userPO{}, item.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !unique {
|
||||
continue
|
||||
}
|
||||
migrator := clean.Session(&gorm.Session{NewDB: true}).Migrator()
|
||||
if err = migrator.DropIndex(&userPO{}, item.name); err != nil {
|
||||
return fmt.Errorf("drop legacy unique index %s: %w", item.name, err)
|
||||
}
|
||||
if err = migrator.CreateIndex(&userPO{}, item.field); err != nil {
|
||||
return fmt.Errorf("create reference index %s: %w", item.name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func indexIsUnique(db *gorm.DB, model any, name string) (bool, error) {
|
||||
indexes, err := db.Migrator().GetIndexes(model)
|
||||
if err != nil {
|
||||
// Some third-party GORM drivers do not implement index inspection.
|
||||
// Fresh schemas are already correct; skip only the legacy repair there.
|
||||
return false, nil
|
||||
}
|
||||
for _, index := range indexes {
|
||||
if index.Name() == name {
|
||||
unique, known := index.Unique()
|
||||
return known && unique, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -1,9 +1,117 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"kra/internal/data/migration"
|
||||
)
|
||||
|
||||
func TestMigrateAllUsesVersionTableAndIsIdempotent(t *testing.T) {
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
|
||||
if err = migrateAll(db); err != nil {
|
||||
t.Fatalf("first migration: %v", err)
|
||||
}
|
||||
if err = migrateAll(db); err != nil {
|
||||
t.Fatalf("second migration: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasTable(migration.TableName) {
|
||||
t.Fatalf("missing migration table %q", migration.TableName)
|
||||
}
|
||||
var count int64
|
||||
if err = db.Table(migration.TableName).Count(&count).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Fatalf("migration rows = %d, want 2", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsurePaymentAdminSurfaceIsIdempotent(t *testing.T) {
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
if err = db.AutoMigrate(&menuPO{}, &authorityMenuPO{}, &authorityPO{}, &apiPO{}, &casbinRulePO{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rootParentID := uint(0)
|
||||
if err = db.Create(&authorityPO{AuthorityID: 888, AuthorityName: "root", ParentID: &rootParentID}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = db.Create(&menuPO{Name: "extensions", Path: "legacy-extensions", Title: "旧扩展"}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = db.Create(&menuPO{Name: "paymentOrders", Component: "legacy.vue", Title: "旧支付订单"}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = db.Create(&apiPO{Path: "/payment/configs", Method: "GET", APIGroup: "legacy"}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if err = ensurePaymentAdminSurface(db); err != nil {
|
||||
t.Fatalf("ensure payment admin surface pass %d: %v", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
var parent menuPO
|
||||
if err = db.Where("name = ?", "extensions").First(&parent).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, component := range map[string]string{
|
||||
"paymentOrders": "view/systemTools/payment/orders.vue",
|
||||
"paymentConfig": "view/systemTools/payment/config.vue",
|
||||
} {
|
||||
var menu menuPO
|
||||
if err = db.Where("name = ?", name).First(&menu).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if menu.ParentID != parent.ID || menu.Component != component {
|
||||
t.Fatalf("menu %s = %#v", name, menu)
|
||||
}
|
||||
var linkCount int64
|
||||
if err = db.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", 888, menu.ID).Count(&linkCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if linkCount != 1 {
|
||||
t.Fatalf("menu %s root links = %d, want 1", name, linkCount)
|
||||
}
|
||||
}
|
||||
for _, item := range []struct{ method, path string }{
|
||||
{"GET", "/payment/configs"}, {"POST", "/payment/config"},
|
||||
{"GET", "/payment/orders"}, {"POST", "/payment/order"},
|
||||
} {
|
||||
var apiCount int64
|
||||
if err = db.Model(&apiPO{}).Where("path = ? AND method = ?", item.path, item.method).Count(&apiCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if apiCount != 1 {
|
||||
t.Fatalf("API %s %s rows = %d, want 1", item.method, item.path, apiCount)
|
||||
}
|
||||
var policyCount int64
|
||||
if err = policyScope(db).Where("v0 = ? AND v1 = ? AND v2 = ?", "888", item.path, item.method).Count(&policyCount).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if policyCount != 1 {
|
||||
t.Fatalf("policy %s %s rows = %d, want 1", item.method, item.path, policyCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateLegacyAuthorityAPIsWithoutDeletedAt(t *testing.T) {
|
||||
db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
|
@ -96,9 +96,6 @@ type menuParameterPO struct {
|
|||
|
||||
func (menuParameterPO) TableName() string { return "sys_base_menu_parameters" }
|
||||
|
||||
type userRepo struct{ data *Data }
|
||||
type initializationRepo struct{ data *Data }
|
||||
type userRepo struct{ data Provider }
|
||||
|
||||
func NewUserRepo(data *Data) biz.UserRepo { return &userRepo{data: data} }
|
||||
|
||||
func NewInitializationRepo(data *Data) biz.InitializationRepo { return &initializationRepo{data: data} }
|
||||
func NewUserRepo(data Provider) biz.UserRepo { return &userRepo{data: data} }
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
|
@ -30,13 +31,13 @@ type operationPO struct {
|
|||
func (operationPO) TableName() string { return "sys_operation_records" }
|
||||
|
||||
func (r *auditRecorderRepo) RecordOperation(ctx context.Context, v *biz.OperationRecord) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&operationPO{IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&operationPO{IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID}).Error
|
||||
}
|
||||
func opFromPO(v operationPO) *biz.OperationRecord {
|
||||
return &biz.OperationRecord{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID}
|
||||
}
|
||||
func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*biz.OperationRecord, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&operationPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&operationPO{})
|
||||
if q != nil {
|
||||
if q.Path != "" {
|
||||
db = db.Where("path LIKE ?", "%"+q.Path+"%")
|
||||
|
|
@ -53,10 +54,10 @@ func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []operationPO
|
||||
if err := applyRequiredPagination(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
users := auditUsers(ctx, r.data.gormDB.WithContext(ctx), operationUserIDs(pos))
|
||||
users := auditUsers(ctx, r.data.DB().WithContext(ctx), operationUserIDs(pos))
|
||||
out := make([]*biz.OperationRecord, 0, len(pos))
|
||||
for _, po := range pos {
|
||||
value := opFromPO(po)
|
||||
|
|
@ -92,7 +93,7 @@ func auditUsers(ctx context.Context, db *gorm.DB, ids []uint) map[uint]*biz.User
|
|||
}
|
||||
func (r *auditQueryRepo) FindOperation(ctx context.Context, id uint) (*biz.OperationRecord, error) {
|
||||
var po operationPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return opFromPO(po), nil
|
||||
|
|
@ -100,7 +101,7 @@ func (r *auditQueryRepo) FindOperation(ctx context.Context, id uint) (*biz.Opera
|
|||
|
||||
func (r *auditQueryRepo) DeleteOperations(ctx context.Context, ids []int) error {
|
||||
if len(ids) == 1 && ids[0] == 0 {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&operationPO{}).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&operationPO{}).Error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&operationPO{}, "id IN ?", ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&operationPO{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type parameterRepo struct{ data *Data }
|
||||
type parameterRepo struct{ data Provider }
|
||||
|
||||
func NewParameterRepo(data *Data) biz.ParameterRepo { return ¶meterRepo{data: data} }
|
||||
func NewParameterRepo(data Provider) biz.ParameterRepo { return ¶meterRepo{data: data} }
|
||||
|
||||
type parameterPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -29,7 +30,7 @@ func (parameterPO) TableName() string { return "sys_params" }
|
|||
|
||||
func (r *parameterRepo) CreateParameter(ctx context.Context, v *biz.SystemParameter) error {
|
||||
po := parameterPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID, v.CreatedAt, v.UpdatedAt = po.ID, po.CreatedAt, po.UpdatedAt
|
||||
|
|
@ -38,30 +39,30 @@ func (r *parameterRepo) CreateParameter(ctx context.Context, v *biz.SystemParame
|
|||
func (r *parameterRepo) UpdateParameter(ctx context.Context, v *biz.SystemParameter) error {
|
||||
// The compatible update uses a struct, so zero-value optional fields (notably desc)
|
||||
// are intentionally ignored rather than clearing an existing value.
|
||||
return r.data.gormDB.WithContext(ctx).Model(¶meterPO{}).Where("id = ?", v.ID).Updates(¶meterPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}).Error
|
||||
return r.data.DB().WithContext(ctx).Model(¶meterPO{}).Where("id = ?", v.ID).Updates(¶meterPO{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc}).Error
|
||||
}
|
||||
func (r *parameterRepo) DeleteParameters(ctx context.Context, ids []string) error {
|
||||
if len(ids) == 1 {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(¶meterPO{}, "id = ?", ids[0]).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(¶meterPO{}, "id = ?", ids[0]).Error
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&[]parameterPO{}, "id in ?", ids).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&[]parameterPO{}, "id in ?", ids).Error
|
||||
}
|
||||
func (r *parameterRepo) FindParameterByID(ctx context.Context, id string) (*biz.SystemParameter, error) {
|
||||
var po parameterPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id = ?", id).First(&po).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parameterFromPO(po), nil
|
||||
}
|
||||
func (r *parameterRepo) FindParameterByKey(ctx context.Context, key string) (*biz.SystemParameter, error) {
|
||||
var po parameterPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where(parameterPO{Key: key}).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where(parameterPO{Key: key}).First(&po).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parameterFromPO(po), nil
|
||||
}
|
||||
func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*biz.SystemParameter, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(¶meterPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(¶meterPO{})
|
||||
if q != nil {
|
||||
if q.StartCreatedAt != nil && q.EndCreatedAt != nil {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", q.StartCreatedAt, q.EndCreatedAt)
|
||||
|
|
@ -78,7 +79,7 @@ func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *b
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []parameterPO
|
||||
if err := applyPagination(db, page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.Apply(db, page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.SystemParameter, 0, len(pos))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -10,9 +10,9 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type permissionRepo struct{ data *Data }
|
||||
type permissionRepo struct{ data Provider }
|
||||
|
||||
func NewPermissionRepo(data *Data) biz.PermissionRepo { return &permissionRepo{data: data} }
|
||||
func NewPermissionRepo(data Provider) biz.PermissionRepo { return &permissionRepo{data: data} }
|
||||
|
||||
type menuButtonPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -36,7 +36,7 @@ func (authorityButtonPO) TableName() string { return "sys_authority_btns" }
|
|||
|
||||
func (r *permissionRepo) Buttons(ctx context.Context, menuID uint) ([]*biz.MenuButton, error) {
|
||||
var pos []menuButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id = ?", menuID).Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id = ?", menuID).Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*biz.MenuButton, 0, len(pos))
|
||||
|
|
@ -52,7 +52,7 @@ func (r *permissionRepo) SetAuthorityButtons(ctx context.Context, aid uint, butt
|
|||
if err := r.checkButtonAssignmentAuth(ctx, buttons); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ?", aid).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ func (r *permissionRepo) SetAuthorityButtons(ctx context.Context, aid uint, butt
|
|||
}
|
||||
func (r *permissionRepo) SelectedButtons(ctx context.Context, aid, menuID uint) ([]uint, error) {
|
||||
var rows []authorityButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Find(&rows).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []uint
|
||||
|
|
@ -86,7 +86,7 @@ func (r *permissionRepo) SetSelectedButtons(ctx context.Context, aid, menuID uin
|
|||
if err := r.checkButtonAssignmentAuth(ctx, map[uint][]uint{menuID: ids}); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("authority_id = ? AND sys_menu_id = ?", aid, menuID).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ func (r *permissionRepo) SetSelectedButtons(ctx context.Context, aid, menuID uin
|
|||
}
|
||||
func (r *permissionRepo) CanRemoveButton(ctx context.Context, id string) (bool, error) {
|
||||
var row authorityButtonPO
|
||||
err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_btn_id = ?", id).First(&row).Error
|
||||
err := r.data.DB().WithContext(ctx).Where("sys_base_menu_btn_id = ?", id).First(&row).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ func (r *permissionRepo) CanRemoveButton(ctx context.Context, id string) (bool,
|
|||
}
|
||||
func (r *permissionRepo) AuthorityButtonIDs(ctx context.Context, aid uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ?", aid).Pluck("sys_base_menu_btn_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&authorityButtonPO{}).Where("authority_id = ?", aid).Pluck("sys_base_menu_btn_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ func (r *permissionRepo) checkButtonAssignmentAuth(ctx context.Context, buttons
|
|||
if err != nil || !strict {
|
||||
return err
|
||||
}
|
||||
return checkButtonAssignment(r.data.gormDB.WithContext(ctx), actor.AuthorityID, allowedAuthorities[actor.AuthorityID], buttons)
|
||||
return checkButtonAssignment(r.data.DB().WithContext(ctx), actor.AuthorityID, allowedAuthorities[actor.AuthorityID], buttons)
|
||||
}
|
||||
|
||||
func checkButtonAssignment(db *gorm.DB, actorID uint, root bool, buttons map[uint][]uint) error {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -10,9 +10,9 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type positionRepo struct{ data *Data }
|
||||
type positionRepo struct{ data Provider }
|
||||
|
||||
func NewPositionRepo(data *Data) biz.PositionRepo { return &positionRepo{data: data} }
|
||||
func NewPositionRepo(data Provider) biz.PositionRepo { return &positionRepo{data: data} }
|
||||
|
||||
type positionPO struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
|
|
@ -41,33 +41,33 @@ func posFromPO(po positionPO) *biz.Position {
|
|||
return &biz.Position{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, Code: po.Code, Sort: po.Sort, Status: po.Status, Remark: po.Remark}
|
||||
}
|
||||
func (r *positionRepo) CreatePosition(ctx context.Context, v *biz.Position) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&positionPO{Name: v.Name, Code: v.Code, Sort: v.Sort, Status: v.Status, Remark: v.Remark}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&positionPO{Name: v.Name, Code: v.Code, Sort: v.Sort, Status: v.Status, Remark: v.Remark}).Error
|
||||
}
|
||||
func (r *positionRepo) UpdatePosition(ctx context.Context, v *biz.Position) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&positionPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&positionPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "code": v.Code, "sort": v.Sort, "status": v.Status, "remark": v.Remark}).Error
|
||||
}
|
||||
func (r *positionRepo) DeletePosition(ctx context.Context, id uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("岗位ID不能为空")
|
||||
}
|
||||
var count int64
|
||||
if err := r.data.gormDB.WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Count(&count).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("该岗位下存在用户,不允许删除")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&positionPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&positionPO{}, id).Error
|
||||
}
|
||||
func (r *positionRepo) FindPosition(ctx context.Context, id uint) (*biz.Position, error) {
|
||||
var po positionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return posFromPO(po), nil
|
||||
}
|
||||
func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *biz.PositionListFilter) ([]*biz.Position, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&positionPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&positionPO{})
|
||||
if q != nil {
|
||||
if q.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+q.Name+"%")
|
||||
|
|
@ -98,14 +98,14 @@ func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *biz
|
|||
}
|
||||
func (r *positionRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
err := r.data.DB().WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Pluck("sys_user_id", &ids).Error
|
||||
return ids, err
|
||||
}
|
||||
func (r *positionRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint) error {
|
||||
if id == 0 {
|
||||
return errors.New("岗位ID不能为空")
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_position_id = ?", id).Delete(&userPositionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -120,7 +120,7 @@ func (r *positionRepo) SetPositionUsers(ctx context.Context, id uint, ids []uint
|
|||
})
|
||||
}
|
||||
func (r *positionRepo) SetUserPositions(ctx context.Context, uid uint, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("sys_user_id = ?", uid).Delete(&userPositionPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/conf"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Provider exposes the shared data infrastructure needed by system
|
||||
// repositories without coupling this module to the parent data package.
|
||||
type Provider interface {
|
||||
DB() *gorm.DB
|
||||
Database(name string) (*gorm.DB, error)
|
||||
DatabaseReady() bool
|
||||
Runtime() *conf.Runtime
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SeedSystem(ctx context.Context, db *gorm.DB, input *biz.DatabaseConfig) error {
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
rootParentID := uint(0)
|
||||
authority := authorityPO{AuthorityID: 888, AuthorityName: "超级管理员", ParentID: &rootParentID, DataScope: 1, DefaultRouter: "dashboard"}
|
||||
if err := tx.FirstOrCreate(&authority, authorityPO{AuthorityID: 888}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&authorityPO{}).Where("authority_id = ? AND parent_id IS NULL", 888).Update("parent_id", 0).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
menus := defaultMenus()
|
||||
for i := range menus {
|
||||
if err := tx.Where("name = ?", menus[i].Name).FirstOrCreate(&menus[i]).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var persisted []menuPO
|
||||
if err := tx.Order("sort asc, id asc").Find(&persisted).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
nameID := make(map[string]uint, len(persisted))
|
||||
for _, menu := range persisted {
|
||||
nameID[menu.Name] = menu.ID
|
||||
}
|
||||
for i := range menus {
|
||||
if menus[i].ActiveName != "" {
|
||||
if err := tx.Model(&menuPO{}).Where("name = ?", menus[i].Name).Updates(map[string]any{"parent_id": nameID[menus[i].ActiveName], "active_name": ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := tx.Where("sys_authority_authority_id = ?", 888).Delete(&authorityMenuPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
links := make([]authorityMenuPO, 0, len(persisted))
|
||||
for _, menu := range persisted {
|
||||
links = append(links, authorityMenuPO{SysAuthorityAuthorityID: 888, SysBaseMenuID: menu.ID})
|
||||
}
|
||||
if len(links) > 0 {
|
||||
if err := tx.Create(&links).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&userPO{}).Where("username = ?", "admin").Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(input.AdminPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
user := userPO{UUID: uuid.NewString(), Username: "admin", Password: string(hash), NickName: "超级管理员", AuthorityID: 888, Enable: 1, PasswordUpdatedAt: &now}
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&userAuthorityPO{SysUserID: user.ID, SysAuthorityAuthorityID: 888}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
enabled := true
|
||||
department := departmentPO{Name: "总公司", ParentID: 0, Ancestors: "0", Sort: 0, Status: &enabled}
|
||||
if err := tx.Where("name = ?", department.Name).FirstOrCreate(&department).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, position := range []positionPO{{Name: "总经理", Code: "CEO", Sort: 1, Status: &enabled}, {Name: "普通员工", Code: "STAFF", Sort: 2, Status: &enabled}} {
|
||||
if err := tx.Where("code = ?", position.Code).FirstOrCreate(&position).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
security := DefaultSecurityConfig()
|
||||
if err := tx.FirstOrCreate(&security, SecurityConfigPO{ID: 1}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
exportTemplate := exportTemplatePO{Name: "api", DBTableName: "sys_apis", TemplateID: "api", TemplateInfo: "{\n\"path\":\"路径\",\n\"method\":\"方法(大写)\",\n\"description\":\"方法介绍\",\n\"api_group\":\"方法分组\"\n}"}
|
||||
if err := tx.Where("template_id = ?", exportTemplate.TemplateID).FirstOrCreate(&exportTemplate).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, task := range []taskPO{{Name: "ClearDB", Description: "定时清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Spec: "@daily", ExecutorType: "method", MethodName: "ClearDB", Enabled: true}, {Name: "CleanStaleUploads", Description: "定时清理过期大文件上传会话", Spec: "@hourly", ExecutorType: "method", MethodName: "CleanStaleUploads", Enabled: true}} {
|
||||
if err := tx.Where("name = ?", task.Name).FirstOrCreate(&task).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, item := range input.APIs {
|
||||
if item != nil {
|
||||
po := apiPO{Path: item.Path, Method: strings.ToUpper(item.Method), Description: item.Description, APIGroup: item.APIGroup}
|
||||
if err := tx.Where("path = ? AND method = ?", po.Path, po.Method).FirstOrCreate(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
ignoredAPIs := make([]ignoredAPIPO, 0)
|
||||
for _, item := range DefaultIgnoredAPIs("uploads/file") {
|
||||
ignoredAPIs = append(ignoredAPIs, ignoredAPIPO{Method: item.Method, Path: item.Path})
|
||||
}
|
||||
for _, ignored := range ignoredAPIs {
|
||||
if err := tx.FirstOrCreate(&ignored, ignored).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ignoreSet := make(map[string]struct{}, len(ignoredAPIs))
|
||||
for _, ignored := range ignoredAPIs {
|
||||
ignoreSet[ignored.Method+"\x00"+ignored.Path] = struct{}{}
|
||||
}
|
||||
var apiRows []apiPO
|
||||
if err := tx.Find(&apiRows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, api := range apiRows {
|
||||
if _, ignored := ignoreSet[api.Method+"\x00"+api.Path]; ignored {
|
||||
continue
|
||||
}
|
||||
exists, err := policyExists(tx, 888, api.Path, api.Method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
rule := newPolicyRule(888, api.Path, api.Method)
|
||||
if err := tx.Create(&rule).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func defaultMenus() []menuPO {
|
||||
root := func(path, name, title, icon string, sort int) menuPO {
|
||||
return menuPO{Path: path, Name: name, Component: "view/routerHolder.vue", Title: title, Icon: icon, Sort: sort}
|
||||
}
|
||||
child := func(parent, path, name, component, title, icon string, sort int) menuPO {
|
||||
return menuPO{MenuLevel: 1, Path: path, Name: name, Component: component, Title: title, Icon: icon, Sort: sort, ActiveName: parent}
|
||||
}
|
||||
cachedChild := func(parent, path, name, component, title, icon string, sort int) menuPO {
|
||||
value := child(parent, path, name, component, title, icon, sort)
|
||||
value.KeepAlive = true
|
||||
return value
|
||||
}
|
||||
return []menuPO{{Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Title: "仪表盘", Icon: "odometer", Sort: 1}, root("permission", "permission", "权限管理", "perm-kra", 2), root("org", "org", "组织管理", "share", 3), root("systemConfig", "systemConfig", "系统设置", "config-kra", 4), root("monitor", "monitor", "运维监控", "monitor-kra", 5), root("media", "media", "媒体管理", "folder-opened", 6), root("extensions", "extensions", "扩展功能", "cherry", 10), {Path: "person", Name: "person", Component: "view/person/person.vue", Title: "个人信息", Icon: "postcard", Hidden: true, Sort: 13}, child("permission", "authority", "authority", "view/superAdmin/authority/authority.vue", "角色管理", "role-kra", 1), cachedChild("permission", "menu", "menu", "view/superAdmin/menu/menu.vue", "菜单管理", "tickets", 2), cachedChild("permission", "api", "api", "view/superAdmin/api/api.vue", "api管理", "api-kra", 3), child("permission", "apiToken", "apiToken", "view/systemTools/apiToken/index.vue", "API Token", "key", 4), child("org", "user", "user", "view/superAdmin/user/user.vue", "用户管理", "user", 1), child("org", "department", "department", "view/superAdmin/department/department.vue", "部门管理", "office-building", 2), child("org", "position", "position", "view/superAdmin/position/position.vue", "岗位管理", "postcard", 3), child("systemConfig", "system", "system", "view/systemTools/system/system.vue", "配置文件", "config-file-kra", 1), child("systemConfig", "dictionary", "dictionary", "view/superAdmin/dictionary/sysDictionary.vue", "字典管理", "notebook", 2), child("systemConfig", "sysParams", "sysParams", "view/superAdmin/params/sysParams.vue", "参数管理", "set-up", 3), child("systemConfig", "security", "security", "view/system/security/index.vue", "安全配置", "security-kra", 4), child("monitor", "operation", "operation", "view/superAdmin/operation/sysOperationRecord.vue", "操作历史", "document", 1), child("monitor", "loginLog", "loginLog", "view/systemTools/loginLog/index.vue", "登录日志", "clock", 2), child("monitor", "sysError", "sysError", "view/systemTools/sysError/sysError.vue", "错误日志", "error-kra", 3), child("monitor", "sysVersion", "sysVersion", "view/systemTools/version/version.vue", "版本管理", "version-kra", 4), child("monitor", "state", "state", "view/system/state.vue", "服务器状态", "server", 5), child("monitor", "dataAccessLog", "dataAccessLog", "view/superAdmin/dataAccessLog/dataAccessLog.vue", "数据权限审计", "warning", 6), child("monitor", "timedTask", "timedTask", "view/systemTools/timedTask/index.vue", "定时任务", "timer", 7), child("monitor", "logViewer", "logViewer", "view/systemTools/logViewer/index.vue", "文件日志", "document", 8), child("media", "upload", "upload", "view/media/upload.vue", "媒体库(上传下载)", "upload", 1), child("media", "chunkUpload", "chunkUpload", "view/media/chunkUpload.vue", "大文件上传", "folder-add", 2), child("extensions", "email", "email", "modules/email/view/index.vue", "邮件发送", "message", 4), child("extensions", "anInfo", "anInfo", "modules/announcement/view/info.vue", "公告管理", "bell", 5)}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import "testing"
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ func TestDefaultIgnoredAPIsIncludeSwagger(t *testing.T) {
|
|||
"GET /uploads/file/*filepath": false,
|
||||
"HEAD /uploads/file/*filepath": false,
|
||||
}
|
||||
for _, api := range defaultIgnoredAPIs("uploads/file") {
|
||||
for _, api := range DefaultIgnoredAPIs("uploads/file") {
|
||||
key := api.Method + " " + api.Path
|
||||
if _, ok := wants[key]; ok {
|
||||
wants[key] = true
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/gormkit"
|
||||
"kra/pkg/pagination"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
|
@ -17,10 +19,10 @@ type taskPO struct {
|
|||
Description, Spec string
|
||||
WithSeconds bool
|
||||
ExecutorType, MethodName string
|
||||
Params jsonPO
|
||||
Params gormkit.JSON
|
||||
HTTPURL string
|
||||
HTTPMethod string
|
||||
HTTPHeader jsonPO
|
||||
HTTPHeader gormkit.JSON
|
||||
HTTPBody string `gorm:"type:text"`
|
||||
HTTPAllowPrivate, Enabled bool
|
||||
}
|
||||
|
|
@ -42,18 +44,18 @@ type taskLogPO struct {
|
|||
|
||||
func (taskLogPO) TableName() string { return "sys_timed_task_logs" }
|
||||
|
||||
type taskRepo struct{ data *Data }
|
||||
type taskRepo struct{ data Provider }
|
||||
|
||||
func NewTaskRepo(data *Data) biz.TaskRepo { return &taskRepo{data: data} }
|
||||
func NewTaskRepo(data Provider) biz.TaskRepo { return &taskRepo{data: data} }
|
||||
func taskToPO(v *biz.TimedTask) taskPO {
|
||||
return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: jsonPO(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: jsonPO(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: gormkit.JSON(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: gormkit.JSON(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
}
|
||||
func taskFromPO(v taskPO) *biz.TimedTask {
|
||||
return &biz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled}
|
||||
}
|
||||
func (r *taskRepo) CreateTask(ctx context.Context, v *biz.TimedTask) error {
|
||||
po := taskToPO(v)
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
|
|
@ -61,7 +63,7 @@ func (r *taskRepo) CreateTask(ctx context.Context, v *biz.TimedTask) error {
|
|||
}
|
||||
func (r *taskRepo) TaskNameExists(ctx context.Context, name string, excludeID uint) (bool, error) {
|
||||
var count int64
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("name = ?", name)
|
||||
db := r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("name = ?", name)
|
||||
if excludeID > 0 {
|
||||
db = db.Where("id <> ?", excludeID)
|
||||
}
|
||||
|
|
@ -70,20 +72,20 @@ func (r *taskRepo) TaskNameExists(ctx context.Context, name string, excludeID ui
|
|||
}
|
||||
func (r *taskRepo) UpdateTask(ctx context.Context, v *biz.TimedTask) error {
|
||||
po := taskToPO(v)
|
||||
return r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error
|
||||
}
|
||||
func (r *taskRepo) DeleteTask(ctx context.Context, id uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Delete(&taskPO{}, id).Error
|
||||
return r.data.DB().WithContext(ctx).Delete(&taskPO{}, id).Error
|
||||
}
|
||||
func (r *taskRepo) FindTask(ctx context.Context, id uint) (*biz.TimedTask, error) {
|
||||
var po taskPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return taskFromPO(po), nil
|
||||
}
|
||||
func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*biz.TimedTask, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&taskPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&taskPO{})
|
||||
if q != nil {
|
||||
if q.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+q.Name+"%")
|
||||
|
|
@ -100,7 +102,7 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTa
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []taskPO
|
||||
if err := applyPagination(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.TimedTask, 0, len(pos))
|
||||
|
|
@ -110,16 +112,16 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *biz.TimedTa
|
|||
return out, total, nil
|
||||
}
|
||||
func (r *taskRepo) ToggleTask(ctx context.Context, id uint, enabled bool) error {
|
||||
return r.data.gormDB.WithContext(ctx).Model(&taskPO{}).Where("id = ?", id).Update("enabled", enabled).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", id).Update("enabled", enabled).Error
|
||||
}
|
||||
func (r *taskRepo) RecordTaskLog(ctx context.Context, v *biz.TimedTaskLog) error {
|
||||
return r.data.gormDB.WithContext(ctx).Create(&taskLogPO{TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}).Error
|
||||
return r.data.DB().WithContext(ctx).Create(&taskLogPO{TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}).Error
|
||||
}
|
||||
func taskLogFromPO(v taskLogPO) *biz.TimedTaskLog {
|
||||
return &biz.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}
|
||||
}
|
||||
func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*biz.TimedTaskLog, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&taskLogPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&taskLogPO{})
|
||||
if taskID != 0 {
|
||||
db = db.Where("task_id = ?", taskID)
|
||||
}
|
||||
|
|
@ -131,7 +133,7 @@ func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []taskLogPO
|
||||
if err := applyPagination(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.TimedTaskLog, 0, len(pos))
|
||||
|
|
@ -142,7 +144,7 @@ func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint
|
|||
}
|
||||
func (r *taskRepo) CleanupLogs(ctx context.Context) error {
|
||||
now := time.Now()
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Unscoped().Where("created_at < ?", now.Add(-2160*time.Hour)).Delete(&operationPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/data/migration"
|
||||
)
|
||||
|
||||
// Data is a small in-package test harness. Production repositories depend on
|
||||
// Provider; tests keep the old, convenient field-based setup without coupling
|
||||
// the system module back to internal/data.
|
||||
type Data struct {
|
||||
gormDB *reloadableDB
|
||||
redis *reloadableRedis
|
||||
runtime *conf.Runtime
|
||||
databaseReady atomic.Bool
|
||||
}
|
||||
|
||||
func (d *Data) DB() *gorm.DB {
|
||||
if d == nil || d.gormDB == nil {
|
||||
return nil
|
||||
}
|
||||
return d.gormDB.DB()
|
||||
}
|
||||
|
||||
func (d *Data) Database(string) (*gorm.DB, error) { return d.DB(), nil }
|
||||
func (d *Data) DatabaseReady() bool { return d != nil && d.databaseReady.Load() }
|
||||
func (d *Data) Runtime() *conf.Runtime {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return d.runtime
|
||||
}
|
||||
|
||||
type reloadableDB struct{ db *gorm.DB }
|
||||
|
||||
func newReloadableDB(db *gorm.DB, _ any) *reloadableDB { return &reloadableDB{db: db} }
|
||||
func (r *reloadableDB) DB() *gorm.DB {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return r.db
|
||||
}
|
||||
func (r *reloadableDB) WithContext(ctx context.Context) *gorm.DB { return r.db.WithContext(ctx) }
|
||||
func (r *reloadableDB) close() {
|
||||
if r == nil || r.db == nil {
|
||||
return
|
||||
}
|
||||
sqlDB, err := r.db.DB()
|
||||
if err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type reloadableRedis struct{}
|
||||
|
||||
func newReloadableRedis(_ any) *reloadableRedis { return &reloadableRedis{} }
|
||||
|
||||
func openWithDriver(driver, dsn string) (*gorm.DB, error) {
|
||||
if driver != "sqlite" {
|
||||
return nil, fmt.Errorf("unsupported test database driver %q", driver)
|
||||
}
|
||||
return gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||
}
|
||||
|
||||
func migrateAll(db *gorm.DB) error {
|
||||
return migration.Run(db, []migration.Step{
|
||||
{ID: "test_baseline", Migrate: LegacySchemaMigration},
|
||||
{ID: "test_reconcile", Migrate: CurrentDataMigration},
|
||||
})
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
|
|
@ -17,7 +18,7 @@ import (
|
|||
|
||||
func (r *userRepo) FindUserByUsername(ctx context.Context, username string) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("username = ?", username).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("username = ?", username).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrUserNotFound
|
||||
}
|
||||
|
|
@ -28,7 +29,7 @@ func (r *userRepo) FindUserByUsername(ctx context.Context, username string) (*bi
|
|||
|
||||
func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrUserNotFound
|
||||
}
|
||||
|
|
@ -39,7 +40,7 @@ func (r *userRepo) FindUserByID(ctx context.Context, id uint) (*biz.User, error)
|
|||
|
||||
func (r *userRepo) FindUserByUUID(ctx context.Context, value string) (*biz.User, error) {
|
||||
var po userPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("uuid = ?", value).First(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("uuid = ?", value).First(&po).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, biz.ErrUserNotFound
|
||||
}
|
||||
|
|
@ -50,11 +51,11 @@ func (r *userRepo) FindUserByUUID(ctx context.Context, value string) (*biz.User,
|
|||
|
||||
func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error) {
|
||||
var authority authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&authority, "authority_id = ?", po.AuthorityID).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err := r.data.DB().WithContext(ctx).First(&authority, "authority_id = ?", po.AuthorityID).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
var authorityPOs []authorityPO
|
||||
if err := r.data.gormDB.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).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 {
|
||||
return nil, err
|
||||
}
|
||||
authorities := make([]biz.Authority, 0, len(authorityPOs))
|
||||
|
|
@ -66,7 +67,7 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error)
|
|||
_ = json.Unmarshal([]byte(po.OriginSetting), &setting)
|
||||
}
|
||||
var departmentPOs []departmentPO
|
||||
if err := r.data.gormDB.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).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 {
|
||||
return nil, err
|
||||
}
|
||||
departments := make([]biz.Department, 0, len(departmentPOs))
|
||||
|
|
@ -77,14 +78,14 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*biz.User, error)
|
|||
var primary *biz.Department
|
||||
if po.DeptID != 0 {
|
||||
var primaryPO departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&primaryPO, po.DeptID).Error; err == nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&primaryPO, po.DeptID).Error; err == nil {
|
||||
primary = deptFromPO(primaryPO)
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var positionPOs []positionPO
|
||||
if err := r.data.gormDB.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).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 {
|
||||
return nil, err
|
||||
}
|
||||
positions := make([]biz.Position, 0, len(positionPOs))
|
||||
|
|
@ -115,7 +116,7 @@ func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*biz.User, er
|
|||
if len(pos) == 0 {
|
||||
return []*biz.User{}, nil
|
||||
}
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
userIDs := make([]uint, 0, len(pos))
|
||||
authorityIDs := make([]uint, 0, len(pos))
|
||||
departmentIDs := make([]uint, 0, len(pos))
|
||||
|
|
@ -218,7 +219,7 @@ func (r *userRepo) loadUsers(ctx context.Context, pos []userPO) ([]*biz.User, er
|
|||
|
||||
func (r *userRepo) HasAuthorityMenu(ctx context.Context, authorityID uint, name string) (bool, error) {
|
||||
var count int64
|
||||
err := r.data.gormDB.WithContext(ctx).Model(&menuPO{}).
|
||||
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.name = ?", authorityID, name).
|
||||
Count(&count).Error
|
||||
|
|
@ -227,7 +228,7 @@ func (r *userRepo) HasAuthorityMenu(ctx context.Context, authorityID uint, name
|
|||
|
||||
func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*biz.Menu, error) {
|
||||
var pos []menuPO
|
||||
err := r.data.gormDB.WithContext(ctx).Table("sys_base_menus").
|
||||
err := r.data.DB().WithContext(ctx).Table("sys_base_menus").
|
||||
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
|
||||
|
|
@ -247,7 +248,7 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*b
|
|||
}
|
||||
if len(menuIDs) > 0 {
|
||||
var parameters []menuParameterPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(¶meters).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(¶meters).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, parameter := range parameters {
|
||||
|
|
@ -255,7 +256,7 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*b
|
|||
menu.Parameters = append(menu.Parameters, menuParameterFromPO(parameter))
|
||||
}
|
||||
var buttons []menuButtonPO
|
||||
if err := r.data.gormDB.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).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 {
|
||||
return nil, err
|
||||
}
|
||||
for _, button := range buttons {
|
||||
|
|
@ -278,7 +279,7 @@ func (r *userRepo) MenusByAuthority(ctx context.Context, authorityID uint) ([]*b
|
|||
}
|
||||
|
||||
func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*biz.User, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&userPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&userPO{})
|
||||
order := "id desc"
|
||||
if filter != nil {
|
||||
if filter.Username != "" {
|
||||
|
|
@ -306,7 +307,7 @@ func (r *userRepo) ListUsers(ctx context.Context, page, pageSize int, filter *bi
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []userPO
|
||||
if err := applyRequiredPagination(db.Order(order), page, pageSize, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.ApplyRequired(db.Order(order), page, pageSize, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
users, err := r.loadUsers(ctx, pos)
|
||||
|
|
@ -337,7 +338,7 @@ func (r *userRepo) CreateUserWithAuthorities(ctx context.Context, user *biz.User
|
|||
now := time.Now()
|
||||
po := userPO{UUID: user.UUID, Username: user.Username, Password: user.Password, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Phone: user.Phone, Email: user.Email, Enable: user.Enable, PasswordUpdatedAt: &now, MustChangePassword: user.MustChangePassword}
|
||||
var createdAuthorities []biz.Authority
|
||||
if err := r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// The compatible registration rejects an existing non-deleted username before creating the
|
||||
// account. The username column is intentionally not unique for legacy
|
||||
// schema compatibility, so keep the behavioral check here rather than
|
||||
|
|
@ -384,7 +385,7 @@ func (r *userRepo) UpdateUser(ctx context.Context, user *biz.User) error {
|
|||
if err := r.checkUserUpdateAuth(ctx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.updateUser(r.data.gormDB.WithContext(ctx), user)
|
||||
return r.updateUser(r.data.DB().WithContext(ctx), user)
|
||||
}
|
||||
|
||||
func (r *userRepo) checkUserUpdateAuth(ctx context.Context, user *biz.User) error {
|
||||
|
|
@ -418,7 +419,7 @@ func (r *userRepo) UpdateSelfUser(ctx context.Context, user *biz.User) error {
|
|||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", user.ID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *userRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User) error {
|
||||
|
|
@ -441,7 +442,7 @@ func (r *userRepo) FillDepartmentNamePaths(ctx context.Context, user *biz.User)
|
|||
nameByID := make(map[uint]string, len(ids))
|
||||
if len(ids) > 0 {
|
||||
var rows []departmentPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Select("id", "name").Where("id IN ?", ids).Find(&rows).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Select("id", "name").Where("id IN ?", ids).Find(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
|
|
@ -478,7 +479,7 @@ func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.User
|
|||
if err := access.checkAuthorityIDsAuth(ctx, authorityIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := r.updateUser(tx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -492,7 +493,7 @@ func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
|||
if err := (&authorityAccessRepo{data: r.data}).checkUserIDAuth(ctx, id, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("id = ?", id).Delete(&userPO{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -517,11 +518,11 @@ func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string,
|
|||
if clearMustChange {
|
||||
updates["must_change_password"] = false
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Updates(updates).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
func (r *userRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||
var pos []authorityPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*biz.Authority, 0, len(pos))
|
||||
|
|
@ -540,7 +541,7 @@ func (r *userRepo) SetUserAuthorities(ctx context.Context, id uint, authorityIDs
|
|||
if err := access.checkAuthorityIDsAuth(ctx, authorityIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var user userPO
|
||||
if err := tx.Where("id = ?", id).First(&user).Error; err != nil {
|
||||
return errors.New("查询用户数据失败")
|
||||
|
|
@ -567,7 +568,7 @@ func setUserAuthorities(tx *gorm.DB, id uint, authorityIDs []uint) error {
|
|||
}
|
||||
|
||||
func (r *userRepo) SetUserAuthority(ctx context.Context, id, authorityID uint) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
var count int64
|
||||
if err := db.Model(&userAuthorityPO{}).Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
|
|
@ -597,7 +598,7 @@ func (r *userRepo) SetUserSetting(ctx context.Context, id uint, setting map[stri
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Update("origin_setting", string(value)).Error
|
||||
return r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", id).Update("origin_setting", string(value)).Error
|
||||
}
|
||||
|
||||
// ActiveName temporarily carries the parent menu name during seeding. It is
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz"
|
||||
"kra/pkg/pagination"
|
||||
)
|
||||
|
||||
type versionPO struct {
|
||||
|
|
@ -22,32 +23,32 @@ type versionPO struct {
|
|||
|
||||
func (versionPO) TableName() string { return "sys_versions" }
|
||||
|
||||
type versionRepo struct{ data *Data }
|
||||
type versionRepo struct{ data Provider }
|
||||
|
||||
func NewVersionRepo(data *Data) biz.VersionRepo { return &versionRepo{data: data} }
|
||||
func NewVersionRepo(data Provider) biz.VersionRepo { return &versionRepo{data: data} }
|
||||
func versionFromPO(v versionPO) *biz.Version {
|
||||
return &biz.Version{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data}
|
||||
}
|
||||
func (r *versionRepo) CreateVersion(ctx context.Context, v *biz.Version) error {
|
||||
po := versionPO{Name: v.Name, Code: v.Code, Description: v.Description, Data: v.Data}
|
||||
if err := r.data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
v.ID = po.ID
|
||||
return nil
|
||||
}
|
||||
func (r *versionRepo) DeleteVersions(ctx context.Context, ids []uint) error {
|
||||
return r.data.gormDB.WithContext(ctx).Where("id IN ?", ids).Delete(&versionPO{}).Error
|
||||
return r.data.DB().WithContext(ctx).Where("id IN ?", ids).Delete(&versionPO{}).Error
|
||||
}
|
||||
func (r *versionRepo) FindVersion(ctx context.Context, id uint) (*biz.Version, error) {
|
||||
var po versionPO
|
||||
if err := r.data.gormDB.WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return versionFromPO(po), nil
|
||||
}
|
||||
func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*biz.Version, int64, error) {
|
||||
db := r.data.gormDB.WithContext(ctx).Model(&versionPO{})
|
||||
db := r.data.DB().WithContext(ctx).Model(&versionPO{})
|
||||
if len(createdAtRange) == 2 && createdAtRange[0] != nil && createdAtRange[1] != nil {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", createdAtRange[0], createdAtRange[1])
|
||||
}
|
||||
|
|
@ -62,7 +63,7 @@ func (r *versionRepo) ListVersions(ctx context.Context, page, size int, name, co
|
|||
return nil, 0, err
|
||||
}
|
||||
var pos []versionPO
|
||||
if err := applyPagination(db, page, size, 100).Find(&pos).Error; err != nil {
|
||||
if err := pagination.Apply(db, page, size, 100).Find(&pos).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out := make([]*biz.Version, 0, len(pos))
|
||||
|
|
@ -75,7 +76,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||
bundle := &biz.VersionBundle{}
|
||||
var menus []menuPO
|
||||
if len(menuIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", menuIDs).Order("sort,id").Find(&menus).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id IN ?", menuIDs).Order("sort,id").Find(&menus).Error; err != nil {
|
||||
return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +87,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||
}
|
||||
if len(menuIDs) > 0 {
|
||||
var buttons []menuButtonPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(&buttons).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(&buttons).Error; err != nil {
|
||||
return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
|
||||
}
|
||||
for _, button := range buttons {
|
||||
|
|
@ -95,7 +96,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||
}
|
||||
}
|
||||
var parameters []menuParameterPO
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(¶meters).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("sys_base_menu_id IN ?", menuIDs).Find(¶meters).Error; err != nil {
|
||||
return nil, &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
|
||||
}
|
||||
for _, parameter := range parameters {
|
||||
|
|
@ -142,7 +143,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||
}
|
||||
var apis []apiPO
|
||||
if len(apiIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", apiIDs).Find(&apis).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id IN ?", apiIDs).Find(&apis).Error; err != nil {
|
||||
return nil, &biz.VersionStageError{Stage: biz.VersionStageAPIs, Err: err}
|
||||
}
|
||||
}
|
||||
|
|
@ -153,7 +154,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||
}
|
||||
var dictionaries []dictionaryPO
|
||||
if len(dictIDs) > 0 {
|
||||
if err := r.data.gormDB.WithContext(ctx).Where("id IN ?", dictIDs).Find(&dictionaries).Error; err != nil {
|
||||
if err := r.data.DB().WithContext(ctx).Where("id IN ?", dictIDs).Find(&dictionaries).Error; err != nil {
|
||||
return nil, &biz.VersionStageError{Stage: biz.VersionStageDictionaries, Err: err}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +179,7 @@ func (r *versionRepo) BuildVersionBundle(ctx context.Context, menuIDs, apiIDs, d
|
|||
return bundle, nil
|
||||
}
|
||||
func (r *versionRepo) ImportVersionBundle(ctx context.Context, b *biz.VersionBundle) error {
|
||||
db := r.data.gormDB.WithContext(ctx)
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
if len(b.Menus) > 0 {
|
||||
if err := db.Transaction(func(tx *gorm.DB) error { return importVersionMenus(tx, b.Menus, 0) }); err != nil {
|
||||
return &biz.VersionStageError{Stage: biz.VersionStageMenus, Err: err}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package data
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -6,33 +6,34 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"kra/internal/biz"
|
||||
)
|
||||
|
||||
type memoryCacheEntry struct {
|
||||
type RedisProvider interface {
|
||||
RedisClient() redis.UniversalClient
|
||||
}
|
||||
|
||||
type memoryEntry struct {
|
||||
value string
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type cacheStore struct {
|
||||
data *Data
|
||||
mu sync.Mutex
|
||||
memory map[string]memoryCacheEntry
|
||||
type Store struct {
|
||||
provider RedisProvider
|
||||
mu sync.Mutex
|
||||
memory map[string]memoryEntry
|
||||
}
|
||||
|
||||
const maxMemoryCacheEntries = 10000
|
||||
const maxMemoryEntries = 10000
|
||||
|
||||
func NewCache(data *Data) biz.Cache {
|
||||
return &cacheStore{data: data, memory: make(map[string]memoryCacheEntry)}
|
||||
func New(provider RedisProvider) biz.Cache {
|
||||
return &Store{provider: provider, memory: make(map[string]memoryEntry)}
|
||||
}
|
||||
|
||||
func (s *cacheStore) client() redis.UniversalClient {
|
||||
return s.data.redis.load()
|
||||
}
|
||||
func (s *Store) client() redis.UniversalClient { return s.provider.RedisClient() }
|
||||
|
||||
func (s *cacheStore) Get(ctx context.Context, key string) (string, bool, error) {
|
||||
func (s *Store) Get(ctx context.Context, key string) (string, bool, error) {
|
||||
if client := s.client(); client != nil {
|
||||
value, err := client.Get(ctx, key).Result()
|
||||
if err == nil {
|
||||
|
|
@ -56,14 +57,14 @@ func (s *cacheStore) Get(ctx context.Context, key string) (string, bool, error)
|
|||
return entry.value, true, nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) Set(ctx context.Context, key, value string, expiration time.Duration) error {
|
||||
func (s *Store) Set(ctx context.Context, key, value string, expiration time.Duration) error {
|
||||
if client := s.client(); client != nil {
|
||||
return client.Set(ctx, key, value, expiration).Err()
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.makeMemoryRoom(key)
|
||||
entry := memoryCacheEntry{value: value}
|
||||
s.makeRoom(key)
|
||||
entry := memoryEntry{value: value}
|
||||
if expiration > 0 {
|
||||
entry.expiresAt = time.Now().Add(expiration)
|
||||
}
|
||||
|
|
@ -71,7 +72,7 @@ func (s *cacheStore) Set(ctx context.Context, key, value string, expiration time
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) Delete(ctx context.Context, key string) error {
|
||||
func (s *Store) Delete(ctx context.Context, key string) error {
|
||||
if client := s.client(); client != nil {
|
||||
return client.Del(ctx, key).Err()
|
||||
}
|
||||
|
|
@ -81,7 +82,7 @@ func (s *cacheStore) Delete(ctx context.Context, key string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
func (s *Store) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
if client := s.client(); client != nil {
|
||||
value, err := client.Incr(ctx, key).Result()
|
||||
if err != nil {
|
||||
|
|
@ -96,7 +97,7 @@ func (s *cacheStore) Increment(ctx context.Context, key string, expiration time.
|
|||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.makeMemoryRoom(key)
|
||||
s.makeRoom(key)
|
||||
entry, ok := s.memory[key]
|
||||
if ok && !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
|
||||
ok = false
|
||||
|
|
@ -114,8 +115,8 @@ func (s *cacheStore) Increment(ctx context.Context, key string, expiration time.
|
|||
return value, nil
|
||||
}
|
||||
|
||||
func (s *cacheStore) makeMemoryRoom(incoming string) {
|
||||
if len(s.memory) < maxMemoryCacheEntries {
|
||||
func (s *Store) makeRoom(incoming string) {
|
||||
if len(s.memory) < maxMemoryEntries {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
|
|
@ -124,14 +125,12 @@ func (s *cacheStore) makeMemoryRoom(incoming string) {
|
|||
delete(s.memory, key)
|
||||
}
|
||||
}
|
||||
if len(s.memory) < maxMemoryCacheEntries {
|
||||
if len(s.memory) < maxMemoryEntries {
|
||||
return
|
||||
}
|
||||
if _, exists := s.memory[incoming]; exists {
|
||||
return
|
||||
}
|
||||
// The in-memory store is only a Redis fallback. A bounded arbitrary eviction
|
||||
// is preferable to unbounded growth under IP churn.
|
||||
for key := range s.memory {
|
||||
delete(s.memory, key)
|
||||
break
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
type Reloadable struct {
|
||||
mu sync.RWMutex
|
||||
current biz.FileStorage
|
||||
}
|
||||
|
||||
func NewFileStorage(runtime *conf.Runtime) (*Reloadable, error) {
|
||||
var config *conf.AdminBackend
|
||||
if runtime != nil {
|
||||
config = runtime.Admin()
|
||||
}
|
||||
return NewReloadable(config)
|
||||
}
|
||||
|
||||
func NewReloadable(config *conf.AdminBackend) (*Reloadable, error) {
|
||||
current, err := New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Reloadable{current: current}, nil
|
||||
}
|
||||
|
||||
func (s *Reloadable) Replace(current biz.FileStorage) {
|
||||
s.mu.Lock()
|
||||
s.current = current
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Reloadable) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Put(ctx, name, reader)
|
||||
}
|
||||
func (s *Reloadable) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Open(ctx, name)
|
||||
}
|
||||
func (s *Reloadable) Delete(ctx context.Context, name string) error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Delete(ctx, name)
|
||||
}
|
||||
func (s *Reloadable) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.Compose(ctx, names, destination)
|
||||
}
|
||||
func (s *Reloadable) DeletePrefix(ctx context.Context, prefix string) error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.DeletePrefix(ctx, prefix)
|
||||
}
|
||||
func (s *Reloadable) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.current.List(ctx, prefix, cursor, limit)
|
||||
}
|
||||
Loading…
Reference in New Issue