refactor(account): 统一账号管理API、完善权限检查和操作审计
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s
- 合并 customer_account 和 shop_account 路由到统一的 account 接口 - 新增统一认证接口 (auth handler) - 实现越权防护中间件和权限检查工具函数 - 新增操作审计日志模型和服务 - 更新数据库迁移 (版本 39: account_operation_log 表) - 补充集成测试覆盖权限检查和审计日志场景
This commit is contained in:
42
internal/service/account_audit/service.go
Normal file
42
internal/service/account_audit/service.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package account_audit 提供账号操作审计日志服务
|
||||
// 负责记录所有账号管理操作,用于审计追踪和合规要求
|
||||
package account_audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AccountOperationLogStore 账号操作日志存储接口
|
||||
type AccountOperationLogStore interface {
|
||||
Create(ctx context.Context, log *model.AccountOperationLog) error
|
||||
}
|
||||
|
||||
// Service 账号审计服务
|
||||
type Service struct {
|
||||
store AccountOperationLogStore
|
||||
}
|
||||
|
||||
// NewService 创建账号审计服务实例
|
||||
func NewService(store AccountOperationLogStore) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
// LogOperation 记录账号操作日志(异步写入,不阻塞主流程)
|
||||
func (s *Service) LogOperation(ctx context.Context, log *model.AccountOperationLog) {
|
||||
// 异步写入审计日志,不阻塞业务操作
|
||||
go func() {
|
||||
if err := s.store.Create(context.Background(), log); err != nil {
|
||||
// 写入失败只记录错误日志,不影响业务
|
||||
logger.GetAppLogger().Error("写入账号操作日志失败",
|
||||
zap.Uint("operator_id", log.OperatorID),
|
||||
zap.String("operation_type", log.OperationType),
|
||||
zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
145
internal/service/account_audit/service_test.go
Normal file
145
internal/service/account_audit/service_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package account_audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockAccountOperationLogStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockAccountOperationLogStore) Create(ctx context.Context, log *model.AccountOperationLog) error {
|
||||
args := m.Called(ctx, log)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestLogOperation_Success(t *testing.T) {
|
||||
mockStore := new(MockAccountOperationLogStore)
|
||||
service := NewService(mockStore)
|
||||
|
||||
log := &model.AccountOperationLog{
|
||||
OperatorID: 1,
|
||||
OperatorType: 2,
|
||||
OperatorName: "admin",
|
||||
OperationType: "create",
|
||||
OperationDesc: "创建账号: testuser",
|
||||
}
|
||||
|
||||
mockStore.On("Create", mock.Anything, log).Return(nil)
|
||||
|
||||
ctx := context.Background()
|
||||
service.LogOperation(ctx, log)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
mockStore.AssertCalled(t, "Create", mock.Anything, log)
|
||||
}
|
||||
|
||||
func TestLogOperation_Failure(t *testing.T) {
|
||||
mockStore := new(MockAccountOperationLogStore)
|
||||
service := NewService(mockStore)
|
||||
|
||||
log := &model.AccountOperationLog{
|
||||
OperatorID: 1,
|
||||
OperatorType: 2,
|
||||
OperatorName: "admin",
|
||||
OperationType: "create",
|
||||
OperationDesc: "创建账号: testuser",
|
||||
}
|
||||
|
||||
mockStore.On("Create", mock.Anything, log).Return(errors.New("database error"))
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
service.LogOperation(ctx, log)
|
||||
})
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
mockStore.AssertCalled(t, "Create", mock.Anything, log)
|
||||
}
|
||||
|
||||
func TestLogOperation_NonBlocking(t *testing.T) {
|
||||
mockStore := new(MockAccountOperationLogStore)
|
||||
service := NewService(mockStore)
|
||||
|
||||
log := &model.AccountOperationLog{
|
||||
OperatorID: 1,
|
||||
OperatorType: 2,
|
||||
OperatorName: "admin",
|
||||
OperationType: "create",
|
||||
OperationDesc: "创建账号: testuser",
|
||||
}
|
||||
|
||||
mockStore.On("Create", mock.Anything, log).Run(func(args mock.Arguments) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}).Return(nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
start := time.Now()
|
||||
service.LogOperation(ctx, log)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
assert.Less(t, elapsed, 50*time.Millisecond, "LogOperation should return immediately")
|
||||
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
mockStore.AssertCalled(t, "Create", mock.Anything, log)
|
||||
}
|
||||
|
||||
func TestNewService(t *testing.T) {
|
||||
mockStore := new(MockAccountOperationLogStore)
|
||||
service := NewService(mockStore)
|
||||
|
||||
assert.NotNil(t, service)
|
||||
assert.Equal(t, mockStore, service.store)
|
||||
}
|
||||
|
||||
func TestLogOperation_WithAllFields(t *testing.T) {
|
||||
mockStore := new(MockAccountOperationLogStore)
|
||||
service := NewService(mockStore)
|
||||
|
||||
targetAccountID := uint(10)
|
||||
targetUsername := "targetuser"
|
||||
targetUserType := 3
|
||||
requestID := "req-12345"
|
||||
ipAddress := "127.0.0.1"
|
||||
userAgent := "Mozilla/5.0"
|
||||
|
||||
log := &model.AccountOperationLog{
|
||||
OperatorID: 1,
|
||||
OperatorType: 2,
|
||||
OperatorName: "admin",
|
||||
TargetAccountID: &targetAccountID,
|
||||
TargetUsername: &targetUsername,
|
||||
TargetUserType: &targetUserType,
|
||||
OperationType: "update",
|
||||
OperationDesc: "更新账号: targetuser",
|
||||
BeforeData: model.JSONB{
|
||||
"username": "oldname",
|
||||
},
|
||||
AfterData: model.JSONB{
|
||||
"username": "newname",
|
||||
},
|
||||
RequestID: &requestID,
|
||||
IPAddress: &ipAddress,
|
||||
UserAgent: &userAgent,
|
||||
}
|
||||
|
||||
mockStore.On("Create", mock.Anything, log).Return(nil)
|
||||
|
||||
ctx := context.Background()
|
||||
service.LogOperation(ctx, log)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
mockStore.AssertCalled(t, "Create", mock.Anything, log)
|
||||
}
|
||||
Reference in New Issue
Block a user