优化结构

This commit is contained in:
Yvan
2026-08-28 09:19:54 +08:00
parent 03a4131f6e
commit 279692375b
57 changed files with 778 additions and 799 deletions
+6 -19
View File
@@ -48,8 +48,7 @@ func (d *Data) persistConfigValues(dataConfig *config.Data, adminConfig *config.
if err = document.Set("admin", fileAdmin); err != nil {
return err
}
document.Delete("admin", "storage")
document.Delete("admin", "email")
document.DeleteIntegrationConfig()
if adminConfig.System != nil {
if err = document.SetServerHTTPPort(adminConfig.System.Addr); err != nil {
return err
@@ -100,8 +99,7 @@ func (d *Data) persistDatabaseConfig(database *config.Database, signingKey strin
return err
}
}
document.Delete("admin", "storage")
document.Delete("admin", "email")
document.DeleteIntegrationConfig()
return document.Save()
}
@@ -119,8 +117,7 @@ func (d *Data) removeIntegrationConfigFromFile() error {
if !document.Has("admin", "storage") && !document.Has("admin", "email") {
return nil
}
document.Delete("admin", "storage")
document.Delete("admin", "email")
document.DeleteIntegrationConfig()
return document.Save()
}
@@ -135,6 +132,7 @@ func (d *Data) reloadConfig(ctx context.Context) error {
if err != nil {
return err
}
next = config.MergeRuntimeConfig(d.Config(), next)
if next.Data == nil || next.Data.Database == nil || next.Admin == nil {
return fmt.Errorf("data.database and admin configuration are required")
}
@@ -170,23 +168,12 @@ func (d *Data) reloadConfig(ctx context.Context) error {
return fmt.Errorf("reload database migrations: %w", err)
}
}
legacyStorage := next.Admin.Storage
legacyEmail := next.Admin.Email
currentAdmin := d.runtime.Admin()
if legacyStorage == nil {
if currentAdmin != nil {
legacyStorage = currentAdmin.Storage
}
}
if legacyEmail == nil && currentAdmin != nil {
legacyEmail = currentAdmin.Email
}
storageConfig, err := dataintegration.ResolveStorageConfig(candidateDB.WithContext(ctx), legacyStorage)
storageConfig, err := dataintegration.ResolveStorageConfig(candidateDB.WithContext(ctx), next.Admin.Storage)
if err != nil {
return fmt.Errorf("reload storage configuration: %w", err)
}
next.Admin.Storage = storageConfig
emailConfig, err := dataintegration.ResolveEmailConfig(candidateDB.WithContext(ctx), legacyEmail)
emailConfig, err := dataintegration.ResolveEmailConfig(candidateDB.WithContext(ctx), next.Admin.Email)
if err != nil {
return fmt.Errorf("reload email configuration: %w", err)
}
+5 -1
View File
@@ -57,7 +57,7 @@ func normalizedDriver(driver string) string {
}
func databaseDSN(c *config.Database, name string) (string, error) {
if c.Source != "" && c.Host == "" && c.Path == "" {
if c.Source != "" && !databaseHasStructuredFields(c) {
return c.Source, nil
}
driver := normalizedDriver(c.Driver)
@@ -118,6 +118,10 @@ func databaseDSN(c *config.Database, name string) (string, error) {
return "", fmt.Errorf("unsupported database driver %q", c.Driver)
}
func databaseHasStructuredFields(c *config.Database) bool {
return c != nil && (c.Host != "" || c.Port != "" || c.User != "" || c.Password != "" || c.Name != "" || c.Config != "" || c.Path != "")
}
func gormConfig(config *config.Database, appLogger *slog.Logger) *gorm.Config {
level := logger.Info
switch strings.ToLower(config.LogMode) {
+2 -24
View File
@@ -43,15 +43,11 @@ func refreshDatabaseSource(database *configpkg.Database) error {
if database == nil {
return nil
}
hasStructuredConfig := database.Host != "" || database.Port != "" || database.User != "" || database.Password != "" || database.Name != "" || database.Config != "" || database.Path != ""
if !hasStructuredConfig {
if !databaseHasStructuredFields(database) {
return nil
}
previousSource := database.Source
database.Source = ""
source, err := databaseDSN(database, "")
if err != nil {
database.Source = previousSource
return err
}
database.Source = source
@@ -64,25 +60,7 @@ func (d *Data) PersistRuntimeConfig(ctx context.Context, value *configpkg.Config
if current == nil {
current = &configpkg.Config{}
}
next := configpkg.Clone(value)
if next == nil {
next = configpkg.Clone(current)
}
if next.Data == nil {
next.Data = configpkg.CloneData(current.Data)
}
if next.Admin == nil {
next.Admin = configpkg.CloneAdmin(current.Admin)
}
if next.Admin != nil && current.Admin != nil {
if next.Admin.Storage == nil {
next.Admin.Storage = configpkg.CloneStorage(current.Admin.Storage)
}
if next.Admin.Email == nil {
next.Admin.Email = configpkg.CloneEmail(current.Admin.Email)
}
next.Admin.ConfigPath = current.Admin.ConfigPath
}
next := configpkg.MergeRuntimeConfig(current, value)
if next.Data == nil {
next.Data = &configpkg.Data{}
}
+32 -62
View File
@@ -9,7 +9,7 @@ import (
"fmt"
integrationbiz "kra/internal/biz/integration"
bizpayment "kra/internal/biz/payment"
"net/url"
"kra/internal/paymentkit"
"strconv"
"strings"
"time"
@@ -21,10 +21,11 @@ type paymentRepo struct {
data Provider
config integrationbiz.PaymentConfigReader
factory bizpayment.PaymentAdapterFactory
orders bizpayment.PaymentOrderRepo
}
func NewPaymentRepo(data Provider, config integrationbiz.PaymentConfigReader, factory bizpayment.PaymentAdapterFactory) bizpayment.PaymentRepo {
return &paymentRepo{data: data, config: config, factory: factory}
func NewPaymentRepo(data Provider, config integrationbiz.PaymentConfigReader, factory bizpayment.PaymentAdapterFactory, orders bizpayment.PaymentOrderRepo) bizpayment.PaymentRepo {
return &paymentRepo{data: data, config: config, factory: factory, orders: orders}
}
func (r *paymentRepo) values(ctx context.Context, provider string) (map[string]any, error) {
@@ -71,7 +72,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
add("config", "failed", err.Error(), "", started)
return test, err
}
test.Mode = strings.ToLower(strings.TrimSpace(text(values, "environment")))
test.Mode = strings.ToLower(strings.TrimSpace(paymentkit.Text(values, "environment")))
configStart := time.Now()
if err = integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values); err != nil {
add("config", "failed", err.Error(), "", configStart)
@@ -94,12 +95,16 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
add("adapter", "failed", err.Error(), "", time.Now())
return test, err
}
if r.orders == nil {
err = errors.New("支付订单仓储未接入")
add("local_order", "failed", err.Error(), "", time.Now())
return test, err
}
req := paymentTestRequest(provider, values)
test.TradeNo = req.TradeNo
orders := &paymentOrderRepo{data: r.data}
extra, _ := json.Marshal(req.Extra)
localStart := time.Now()
order, _, err := orders.CreatePaymentOrder(ctx, &bizpayment.PaymentOrder{
order, _, err := r.orders.CreatePaymentOrder(ctx, &bizpayment.PaymentOrder{
TradeNo: req.TradeNo, Provider: provider, BusinessType: req.BusinessType, BusinessID: req.BusinessID,
Subject: req.Subject, PaymentMode: bizpayment.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount,
Currency: req.Currency, PaymentStatus: bizpayment.PaymentStatusInitialized, FulfillmentStatus: bizpayment.FulfillmentStatusPending,
@@ -127,7 +132,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
add("create", "failed", err.Error(), req.TradeNo, createStart)
return test, err
}
if order, err = orders.RecordPaymentCreate(ctx, provider, req.TradeNo, paymentTestProviderUpdate(created)); err != nil {
if order, err = r.orders.RecordPaymentCreate(ctx, provider, req.TradeNo, paymentTestProviderUpdate(created)); err != nil {
add("local_order", "failed", "记录第三方下单结果失败: "+err.Error(), req.TradeNo, createStart)
return test, err
}
@@ -142,7 +147,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
queryID = req.TradeNo
}
if provider == bizpayment.PaymentApple {
queryID = strings.TrimSpace(text(values, "test_transaction_id"))
queryID = strings.TrimSpace(paymentkit.Text(values, "test_transaction_id"))
if queryID == "" {
err = errors.New("Apple 连通性测试需要配置 test_transaction_id(沙箱交易 ID)")
add("query", "failed", err.Error(), req.TradeNo, time.Now())
@@ -162,7 +167,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
return test, err
}
if provider != bizpayment.PaymentApple {
if order, err = orders.ApplyPaymentResult(ctx, provider, req.TradeNo, paymentTestProviderUpdate(queried)); err != nil {
if order, err = r.orders.ApplyPaymentResult(ctx, provider, req.TradeNo, paymentTestProviderUpdate(queried)); err != nil {
add("local_order", "failed", "回写测试查单结果失败: "+err.Error(), req.TradeNo, queryStart)
return test, err
}
@@ -185,7 +190,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
return test, nil
}
refundStart := time.Now()
order, refundToken, beginErr := orders.BeginPaymentRefund(ctx, provider, req.TradeNo, req.Amount, time.Minute)
order, refundToken, beginErr := r.orders.BeginPaymentRefund(ctx, provider, req.TradeNo, req.Amount, time.Minute)
if beginErr != nil {
add("refund", "failed", beginErr.Error(), req.TradeNo, refundStart)
return test, beginErr
@@ -201,7 +206,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpa
add("refund", "failed", err.Error(), req.TradeNo, refundStart)
return test, err
}
if _, err = orders.CompletePaymentRefundRequest(ctx, provider, req.TradeNo, refundToken, true, ""); err != nil {
if _, err = r.orders.CompletePaymentRefundRequest(ctx, provider, req.TradeNo, refundToken, true, ""); err != nil {
add("local_order", "failed", "回写测试退款结果失败: "+err.Error(), req.TradeNo, refundStart)
return test, err
}
@@ -301,19 +306,19 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string]
func paymentTestRequest(provider string, values map[string]any) *bizpayment.PaymentRequest {
tradeNo := "kra-test-" + time.Now().UTC().Format("20060102150405.000000000")
amount := configuredInt64(values, "test_amount", 1)
amount := paymentkit.ConfiguredInt64(values, "test_amount", 1)
if amount <= 0 {
amount = 1
}
req := &bizpayment.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}}
req := &bizpayment.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(paymentkit.FirstText(values, "test_currency", "currency", "fee_type")), NotifyURL: paymentkit.Text(values, "notify_url"), ReturnURL: paymentkit.Text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}}
if req.Currency == "" {
req.Currency = "CNY"
}
if provider == bizpayment.PaymentApple {
req.TradeNo = uuid.NewString()
req.Extra["product_id"] = firstAny(values, "product_id", "test_product_id")
req.Extra["product_id"] = paymentkit.FirstText(values, "product_id", "test_product_id")
}
if raw := strings.TrimSpace(text(values, "test_extra")); raw != "" {
if raw := strings.TrimSpace(paymentkit.Text(values, "test_extra")); raw != "" {
var extra map[string]any
if json.Unmarshal([]byte(raw), &extra) == nil {
for key, value := range extra {
@@ -322,7 +327,7 @@ func paymentTestRequest(provider string, values map[string]any) *bizpayment.Paym
}
}
for _, key := range []string{"trade_type", "method", "pay_type", "channel", "openid", "open_id", "auth_code", "authcode", "barcode"} {
if value := text(values, key); value != "" {
if value := paymentkit.Text(values, key); value != "" {
req.Extra[key] = value
}
}
@@ -333,16 +338,16 @@ func validatePaymentTestSettings(provider string, values map[string]any) error {
if !testModeEnabled(values) {
return errors.New("请先打开 test_mode(允许执行渠道测试)")
}
if raw := strings.TrimSpace(text(values, "test_extra")); raw != "" {
if raw := strings.TrimSpace(paymentkit.Text(values, "test_extra")); raw != "" {
var extra map[string]any
if err := json.Unmarshal([]byte(raw), &extra); err != nil {
return fmt.Errorf("test_extra 必须是 JSON 对象: %w", err)
}
}
if provider == bizpayment.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" {
if provider == bizpayment.PaymentApple && strings.TrimSpace(paymentkit.Text(values, "test_transaction_id")) == "" {
return errors.New("Apple 测试需要 test_transaction_id(沙箱交易 ID)")
}
if provider == bizpayment.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" {
if provider == bizpayment.PaymentApple && strings.TrimSpace(paymentkit.FirstText(values, "test_product_id", "product_id")) == "" {
return errors.New("Apple 测试需要 test_product_id(沙箱商品 ID)")
}
return nil
@@ -374,8 +379,8 @@ func (r *paymentRepo) Create(ctx context.Context, req *bizpayment.PaymentRequest
return nil, err
}
effective := *req
effective.NotifyURL = strings.TrimSpace(text(c, "notify_url"))
effective.ReturnURL = strings.TrimSpace(text(c, "return_url"))
effective.NotifyURL = strings.TrimSpace(paymentkit.Text(c, "notify_url"))
effective.ReturnURL = strings.TrimSpace(paymentkit.Text(c, "return_url"))
if paymentCreateRequiresNotifyURL(req.Provider, req.Extra, c) && effective.NotifyURL == "" {
return nil, fmt.Errorf("支付渠道 %s 未配置服务端 notify_url", req.Provider)
}
@@ -412,9 +417,9 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an
case bizpayment.PaymentLakala:
keys = []string{"method", "pay_method", "trade_type"}
}
value := firstAny(extra, keys...)
value := paymentkit.FirstText(extra, keys...)
if value == "" {
value = firstAny(config, keys...)
value = paymentkit.FirstText(config, keys...)
}
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
switch provider {
@@ -484,8 +489,8 @@ func paymentCallbackEventID(callback *bizpayment.PaymentCallback, result *bizpay
if callback == nil {
return ""
}
fields := callbackFields(callback)
if eventID := strings.TrimSpace(first(fields, "event_id", "notify_id", "notificationUUID", "id")); eventID != "" {
fields := paymentkit.CallbackFields(callback.Query, callback.Headers, callback.Body)
if eventID := strings.TrimSpace(paymentkit.FirstString(fields, "event_id", "notify_id", "notificationUUID", "id")); eventID != "" {
return eventID
}
hash := sha256.Sum256(append([]byte(callback.Provider+"\x00"), callback.Body...))
@@ -498,12 +503,12 @@ func paymentCallbackAck(provider string, values map[string]any, success bool) bi
if !success {
prefix = "callback_failure_"
}
if configured := strings.TrimSpace(text(values, prefix+"status")); configured != "" {
if configured := strings.TrimSpace(paymentkit.Text(values, prefix+"status")); configured != "" {
if status, err := strconv.Atoi(configured); err == nil && status >= 200 && status <= 599 {
ack.StatusCode = status
}
}
if contentType := strings.TrimSpace(text(values, prefix+"content_type")); contentType != "" {
if contentType := strings.TrimSpace(paymentkit.Text(values, prefix+"content_type")); contentType != "" {
ack.ContentType = contentType
}
if body, exists := values[prefix+"body"]; exists {
@@ -512,41 +517,6 @@ func paymentCallbackAck(provider string, values map[string]any, success bool) bi
return ack
}
func callbackFields(callback *bizpayment.PaymentCallback) map[string]string {
fields := map[string]string{}
for key, value := range callback.Query {
fields[key] = value
}
contentType := strings.ToLower(callback.Headers["Content-Type"])
if strings.Contains(contentType, "application/x-www-form-urlencoded") {
if values, err := url.ParseQuery(string(callback.Body)); err == nil {
for key, value := range values {
if len(value) > 0 {
fields[key] = value[0]
}
}
}
}
var object map[string]any
if json.Unmarshal(callback.Body, &object) == nil {
for key, value := range object {
if text, ok := value.(string); ok {
fields[key] = text
}
}
}
return fields
}
func first(values map[string]string, keys ...string) string {
for _, key := range keys {
if values[key] != "" {
return values[key]
}
}
return ""
}
func contains(values []string, value string) bool {
for _, item := range values {
if item == value {
-17
View File
@@ -1,17 +0,0 @@
package payment
import "kra/internal/paymentkit"
func text(values map[string]any, key string) string {
return paymentkit.Text(values, key)
}
func firstAny(values map[string]any, keys ...string) string {
return paymentkit.FirstText(values, keys...)
}
// configuredInt64 keeps repository-side validation independent from the
// provider adapter package while sharing the canonical payment parser.
func configuredInt64(values map[string]any, key string, fallback int64) int64 {
return paymentkit.ConfiguredInt64(values, key, fallback)
}
+5 -4
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
bizpayment "kra/internal/biz/payment"
"kra/internal/paymentkit"
"strings"
"time"
@@ -476,12 +477,12 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *bizpayment.P
}
func normalizeOrderPaymentStatus(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "success", "paid", "fulfilled":
switch paymentkit.NormalizeStatus(value, "") {
case "success":
return bizpayment.PaymentStatusPaid
case "pending", "created", "client_pending", "processing":
case "pending":
return bizpayment.PaymentStatusPending
case "failed", "closed", "cancelled", "canceled":
case "failed":
return bizpayment.PaymentStatusFailed
default:
return ""
+3 -17
View File
@@ -7,7 +7,6 @@ import (
"time"
"kra/pkg/database/gormkit"
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
@@ -72,22 +71,9 @@ func (r *announcementRepo) List(ctx context.Context, filter system.AnnouncementF
if filter.StartCreatedAt != nil && filter.EndCreatedAt != nil {
db = db.Where("created_at BETWEEN ? AND ?", filter.StartCreatedAt, filter.EndCreatedAt)
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
if filter.PageSize > 0 {
db = pagination.Apply(db, filter.Page, filter.PageSize, 100)
}
var pos []announcementPO
if err := db.Find(&pos).Error; err != nil {
return nil, 0, err
}
items := make([]*system.Announcement, 0, len(pos))
for _, po := range pos {
items = append(items, announcementToBiz(po))
}
return items, total, nil
return listRows(db, filter.Page, filter.PageSize, filter.PageSize > 0, func(po announcementPO) *system.Announcement {
return announcementToBiz(po)
})
}
func (r *announcementRepo) UserOptions(ctx context.Context) ([]system.UserOption, error) {
+12 -12
View File
@@ -131,15 +131,7 @@ func (r *authorityAccessRepo) ensureAuthorityParentAcyclic(ctx context.Context,
}
func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *system.Authority) error {
if value.DataScope == 0 {
value.DataScope = 1
} else if value.DataScope < 1 || value.DataScope > 5 {
return errInvalidDataScope
}
if err := r.checkDataScopeGrant(ctx, value.DataScope, nil); err != nil {
return err
}
actor, allowed, strict, err := r.strictAuthorityAccess(ctx)
actor, allowed, strict, err := r.prepareAuthorityCreate(ctx, value)
if err != nil {
return err
}
@@ -208,16 +200,24 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *system
return nil
})
}
func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *system.Authority) error {
func (r *authorityAccessRepo) prepareAuthorityCreate(ctx context.Context, value *system.Authority) (system.Actor, map[uint]bool, bool, error) {
if value.DataScope == 0 {
value.DataScope = 1
} else if value.DataScope < 1 || value.DataScope > 5 {
return errInvalidDataScope
return system.Actor{}, nil, false, errInvalidDataScope
}
if err := r.checkDataScopeGrant(ctx, value.DataScope, nil); err != nil {
return err
return system.Actor{}, nil, false, err
}
actor, allowed, strict, err := r.strictAuthorityAccess(ctx)
if err != nil {
return system.Actor{}, nil, false, err
}
return actor, allowed, strict, nil
}
func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *system.Authority) error {
actor, allowed, strict, err := r.prepareAuthorityCreate(ctx, value)
if err != nil {
return err
}
+2 -8
View File
@@ -5,8 +5,6 @@ import (
"kra/internal/biz/system"
"time"
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
@@ -39,12 +37,8 @@ func (r *auditQueryRepo) ListDataAccess(ctx context.Context, page, size int, q *
db = db.Where("target_table LIKE ?", "%"+q.TargetTable+"%")
}
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
var pos []DataAccessLogPO
if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
pos, total, err := queryRows[DataAccessLogPO](db.Order("id desc"), page, size, true, true)
if err != nil {
return nil, 0, err
}
out := make([]*system.DataAccessLog, 0, len(pos))
+3 -15
View File
@@ -5,8 +5,6 @@ import (
"kra/internal/biz/system"
"time"
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
@@ -92,17 +90,7 @@ func (r *auditQueryRepo) ListErrors(ctx context.Context, page, size int, q *syst
db = db.Where("info LIKE ?", "%"+*q.Info+"%")
}
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
var pos []errorRecordPO
if err := pagination.Apply(db.Order("created_at desc"), page, size, 100).Find(&pos).Error; err != nil {
return nil, 0, err
}
out := make([]*system.ErrorRecord, 0, len(pos))
for _, po := range pos {
out = append(out, errorFromPO(po))
}
return out, total, nil
return listRows(db.Order("created_at desc"), page, size, true, func(po errorRecordPO) *system.ErrorRecord {
return errorFromPO(po)
})
}
+3 -5
View File
@@ -10,6 +10,8 @@ import (
"strings"
"time"
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
@@ -171,11 +173,7 @@ func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q
return nil, 0, err
}
var pos []exportTemplatePO
// This module applies pageSize and its raw offset formula verbatim instead
// of using the shared PageInfo normalization.
if size != 0 {
db = db.Limit(size).Offset(size * (page - 1))
}
db = pagination.Apply(db, page, size, 100)
if err := db.Find(&pos).Error; err != nil {
return nil, 0, err
}
+42
View File
@@ -0,0 +1,42 @@
package system
import (
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
// listRows centralizes the storage-only part shared by simple repositories:
// count the filtered query, apply the bounded page, load POs, and convert them
// at the data/biz boundary. Domain-specific filters and ordering stay in each
// repository.
func queryRows[PO any](db *gorm.DB, page, size int, paginate, required bool) ([]PO, int64, error) {
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
if paginate {
if required {
db = pagination.ApplyRequired(db, page, size, 100)
} else {
db = pagination.Apply(db, page, size, 100)
}
}
var pos []PO
if err := db.Find(&pos).Error; err != nil {
return nil, 0, err
}
return pos, total, nil
}
func listRows[PO any, DO any](db *gorm.DB, page, size int, paginate bool, convert func(PO) *DO) ([]*DO, int64, error) {
pos, total, err := queryRows[PO](db, page, size, paginate, false)
if err != nil {
return nil, 0, err
}
out := make([]*DO, 0, len(pos))
for _, po := range pos {
out = append(out, convert(po))
}
return out, total, nil
}
+2 -8
View File
@@ -5,8 +5,6 @@ import (
"kra/internal/biz/system"
"time"
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
@@ -53,12 +51,8 @@ func (r *auditQueryRepo) ListLogins(ctx context.Context, page, size int, q *syst
db = db.Where("created_at <= ?", *q.EndCreatedAt)
}
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
var pos []loginLogPO
if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
pos, total, err := queryRows[loginLogPO](db.Order("id desc"), page, size, true, true)
if err != nil {
return nil, 0, err
}
ids := make([]uint, 0, len(pos))
+3
View File
@@ -75,6 +75,9 @@ func (r *mediaRepo) CreateUploadSession(ctx context.Context, v *system.UploadSes
func (r *mediaRepo) FindUploadSession(ctx context.Context, id uint) (*system.UploadSession, error) {
var po uploadSessionPO
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, system.ErrUploadSessionNotFound
}
return nil, err
}
return uploadFromPO(po), nil
+3 -19
View File
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"kra/internal/biz/system"
"time"
"gorm.io/gorm"
)
@@ -14,12 +13,7 @@ type menuRepo struct{ data Provider }
func NewMenuRepo(data Provider) system.MenuRepo { return &menuRepo{data: data} }
func menuFromPO(po menuPO) *system.Menu {
var deletedAt *time.Time
if po.DeletedAt.Valid {
value := po.DeletedAt.Time
deletedAt = &value
}
return &system.Menu{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType}
return &system.Menu{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAtPointer(po.DeletedAt), ID: po.ID, ParentID: po.ParentID, Path: po.Path, Name: po.Name, Hidden: po.Hidden, Component: po.Component, Sort: po.Sort, ActiveName: po.ActiveName, KeepAlive: po.KeepAlive, DefaultMenu: po.DefaultMenu, Title: po.Title, Icon: po.Icon, CloseTab: po.CloseTab, TransitionType: po.TransitionType}
}
func menuToPO(v *system.Menu) menuPO {
@@ -239,21 +233,11 @@ func (r *menuRepo) List(ctx context.Context) ([]*system.Menu, error) {
}
func menuParameterFromPO(po menuParameterPO) *system.MenuParameter {
var deletedAt *time.Time
if po.DeletedAt.Valid {
value := po.DeletedAt.Time
deletedAt = &value
}
return &system.MenuParameter{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, MenuID: po.MenuID, Type: po.Type, Key: po.Key, Value: po.Value}
return &system.MenuParameter{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAtPointer(po.DeletedAt), ID: po.ID, MenuID: po.MenuID, Type: po.Type, Key: po.Key, Value: po.Value}
}
func menuButtonFromPO(po menuButtonPO) *system.MenuButton {
var deletedAt *time.Time
if po.DeletedAt.Valid {
value := po.DeletedAt.Time
deletedAt = &value
}
return &system.MenuButton{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, ID: po.ID, Name: po.Name, Description: po.Description, MenuID: po.MenuID}
return &system.MenuButton{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAtPointer(po.DeletedAt), ID: po.ID, Name: po.Name, Description: po.Description, MenuID: po.MenuID}
}
func (r *menuRepo) SetAuthorityMenus(ctx context.Context, id uint, ids []uint) error {
+2 -8
View File
@@ -5,8 +5,6 @@ import (
"kra/internal/biz/system"
"time"
"kra/pkg/database/pagination"
"gorm.io/gorm"
)
@@ -49,12 +47,8 @@ func (r *auditQueryRepo) ListOperations(ctx context.Context, page, size int, q *
db = db.Where("status = ?", q.Status)
}
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
var pos []operationPO
if err := pagination.ApplyRequired(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil {
pos, total, err := queryRows[operationPO](db.Order("id desc"), page, size, true, true)
if err != nil {
return nil, 0, err
}
users := auditUsers(ctx, r.data.DB().WithContext(ctx), operationUserIDs(pos))
+3 -15
View File
@@ -5,8 +5,6 @@ import (
"kra/internal/biz/system"
"time"
"kra/pkg/database/pagination"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -72,17 +70,7 @@ func (r *parameterRepo) ListParameters(ctx context.Context, page, size int, q *s
db = db.Where(clause.Like{Column: clause.Column{Name: "key"}, Value: "%" + q.Key + "%"})
}
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
var pos []parameterPO
if err := pagination.Apply(db, page, size, 100).Find(&pos).Error; err != nil {
return nil, 0, err
}
out := make([]*system.SystemParameter, 0, len(pos))
for _, po := range pos {
out = append(out, parameterFromPO(po))
}
return out, total, nil
return listRows(db, page, size, true, func(po parameterPO) *system.SystemParameter {
return parameterFromPO(po)
})
}
+3 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"kra/internal/biz/system"
"kra/pkg/database/pagination"
"time"
"gorm.io/gorm"
@@ -83,10 +84,7 @@ func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *sys
return nil, 0, err
}
var pos []positionPO
// The compatible position endpoint applies the supplied LIMIT/OFFSET
// directly (it does not normalize missing page values through the shared
// pagination helper).
if err := db.Order("sort").Limit(size).Offset(size * (page - 1)).Find(&pos).Error; err != nil {
if err := pagination.ApplyRequired(db.Order("sort"), page, size, 100).Find(&pos).Error; err != nil {
return nil, 0, err
}
out := make([]*system.Position, 0, len(pos))
@@ -95,6 +93,7 @@ func (r *positionRepo) ListPositions(ctx context.Context, page, size int, q *sys
}
return out, total, nil
}
func (r *positionRepo) PositionUserIDs(ctx context.Context, id uint) ([]uint, error) {
var ids []uint
err := r.data.DB().WithContext(ctx).Model(&userPositionPO{}).Where("sys_position_id = ?", id).Pluck("sys_user_id", &ids).Error
+15
View File
@@ -0,0 +1,15 @@
package system
import (
"time"
"gorm.io/gorm"
)
func deletedAtPointer(value gorm.DeletedAt) *time.Time {
if !value.Valid {
return nil
}
result := value.Time
return &result
}
+5 -1
View File
@@ -37,7 +37,7 @@ func generateToken(secret, issuer string, expires, buffer time.Duration, userID,
}
now := time.Now()
claims := &tokenClaims{UUID: uuid, ID: userID, Username: username, NickName: nickname, AuthorityID: authorityID, BufferTime: int64(buffer / time.Second), UserType: "admin", MustChangePwd: mustChange, PasswordVersion: passwordVersion, RegisteredClaims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{tokenAudience}, Issuer: issuer, IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now.Add(-time.Second)), ExpiresAt: jwt.NewNumericDate(now.Add(expires))}}
token, err := signToken(secret, claims)
token, err := signTokenUnchecked(secret, claims)
return token, claims, err
}
@@ -48,6 +48,10 @@ func signToken(secret string, claims *tokenClaims) (string, error) {
if claims == nil {
return "", errInvalidClaims
}
return signTokenUnchecked(secret, claims)
}
func signTokenUnchecked(secret string, claims *tokenClaims) (string, error) {
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
}
+1 -6
View File
@@ -97,12 +97,7 @@ func (r *userRepo) loadUser(ctx context.Context, po *userPO) (*system.User, erro
}
func toBizAuthority(po authorityPO) system.Authority {
var deletedAt *time.Time
if po.DeletedAt.Valid {
value := po.DeletedAt.Time
deletedAt = &value
}
return system.Authority{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAt, AuthorityID: po.AuthorityID, AuthorityName: po.AuthorityName, ParentID: po.ParentID, DataScope: po.DataScope, DefaultRouter: po.DefaultRouter}
return system.Authority{CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, DeletedAt: deletedAtPointer(po.DeletedAt), AuthorityID: po.AuthorityID, AuthorityName: po.AuthorityName, ParentID: po.ParentID, DataScope: po.DataScope, DefaultRouter: po.DefaultRouter}
}
func parseOriginSetting(raw string) (map[string]any, error) {