feat: add audit logs and system configs
This commit is contained in:
28
backend/app/http/super/v1/audit_logs.go
Normal file
28
backend/app/http/super/v1/audit_logs.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
dto "quyun/v2/app/http/super/v1/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/app/services"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type auditLogs struct{}
|
||||
|
||||
// List audit logs
|
||||
//
|
||||
// @Router /super/v1/audit-logs [get]
|
||||
// @Summary List audit logs
|
||||
// @Description List audit logs across tenants
|
||||
// @Tags Audit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param limit query int false "Page size"
|
||||
// @Success 200 {object} requests.Pager{items=[]dto.SuperAuditLogItem}
|
||||
// @Bind filter query
|
||||
func (c *auditLogs) List(ctx fiber.Ctx, filter *dto.SuperAuditLogListFilter) (*requests.Pager, error) {
|
||||
return services.Super.ListAuditLogs(ctx, filter)
|
||||
}
|
||||
117
backend/app/http/super/v1/dto/super_audit.go
Normal file
117
backend/app/http/super/v1/dto/super_audit.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"quyun/v2/app/requests"
|
||||
)
|
||||
|
||||
// SuperAuditLogListFilter 超管审计日志列表过滤条件。
|
||||
type SuperAuditLogListFilter struct {
|
||||
requests.Pagination
|
||||
// ID 审计日志ID,精确匹配。
|
||||
ID *int64 `query:"id"`
|
||||
// TenantID 租户ID过滤。
|
||||
TenantID *int64 `query:"tenant_id"`
|
||||
// TenantCode 租户编码,模糊匹配。
|
||||
TenantCode *string `query:"tenant_code"`
|
||||
// TenantName 租户名称,模糊匹配。
|
||||
TenantName *string `query:"tenant_name"`
|
||||
// OperatorID 操作者用户ID,精确匹配。
|
||||
OperatorID *int64 `query:"operator_id"`
|
||||
// OperatorName 操作者用户名/昵称,模糊匹配。
|
||||
OperatorName *string `query:"operator_name"`
|
||||
// Action 动作标识过滤,精确匹配。
|
||||
Action *string `query:"action"`
|
||||
// TargetID 目标ID过滤,精确匹配。
|
||||
TargetID *string `query:"target_id"`
|
||||
// Keyword 详情关键词,模糊匹配。
|
||||
Keyword *string `query:"keyword"`
|
||||
// CreatedAtFrom 创建时间起始(RFC3339)。
|
||||
CreatedAtFrom *string `query:"created_at_from"`
|
||||
// CreatedAtTo 创建时间结束(RFC3339)。
|
||||
CreatedAtTo *string `query:"created_at_to"`
|
||||
// Asc 升序字段(id/created_at)。
|
||||
Asc *string `query:"asc"`
|
||||
// Desc 降序字段(id/created_at)。
|
||||
Desc *string `query:"desc"`
|
||||
}
|
||||
|
||||
// SuperAuditLogItem 超管审计日志条目。
|
||||
type SuperAuditLogItem struct {
|
||||
// ID 审计日志ID。
|
||||
ID int64 `json:"id"`
|
||||
// TenantID 租户ID。
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
// TenantCode 租户编码。
|
||||
TenantCode string `json:"tenant_code"`
|
||||
// TenantName 租户名称。
|
||||
TenantName string `json:"tenant_name"`
|
||||
// OperatorID 操作者用户ID。
|
||||
OperatorID int64 `json:"operator_id"`
|
||||
// OperatorName 操作者用户名/昵称。
|
||||
OperatorName string `json:"operator_name"`
|
||||
// Action 动作标识。
|
||||
Action string `json:"action"`
|
||||
// TargetID 目标ID。
|
||||
TargetID string `json:"target_id"`
|
||||
// Detail 操作详情。
|
||||
Detail string `json:"detail"`
|
||||
// CreatedAt 创建时间(RFC3339)。
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// SuperSystemConfigListFilter 超管系统配置列表过滤条件。
|
||||
type SuperSystemConfigListFilter struct {
|
||||
requests.Pagination
|
||||
// ConfigKey 配置Key,精确匹配。
|
||||
ConfigKey *string `query:"config_key"`
|
||||
// Keyword Key或描述关键词,模糊匹配。
|
||||
Keyword *string `query:"keyword"`
|
||||
// CreatedAtFrom 创建时间起始(RFC3339)。
|
||||
CreatedAtFrom *string `query:"created_at_from"`
|
||||
// CreatedAtTo 创建时间结束(RFC3339)。
|
||||
CreatedAtTo *string `query:"created_at_to"`
|
||||
// UpdatedAtFrom 更新时间起始(RFC3339)。
|
||||
UpdatedAtFrom *string `query:"updated_at_from"`
|
||||
// UpdatedAtTo 更新时间结束(RFC3339)。
|
||||
UpdatedAtTo *string `query:"updated_at_to"`
|
||||
// Asc 升序字段(id/config_key/updated_at)。
|
||||
Asc *string `query:"asc"`
|
||||
// Desc 降序字段(id/config_key/updated_at)。
|
||||
Desc *string `query:"desc"`
|
||||
}
|
||||
|
||||
// SuperSystemConfigCreateForm 超管系统配置创建参数。
|
||||
type SuperSystemConfigCreateForm struct {
|
||||
// ConfigKey 配置项Key(唯一)。
|
||||
ConfigKey string `json:"config_key"`
|
||||
// Value 配置值(JSON)。
|
||||
Value json.RawMessage `json:"value"`
|
||||
// Description 配置说明。
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// SuperSystemConfigUpdateForm 超管系统配置更新参数。
|
||||
type SuperSystemConfigUpdateForm struct {
|
||||
// Value 配置值(JSON,可选)。
|
||||
Value *json.RawMessage `json:"value"`
|
||||
// Description 配置说明(可选)。
|
||||
Description *string `json:"description"`
|
||||
}
|
||||
|
||||
// SuperSystemConfigItem 超管系统配置条目。
|
||||
type SuperSystemConfigItem struct {
|
||||
// ID 配置ID。
|
||||
ID int64 `json:"id"`
|
||||
// ConfigKey 配置项Key。
|
||||
ConfigKey string `json:"config_key"`
|
||||
// Value 配置值(JSON)。
|
||||
Value json.RawMessage `json:"value"`
|
||||
// Description 配置说明。
|
||||
Description string `json:"description"`
|
||||
// CreatedAt 创建时间(RFC3339)。
|
||||
CreatedAt string `json:"created_at"`
|
||||
// UpdatedAt 更新时间(RFC3339)。
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
@@ -17,6 +17,13 @@ func Provide(opts ...opt.Option) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*auditLogs, error) {
|
||||
obj := &auditLogs{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*contents, error) {
|
||||
obj := &contents{}
|
||||
|
||||
@@ -75,6 +82,7 @@ func Provide(opts ...opt.Option) error {
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
assets *assets,
|
||||
auditLogs *auditLogs,
|
||||
contents *contents,
|
||||
coupons *coupons,
|
||||
creatorApplications *creatorApplications,
|
||||
@@ -84,12 +92,14 @@ func Provide(opts ...opt.Option) error {
|
||||
orders *orders,
|
||||
payoutAccounts *payoutAccounts,
|
||||
reports *reports,
|
||||
systemConfigs *systemConfigs,
|
||||
tenants *tenants,
|
||||
users *users,
|
||||
withdrawals *withdrawals,
|
||||
) (contracts.HttpRoute, error) {
|
||||
obj := &Routes{
|
||||
assets: assets,
|
||||
auditLogs: auditLogs,
|
||||
contents: contents,
|
||||
coupons: coupons,
|
||||
creatorApplications: creatorApplications,
|
||||
@@ -99,6 +109,7 @@ func Provide(opts ...opt.Option) error {
|
||||
orders: orders,
|
||||
payoutAccounts: payoutAccounts,
|
||||
reports: reports,
|
||||
systemConfigs: systemConfigs,
|
||||
tenants: tenants,
|
||||
users: users,
|
||||
withdrawals: withdrawals,
|
||||
@@ -111,6 +122,13 @@ func Provide(opts ...opt.Option) error {
|
||||
}, atom.GroupRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*systemConfigs, error) {
|
||||
obj := &systemConfigs{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*tenants, error) {
|
||||
obj := &tenants{}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ type Routes struct {
|
||||
middlewares *middlewares.Middlewares
|
||||
// Controller instances
|
||||
assets *assets
|
||||
auditLogs *auditLogs
|
||||
contents *contents
|
||||
coupons *coupons
|
||||
creatorApplications *creatorApplications
|
||||
@@ -34,6 +35,7 @@ type Routes struct {
|
||||
orders *orders
|
||||
payoutAccounts *payoutAccounts
|
||||
reports *reports
|
||||
systemConfigs *systemConfigs
|
||||
tenants *tenants
|
||||
users *users
|
||||
withdrawals *withdrawals
|
||||
@@ -71,6 +73,12 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.assets.Usage,
|
||||
Query[dto.SuperAssetUsageFilter]("filter"),
|
||||
))
|
||||
// Register routes for controller: auditLogs
|
||||
r.log.Debugf("Registering route: Get /super/v1/audit-logs -> auditLogs.List")
|
||||
router.Get("/super/v1/audit-logs"[len(r.Path()):], DataFunc1(
|
||||
r.auditLogs.List,
|
||||
Query[dto.SuperAuditLogListFilter]("filter"),
|
||||
))
|
||||
// Register routes for controller: contents
|
||||
r.log.Debugf("Registering route: Get /super/v1/contents -> contents.List")
|
||||
router.Get("/super/v1/contents"[len(r.Path()):], DataFunc1(
|
||||
@@ -243,6 +251,25 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
r.reports.Export,
|
||||
Body[dto.SuperReportExportForm]("form"),
|
||||
))
|
||||
// Register routes for controller: systemConfigs
|
||||
r.log.Debugf("Registering route: Get /super/v1/system-configs -> systemConfigs.List")
|
||||
router.Get("/super/v1/system-configs"[len(r.Path()):], DataFunc1(
|
||||
r.systemConfigs.List,
|
||||
Query[dto.SuperSystemConfigListFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Patch /super/v1/system-configs/:id<int> -> systemConfigs.Update")
|
||||
router.Patch("/super/v1/system-configs/:id<int>"[len(r.Path()):], DataFunc3(
|
||||
r.systemConfigs.Update,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
PathParam[int64]("id"),
|
||||
Body[dto.SuperSystemConfigUpdateForm]("form"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /super/v1/system-configs -> systemConfigs.Create")
|
||||
router.Post("/super/v1/system-configs"[len(r.Path()):], DataFunc2(
|
||||
r.systemConfigs.Create,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
Body[dto.SuperSystemConfigCreateForm]("form"),
|
||||
))
|
||||
// Register routes for controller: tenants
|
||||
r.log.Debugf("Registering route: Get /super/v1/tenant-join-requests -> tenants.ListJoinRequests")
|
||||
router.Get("/super/v1/tenant-join-requests"[len(r.Path()):], DataFunc1(
|
||||
|
||||
63
backend/app/http/super/v1/system_configs.go
Normal file
63
backend/app/http/super/v1/system_configs.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
dto "quyun/v2/app/http/super/v1/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/app/services"
|
||||
"quyun/v2/database/models"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type systemConfigs struct{}
|
||||
|
||||
// List system configs
|
||||
//
|
||||
// @Router /super/v1/system-configs [get]
|
||||
// @Summary List system configs
|
||||
// @Description List platform system configs
|
||||
// @Tags SystemConfig
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param limit query int false "Page size"
|
||||
// @Success 200 {object} requests.Pager{items=[]dto.SuperSystemConfigItem}
|
||||
// @Bind filter query
|
||||
func (c *systemConfigs) List(ctx fiber.Ctx, filter *dto.SuperSystemConfigListFilter) (*requests.Pager, error) {
|
||||
return services.Super.ListSystemConfigs(ctx, filter)
|
||||
}
|
||||
|
||||
// Create system config
|
||||
//
|
||||
// @Router /super/v1/system-configs [post]
|
||||
// @Summary Create system config
|
||||
// @Description Create platform system config
|
||||
// @Tags SystemConfig
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param form body dto.SuperSystemConfigCreateForm true "Create form"
|
||||
// @Success 200 {object} dto.SuperSystemConfigItem
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *systemConfigs) Create(ctx fiber.Ctx, user *models.User, form *dto.SuperSystemConfigCreateForm) (*dto.SuperSystemConfigItem, error) {
|
||||
return services.Super.CreateSystemConfig(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Update system config
|
||||
//
|
||||
// @Router /super/v1/system-configs/:id<int> [patch]
|
||||
// @Summary Update system config
|
||||
// @Description Update platform system config
|
||||
// @Tags SystemConfig
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int64 true "Config ID"
|
||||
// @Param form body dto.SuperSystemConfigUpdateForm true "Update form"
|
||||
// @Success 200 {object} dto.SuperSystemConfigItem
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id path
|
||||
// @Bind form body
|
||||
func (c *systemConfigs) Update(ctx fiber.Ctx, user *models.User, id int64, form *dto.SuperSystemConfigUpdateForm) (*dto.SuperSystemConfigItem, error) {
|
||||
return services.Super.UpdateSystemConfig(ctx, user.ID, id, form)
|
||||
}
|
||||
@@ -3,18 +3,38 @@ package services
|
||||
import (
|
||||
"context"
|
||||
|
||||
"quyun/v2/database/models"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type audit struct{}
|
||||
|
||||
func (s *audit) Log(ctx context.Context, operatorID int64, action, targetID, detail string) {
|
||||
func (s *audit) Log(ctx context.Context, tenantID, operatorID int64, action, targetID, detail string) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"audit": true,
|
||||
"tenant": tenantID,
|
||||
"operator": operatorID,
|
||||
"action": action,
|
||||
"target": targetID,
|
||||
"detail": detail,
|
||||
}).Info("Audit Log")
|
||||
|
||||
entry := &models.AuditLog{
|
||||
TenantID: tenantID,
|
||||
OperatorID: operatorID,
|
||||
Action: action,
|
||||
TargetID: targetID,
|
||||
Detail: detail,
|
||||
}
|
||||
if err := models.AuditLogQuery.WithContext(ctx).Create(entry); err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"audit": true,
|
||||
"tenant": tenantID,
|
||||
"operator": operatorID,
|
||||
"action": action,
|
||||
"target": targetID,
|
||||
}).WithError(err).Warn("Audit log persist failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,7 +1019,7 @@ func (s *super) ReviewCreatorApplication(ctx context.Context, operatorID, tenant
|
||||
if strings.TrimSpace(form.Reason) != "" {
|
||||
detail += ",原因:" + strings.TrimSpace(form.Reason)
|
||||
}
|
||||
Audit.Log(ctx, operatorID, "review_creator_application", cast.ToString(tenant.ID), detail)
|
||||
Audit.Log(ctx, tenant.ID, operatorID, "review_creator_application", cast.ToString(tenant.ID), detail)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1317,7 +1317,7 @@ func (s *super) RemovePayoutAccount(ctx context.Context, operatorID, id int64) e
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, operatorID, "remove_payout_account", cast.ToString(account.ID), "Removed payout account")
|
||||
Audit.Log(ctx, account.TenantID, operatorID, "remove_payout_account", cast.ToString(account.ID), "Removed payout account")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2376,7 +2376,7 @@ func (s *super) ReviewContent(ctx context.Context, operatorID, contentID int64,
|
||||
_ = Notification.Send(ctx, content.TenantID, content.UserID, string(consts.NotificationTypeAudit), title, detail)
|
||||
}
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, operatorID, "review_content", cast.ToString(contentID), detail)
|
||||
Audit.Log(ctx, content.TenantID, operatorID, "review_content", cast.ToString(contentID), detail)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2465,7 +2465,7 @@ func (s *super) BatchReviewContents(ctx context.Context, operatorID int64, form
|
||||
_ = Notification.Send(ctx, content.TenantID, content.UserID, string(consts.NotificationTypeAudit), title, detail)
|
||||
}
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, operatorID, "review_content", cast.ToString(content.ID), detail)
|
||||
Audit.Log(ctx, content.TenantID, operatorID, "review_content", cast.ToString(content.ID), detail)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3318,6 +3318,431 @@ func (s *super) CreateNotificationTemplate(ctx context.Context, form *super_dto.
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *super) ListAuditLogs(ctx context.Context, filter *super_dto.SuperAuditLogListFilter) (*requests.Pager, error) {
|
||||
if filter == nil {
|
||||
filter = &super_dto.SuperAuditLogListFilter{}
|
||||
}
|
||||
|
||||
tbl, q := models.AuditLogQuery.QueryContext(ctx)
|
||||
|
||||
if filter.ID != nil && *filter.ID > 0 {
|
||||
q = q.Where(tbl.ID.Eq(*filter.ID))
|
||||
}
|
||||
if filter.TenantID != nil && *filter.TenantID > 0 {
|
||||
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
|
||||
}
|
||||
if filter.OperatorID != nil && *filter.OperatorID > 0 {
|
||||
q = q.Where(tbl.OperatorID.Eq(*filter.OperatorID))
|
||||
}
|
||||
if filter.Action != nil && strings.TrimSpace(*filter.Action) != "" {
|
||||
q = q.Where(tbl.Action.Eq(strings.TrimSpace(*filter.Action)))
|
||||
}
|
||||
if filter.TargetID != nil && strings.TrimSpace(*filter.TargetID) != "" {
|
||||
q = q.Where(tbl.TargetID.Eq(strings.TrimSpace(*filter.TargetID)))
|
||||
}
|
||||
if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
|
||||
keyword := "%" + strings.TrimSpace(*filter.Keyword) + "%"
|
||||
q = q.Where(field.Or(tbl.Detail.Like(keyword), tbl.Action.Like(keyword), tbl.TargetID.Like(keyword)))
|
||||
}
|
||||
|
||||
// 跨租户筛选:根据租户编码/名称定位租户ID。
|
||||
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tenantFilter {
|
||||
if len(tenantIDs) == 0 {
|
||||
q = q.Where(tbl.ID.Eq(-1))
|
||||
} else {
|
||||
q = q.Where(tbl.TenantID.In(tenantIDs...))
|
||||
}
|
||||
}
|
||||
|
||||
// 根据操作者昵称/用户名定位用户ID。
|
||||
operatorIDs, operatorFilter, err := s.lookupUserIDs(ctx, filter.OperatorName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if operatorFilter {
|
||||
if len(operatorIDs) == 0 {
|
||||
q = q.Where(tbl.ID.Eq(-1))
|
||||
} else {
|
||||
q = q.Where(tbl.OperatorID.In(operatorIDs...))
|
||||
}
|
||||
}
|
||||
|
||||
if filter.CreatedAtFrom != nil {
|
||||
from, err := s.parseFilterTime(filter.CreatedAtFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if from != nil {
|
||||
q = q.Where(tbl.CreatedAt.Gte(*from))
|
||||
}
|
||||
}
|
||||
if filter.CreatedAtTo != nil {
|
||||
to, err := s.parseFilterTime(filter.CreatedAtTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if to != nil {
|
||||
q = q.Where(tbl.CreatedAt.Lte(*to))
|
||||
}
|
||||
}
|
||||
|
||||
orderApplied := false
|
||||
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
|
||||
switch strings.TrimSpace(*filter.Desc) {
|
||||
case "id":
|
||||
q = q.Order(tbl.ID.Desc())
|
||||
case "created_at":
|
||||
q = q.Order(tbl.CreatedAt.Desc())
|
||||
}
|
||||
orderApplied = true
|
||||
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
|
||||
switch strings.TrimSpace(*filter.Asc) {
|
||||
case "id":
|
||||
q = q.Order(tbl.ID)
|
||||
case "created_at":
|
||||
q = q.Order(tbl.CreatedAt)
|
||||
}
|
||||
orderApplied = true
|
||||
}
|
||||
if !orderApplied {
|
||||
q = q.Order(tbl.CreatedAt.Desc())
|
||||
}
|
||||
|
||||
filter.Pagination.Format()
|
||||
total, err := q.Count()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: []super_dto.SuperAuditLogItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
tenantSet := make(map[int64]struct{})
|
||||
operatorSet := make(map[int64]struct{})
|
||||
for _, log := range list {
|
||||
if log.TenantID > 0 {
|
||||
tenantSet[log.TenantID] = struct{}{}
|
||||
}
|
||||
if log.OperatorID > 0 {
|
||||
operatorSet[log.OperatorID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
tenantMap := make(map[int64]*models.Tenant, len(tenantSet))
|
||||
if len(tenantSet) > 0 {
|
||||
ids := make([]int64, 0, len(tenantSet))
|
||||
for id := range tenantSet {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
|
||||
tenants, err := tenantQuery.Where(tenantTbl.ID.In(ids...)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
for _, tenant := range tenants {
|
||||
tenantMap[tenant.ID] = tenant
|
||||
}
|
||||
}
|
||||
|
||||
operatorMap := make(map[int64]*models.User, len(operatorSet))
|
||||
if len(operatorSet) > 0 {
|
||||
ids := make([]int64, 0, len(operatorSet))
|
||||
for id := range operatorSet {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
|
||||
users, err := userQuery.Where(userTbl.ID.In(ids...)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
for _, user := range users {
|
||||
operatorMap[user.ID] = user
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]super_dto.SuperAuditLogItem, 0, len(list))
|
||||
for _, log := range list {
|
||||
item := super_dto.SuperAuditLogItem{
|
||||
ID: log.ID,
|
||||
TenantID: log.TenantID,
|
||||
OperatorID: log.OperatorID,
|
||||
Action: log.Action,
|
||||
TargetID: log.TargetID,
|
||||
Detail: log.Detail,
|
||||
CreatedAt: s.formatTime(log.CreatedAt),
|
||||
}
|
||||
if tenant := tenantMap[log.TenantID]; tenant != nil {
|
||||
item.TenantCode = tenant.Code
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
if operator := operatorMap[log.OperatorID]; operator != nil {
|
||||
item.OperatorName = operator.Username
|
||||
} else if log.OperatorID > 0 {
|
||||
item.OperatorName = "ID:" + strconv.FormatInt(log.OperatorID, 10)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) ListSystemConfigs(ctx context.Context, filter *super_dto.SuperSystemConfigListFilter) (*requests.Pager, error) {
|
||||
if filter == nil {
|
||||
filter = &super_dto.SuperSystemConfigListFilter{}
|
||||
}
|
||||
|
||||
tbl, q := models.SystemConfigQuery.QueryContext(ctx)
|
||||
|
||||
if filter.ConfigKey != nil && strings.TrimSpace(*filter.ConfigKey) != "" {
|
||||
q = q.Where(tbl.ConfigKey.Eq(strings.TrimSpace(*filter.ConfigKey)))
|
||||
}
|
||||
if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
|
||||
keyword := "%" + strings.TrimSpace(*filter.Keyword) + "%"
|
||||
q = q.Where(field.Or(tbl.ConfigKey.Like(keyword), tbl.Description.Like(keyword)))
|
||||
}
|
||||
|
||||
if filter.CreatedAtFrom != nil {
|
||||
from, err := s.parseFilterTime(filter.CreatedAtFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if from != nil {
|
||||
q = q.Where(tbl.CreatedAt.Gte(*from))
|
||||
}
|
||||
}
|
||||
if filter.CreatedAtTo != nil {
|
||||
to, err := s.parseFilterTime(filter.CreatedAtTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if to != nil {
|
||||
q = q.Where(tbl.CreatedAt.Lte(*to))
|
||||
}
|
||||
}
|
||||
if filter.UpdatedAtFrom != nil {
|
||||
from, err := s.parseFilterTime(filter.UpdatedAtFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if from != nil {
|
||||
q = q.Where(tbl.UpdatedAt.Gte(*from))
|
||||
}
|
||||
}
|
||||
if filter.UpdatedAtTo != nil {
|
||||
to, err := s.parseFilterTime(filter.UpdatedAtTo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if to != nil {
|
||||
q = q.Where(tbl.UpdatedAt.Lte(*to))
|
||||
}
|
||||
}
|
||||
|
||||
orderApplied := false
|
||||
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
|
||||
switch strings.TrimSpace(*filter.Desc) {
|
||||
case "id":
|
||||
q = q.Order(tbl.ID.Desc())
|
||||
case "config_key":
|
||||
q = q.Order(tbl.ConfigKey.Desc())
|
||||
case "updated_at":
|
||||
q = q.Order(tbl.UpdatedAt.Desc())
|
||||
case "created_at":
|
||||
q = q.Order(tbl.CreatedAt.Desc())
|
||||
}
|
||||
orderApplied = true
|
||||
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
|
||||
switch strings.TrimSpace(*filter.Asc) {
|
||||
case "id":
|
||||
q = q.Order(tbl.ID)
|
||||
case "config_key":
|
||||
q = q.Order(tbl.ConfigKey)
|
||||
case "updated_at":
|
||||
q = q.Order(tbl.UpdatedAt)
|
||||
case "created_at":
|
||||
q = q.Order(tbl.CreatedAt)
|
||||
}
|
||||
orderApplied = true
|
||||
}
|
||||
if !orderApplied {
|
||||
q = q.Order(tbl.UpdatedAt.Desc())
|
||||
}
|
||||
|
||||
filter.Pagination.Format()
|
||||
total, err := q.Count()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: []super_dto.SuperSystemConfigItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
items := make([]super_dto.SuperSystemConfigItem, 0, len(list))
|
||||
for _, cfg := range list {
|
||||
items = append(items, super_dto.SuperSystemConfigItem{
|
||||
ID: cfg.ID,
|
||||
ConfigKey: cfg.ConfigKey,
|
||||
Value: json.RawMessage(cfg.Value),
|
||||
Description: cfg.Description,
|
||||
CreatedAt: s.formatTime(cfg.CreatedAt),
|
||||
UpdatedAt: s.formatTime(cfg.UpdatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) CreateSystemConfig(ctx context.Context, operatorID int64, form *super_dto.SuperSystemConfigCreateForm) (*super_dto.SuperSystemConfigItem, error) {
|
||||
if operatorID == 0 {
|
||||
return nil, errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if form == nil {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置参数不能为空")
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(form.ConfigKey)
|
||||
if key == "" {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置Key不能为空")
|
||||
}
|
||||
if len(form.Value) == 0 || !json.Valid(form.Value) {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置值必须是合法JSON")
|
||||
}
|
||||
desc := strings.TrimSpace(form.Description)
|
||||
if desc == "" {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置说明不能为空")
|
||||
}
|
||||
|
||||
// 配置Key唯一,重复提交直接提示。
|
||||
tbl, q := models.SystemConfigQuery.QueryContext(ctx)
|
||||
_, err := q.Where(tbl.ConfigKey.Eq(key)).First()
|
||||
if err == nil {
|
||||
return nil, errorx.ErrRecordDuplicated.WithMsg("配置Key已存在")
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
cfg := &models.SystemConfig{
|
||||
ConfigKey: key,
|
||||
Value: types.JSON(form.Value),
|
||||
Description: desc,
|
||||
}
|
||||
if err := q.Create(cfg); err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, 0, operatorID, "create_system_config", cfg.ConfigKey, "创建系统配置")
|
||||
}
|
||||
|
||||
return &super_dto.SuperSystemConfigItem{
|
||||
ID: cfg.ID,
|
||||
ConfigKey: cfg.ConfigKey,
|
||||
Value: json.RawMessage(cfg.Value),
|
||||
Description: cfg.Description,
|
||||
CreatedAt: s.formatTime(cfg.CreatedAt),
|
||||
UpdatedAt: s.formatTime(cfg.UpdatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) UpdateSystemConfig(ctx context.Context, operatorID, id int64, form *super_dto.SuperSystemConfigUpdateForm) (*super_dto.SuperSystemConfigItem, error) {
|
||||
if operatorID == 0 {
|
||||
return nil, errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if id == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置ID不能为空")
|
||||
}
|
||||
if form == nil {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置参数不能为空")
|
||||
}
|
||||
|
||||
tbl, q := models.SystemConfigQuery.QueryContext(ctx)
|
||||
cfg, err := q.Where(tbl.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound.WithMsg("配置不存在")
|
||||
}
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{}, 3)
|
||||
if form.Value != nil {
|
||||
if len(*form.Value) == 0 || !json.Valid(*form.Value) {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置值必须是合法JSON")
|
||||
}
|
||||
updates["value"] = types.JSON(*form.Value)
|
||||
}
|
||||
if form.Description != nil {
|
||||
desc := strings.TrimSpace(*form.Description)
|
||||
if desc == "" {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("配置说明不能为空")
|
||||
}
|
||||
updates["description"] = desc
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("请至少更新一项配置")
|
||||
}
|
||||
updates["updated_at"] = time.Now()
|
||||
|
||||
if _, err := q.Where(tbl.ID.Eq(id)).Updates(updates); err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
cfg, err = q.Where(tbl.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
details := make([]string, 0, 2)
|
||||
if form.Value != nil {
|
||||
details = append(details, "更新配置值")
|
||||
}
|
||||
if form.Description != nil {
|
||||
details = append(details, "更新配置说明")
|
||||
}
|
||||
detail := strings.Join(details, ",")
|
||||
Audit.Log(ctx, 0, operatorID, "update_system_config", cfg.ConfigKey, detail)
|
||||
}
|
||||
|
||||
return &super_dto.SuperSystemConfigItem{
|
||||
ID: cfg.ID,
|
||||
ConfigKey: cfg.ConfigKey,
|
||||
Value: json.RawMessage(cfg.Value),
|
||||
Description: cfg.Description,
|
||||
CreatedAt: s.formatTime(cfg.CreatedAt),
|
||||
UpdatedAt: s.formatTime(cfg.UpdatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) ListOrders(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
|
||||
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||
|
||||
@@ -5523,7 +5948,7 @@ func (s *super) UpdateCouponStatus(ctx context.Context, operatorID, couponID int
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, operatorID, "freeze_coupon", cast.ToString(coupon.ID), "Freeze coupon")
|
||||
Audit.Log(ctx, coupon.TenantID, operatorID, "freeze_coupon", cast.ToString(coupon.ID), "Freeze coupon")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6077,7 +6502,7 @@ func (s *super) ApproveWithdrawal(ctx context.Context, operatorID, id int64) err
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
if err == nil && Audit != nil {
|
||||
Audit.Log(ctx, operatorID, "approve_withdrawal", cast.ToString(id), "Approved withdrawal")
|
||||
Audit.Log(ctx, o.TenantID, operatorID, "approve_withdrawal", cast.ToString(id), "Approved withdrawal")
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -6404,11 +6829,13 @@ func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reas
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
tenantID := int64(0)
|
||||
err := models.Q.Transaction(func(tx *models.Query) error {
|
||||
o, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
return errorx.ErrRecordNotFound
|
||||
}
|
||||
tenantID = o.TenantID
|
||||
if o.Status != consts.OrderStatusCreated {
|
||||
return errorx.ErrStatusConflict.WithMsg("订单状态不正确")
|
||||
}
|
||||
@@ -6448,7 +6875,7 @@ func (s *super) RejectWithdrawal(ctx context.Context, operatorID, id int64, reas
|
||||
})
|
||||
|
||||
if err == nil && Audit != nil {
|
||||
Audit.Log(ctx, operatorID, "reject_withdrawal", cast.ToString(id), "Rejected: "+reason)
|
||||
Audit.Log(ctx, tenantID, operatorID, "reject_withdrawal", cast.ToString(id), "Rejected: "+reason)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user