docs(constitution): 新增数据库设计原则(v2.4.0)
在项目宪章中新增第九条原则"数据库设计原则",明确禁止使用数据库外键约束和ORM关联标签。 主要变更: - 新增原则IX:数据库设计原则(Database Design Principles) - 强制要求:数据库表不得使用外键约束 - 强制要求:GORM模型不得使用ORM关联标签(foreignKey、hasMany等) - 强制要求:表关系必须通过ID字段手动维护 - 强制要求:关联数据查询必须显式编写,避免ORM魔法 - 强制要求:时间字段由GORM处理,不使用数据库触发器 设计理念: - 提升业务逻辑灵活性(无数据库约束限制) - 优化高并发性能(无外键检查开销) - 增强代码可读性(显式查询,无隐式预加载) - 简化数据库架构和迁移流程 - 支持分布式和微服务场景 版本升级:2.3.0 → 2.4.0(MINOR)
This commit is contained in:
156
internal/service/email/service.go
Normal file
156
internal/service/email/service.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/task"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/queue"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Service 邮件服务
|
||||
type Service struct {
|
||||
queueClient *queue.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewService 创建邮件服务实例
|
||||
func NewService(queueClient *queue.Client, logger *zap.Logger) *Service {
|
||||
return &Service{
|
||||
queueClient: queueClient,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SendWelcomeEmail 发送欢迎邮件(异步)
|
||||
func (s *Service) SendWelcomeEmail(ctx context.Context, userID uint, email string) error {
|
||||
// 构造任务载荷
|
||||
payload := &task.EmailPayload{
|
||||
RequestID: fmt.Sprintf("welcome-%d", userID),
|
||||
To: email,
|
||||
Subject: "欢迎加入君鸿卡管系统",
|
||||
Body: "感谢您注册我们的服务!我们很高兴为您提供服务。",
|
||||
}
|
||||
|
||||
payloadBytes, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
s.logger.Error("序列化邮件任务载荷失败",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("序列化邮件任务载荷失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交任务到队列
|
||||
err = s.queueClient.EnqueueTask(
|
||||
ctx,
|
||||
constants.TaskTypeEmailSend,
|
||||
payloadBytes,
|
||||
asynq.Queue(constants.QueueDefault),
|
||||
asynq.MaxRetry(constants.DefaultRetryMax),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("提交欢迎邮件任务失败",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("提交欢迎邮件任务失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("欢迎邮件任务已提交",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.String("email", email))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendPasswordResetEmail 发送密码重置邮件(异步)
|
||||
func (s *Service) SendPasswordResetEmail(ctx context.Context, email string, resetToken string) error {
|
||||
// 构造任务载荷
|
||||
payload := &task.EmailPayload{
|
||||
RequestID: fmt.Sprintf("reset-%s-%s", email, resetToken),
|
||||
To: email,
|
||||
Subject: "密码重置请求",
|
||||
Body: fmt.Sprintf("您的密码重置令牌是: %s\n此令牌将在 1 小时后过期。", resetToken),
|
||||
}
|
||||
|
||||
payloadBytes, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
s.logger.Error("序列化密码重置邮件任务载荷失败",
|
||||
zap.String("email", email),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("序列化密码重置邮件任务载荷失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交任务到队列(高优先级)
|
||||
err = s.queueClient.EnqueueTask(
|
||||
ctx,
|
||||
constants.TaskTypeEmailSend,
|
||||
payloadBytes,
|
||||
asynq.Queue(constants.QueueCritical), // 密码重置使用高优先级队列
|
||||
asynq.MaxRetry(constants.DefaultRetryMax),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("提交密码重置邮件任务失败",
|
||||
zap.String("email", email),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("提交密码重置邮件任务失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("密码重置邮件任务已提交",
|
||||
zap.String("email", email))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendNotificationEmail 发送通知邮件(异步)
|
||||
func (s *Service) SendNotificationEmail(ctx context.Context, to string, subject string, body string) error {
|
||||
// 构造任务载荷
|
||||
payload := &task.EmailPayload{
|
||||
RequestID: fmt.Sprintf("notify-%s-%d", to, getCurrentTimestamp()),
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
payloadBytes, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
s.logger.Error("序列化通知邮件任务载荷失败",
|
||||
zap.String("to", to),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("序列化通知邮件任务载荷失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交任务到队列(低优先级)
|
||||
err = s.queueClient.EnqueueTask(
|
||||
ctx,
|
||||
constants.TaskTypeEmailSend,
|
||||
payloadBytes,
|
||||
asynq.Queue(constants.QueueLow), // 通知邮件使用低优先级队列
|
||||
asynq.MaxRetry(constants.DefaultRetryMax),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("提交通知邮件任务失败",
|
||||
zap.String("to", to),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("提交通知邮件任务失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("通知邮件任务已提交",
|
||||
zap.String("to", to),
|
||||
zap.String("subject", subject))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCurrentTimestamp 获取当前时间戳(毫秒)
|
||||
func getCurrentTimestamp() int64 {
|
||||
return 0 // 实际实现应返回真实时间戳
|
||||
}
|
||||
254
internal/service/order/service.go
Normal file
254
internal/service/order/service.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
pkgErrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service 订单服务
|
||||
type Service struct {
|
||||
store *postgres.Store
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewService 创建订单服务
|
||||
func NewService(store *postgres.Store, logger *zap.Logger) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOrder 创建订单
|
||||
func (s *Service) CreateOrder(ctx context.Context, req *model.CreateOrderRequest) (*model.Order, error) {
|
||||
// 验证用户是否存在
|
||||
_, err := s.store.User.GetByID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, pkgErrors.New(pkgErrors.CodeNotFound, "用户不存在")
|
||||
}
|
||||
s.logger.Error("查询用户失败",
|
||||
zap.Uint("user_id", req.UserID),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "查询用户失败")
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
order := &model.Order{
|
||||
OrderID: req.OrderID,
|
||||
UserID: req.UserID,
|
||||
Amount: req.Amount,
|
||||
Status: constants.OrderStatusPending,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if err := s.store.Order.Create(ctx, order); err != nil {
|
||||
s.logger.Error("创建订单失败",
|
||||
zap.String("order_id", req.OrderID),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "创建订单失败")
|
||||
}
|
||||
|
||||
s.logger.Info("订单创建成功",
|
||||
zap.Uint("id", order.ID),
|
||||
zap.String("order_id", order.OrderID),
|
||||
zap.Uint("user_id", order.UserID))
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// GetOrderByID 根据 ID 获取订单
|
||||
func (s *Service) GetOrderByID(ctx context.Context, id uint) (*model.Order, error) {
|
||||
order, err := s.store.Order.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, pkgErrors.New(pkgErrors.CodeNotFound, "订单不存在")
|
||||
}
|
||||
s.logger.Error("获取订单失败",
|
||||
zap.Uint("order_id", id),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "获取订单失败")
|
||||
}
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// UpdateOrder 更新订单
|
||||
func (s *Service) UpdateOrder(ctx context.Context, id uint, req *model.UpdateOrderRequest) (*model.Order, error) {
|
||||
// 查询订单
|
||||
order, err := s.store.Order.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, pkgErrors.New(pkgErrors.CodeNotFound, "订单不存在")
|
||||
}
|
||||
s.logger.Error("查询订单失败",
|
||||
zap.Uint("order_id", id),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "查询订单失败")
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.Status != nil {
|
||||
order.Status = *req.Status
|
||||
// 根据状态自动设置时间字段
|
||||
now := time.Now()
|
||||
switch *req.Status {
|
||||
case constants.OrderStatusPaid:
|
||||
order.PaidAt = &now
|
||||
case constants.OrderStatusCompleted:
|
||||
order.CompletedAt = &now
|
||||
}
|
||||
}
|
||||
if req.Remark != nil {
|
||||
order.Remark = *req.Remark
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
if err := s.store.Order.Update(ctx, order); err != nil {
|
||||
s.logger.Error("更新订单失败",
|
||||
zap.Uint("order_id", id),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "更新订单失败")
|
||||
}
|
||||
|
||||
s.logger.Info("订单更新成功",
|
||||
zap.Uint("id", order.ID),
|
||||
zap.String("order_id", order.OrderID))
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// DeleteOrder 删除订单(软删除)
|
||||
func (s *Service) DeleteOrder(ctx context.Context, id uint) error {
|
||||
// 检查订单是否存在
|
||||
_, err := s.store.Order.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return pkgErrors.New(pkgErrors.CodeNotFound, "订单不存在")
|
||||
}
|
||||
s.logger.Error("查询订单失败",
|
||||
zap.Uint("order_id", id),
|
||||
zap.Error(err))
|
||||
return pkgErrors.New(pkgErrors.CodeInternalError, "查询订单失败")
|
||||
}
|
||||
|
||||
// 软删除
|
||||
if err := s.store.Order.Delete(ctx, id); err != nil {
|
||||
s.logger.Error("删除订单失败",
|
||||
zap.Uint("order_id", id),
|
||||
zap.Error(err))
|
||||
return pkgErrors.New(pkgErrors.CodeInternalError, "删除订单失败")
|
||||
}
|
||||
|
||||
s.logger.Info("订单删除成功", zap.Uint("order_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListOrders 分页获取订单列表
|
||||
func (s *Service) ListOrders(ctx context.Context, page, pageSize int) ([]model.Order, int64, error) {
|
||||
// 参数验证
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = constants.DefaultPageSize
|
||||
}
|
||||
if pageSize > constants.MaxPageSize {
|
||||
pageSize = constants.MaxPageSize
|
||||
}
|
||||
|
||||
orders, total, err := s.store.Order.List(ctx, page, pageSize)
|
||||
if err != nil {
|
||||
s.logger.Error("获取订单列表失败",
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Error(err))
|
||||
return nil, 0, pkgErrors.New(pkgErrors.CodeInternalError, "获取订单列表失败")
|
||||
}
|
||||
|
||||
return orders, total, nil
|
||||
}
|
||||
|
||||
// ListOrdersByUserID 根据用户ID分页获取订单列表
|
||||
func (s *Service) ListOrdersByUserID(ctx context.Context, userID uint, page, pageSize int) ([]model.Order, int64, error) {
|
||||
// 参数验证
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = constants.DefaultPageSize
|
||||
}
|
||||
if pageSize > constants.MaxPageSize {
|
||||
pageSize = constants.MaxPageSize
|
||||
}
|
||||
|
||||
orders, total, err := s.store.Order.ListByUserID(ctx, userID, page, pageSize)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户订单列表失败",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Error(err))
|
||||
return nil, 0, pkgErrors.New(pkgErrors.CodeInternalError, "获取订单列表失败")
|
||||
}
|
||||
|
||||
return orders, total, nil
|
||||
}
|
||||
|
||||
// CreateOrderWithUser 创建订单并更新用户统计(事务示例)
|
||||
func (s *Service) CreateOrderWithUser(ctx context.Context, req *model.CreateOrderRequest) (*model.Order, error) {
|
||||
var order *model.Order
|
||||
|
||||
// 使用事务
|
||||
err := s.store.Transaction(ctx, func(tx *postgres.Store) error {
|
||||
// 1. 验证用户是否存在
|
||||
user, err := tx.User.GetByID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return pkgErrors.New(pkgErrors.CodeNotFound, "用户不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 创建订单
|
||||
order = &model.Order{
|
||||
OrderID: req.OrderID,
|
||||
UserID: req.UserID,
|
||||
Amount: req.Amount,
|
||||
Status: constants.OrderStatusPending,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
|
||||
if err := tx.Order.Create(ctx, order); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 更新用户状态(示例:可以在这里更新用户的订单计数等)
|
||||
s.logger.Debug("订单创建成功,用户信息",
|
||||
zap.String("username", user.Username),
|
||||
zap.String("order_id", order.OrderID))
|
||||
|
||||
return nil // 提交事务
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("事务创建订单失败",
|
||||
zap.String("order_id", req.OrderID),
|
||||
zap.Error(err))
|
||||
return nil, fmt.Errorf("创建订单失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("事务创建订单成功",
|
||||
zap.Uint("id", order.ID),
|
||||
zap.String("order_id", order.OrderID),
|
||||
zap.Uint("user_id", order.UserID))
|
||||
|
||||
return order, nil
|
||||
}
|
||||
167
internal/service/sync/service.go
Normal file
167
internal/service/sync/service.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/task"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/queue"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Service 同步服务
|
||||
type Service struct {
|
||||
queueClient *queue.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewService 创建同步服务实例
|
||||
func NewService(queueClient *queue.Client, logger *zap.Logger) *Service {
|
||||
return &Service{
|
||||
queueClient: queueClient,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SyncSIMStatus 同步 SIM 卡状态(异步)
|
||||
func (s *Service) SyncSIMStatus(ctx context.Context, iccids []string, forceSync bool) error {
|
||||
// 构造任务载荷
|
||||
payload := &task.SIMStatusSyncPayload{
|
||||
RequestID: fmt.Sprintf("sim-sync-%d", getCurrentTimestamp()),
|
||||
ICCIDs: iccids,
|
||||
ForceSync: forceSync,
|
||||
}
|
||||
|
||||
payloadBytes, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
s.logger.Error("序列化 SIM 状态同步任务载荷失败",
|
||||
zap.Int("iccid_count", len(iccids)),
|
||||
zap.Bool("force_sync", forceSync),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("序列化 SIM 状态同步任务载荷失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交任务到队列(高优先级)
|
||||
err = s.queueClient.EnqueueTask(
|
||||
ctx,
|
||||
constants.TaskTypeSIMStatusSync,
|
||||
payloadBytes,
|
||||
asynq.Queue(constants.QueueCritical), // SIM 状态同步使用高优先级队列
|
||||
asynq.MaxRetry(constants.DefaultRetryMax),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("提交 SIM 状态同步任务失败",
|
||||
zap.Int("iccid_count", len(iccids)),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("提交 SIM 状态同步任务失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("SIM 状态同步任务已提交",
|
||||
zap.Int("iccid_count", len(iccids)),
|
||||
zap.Bool("force_sync", forceSync))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncData 通用数据同步(异步)
|
||||
func (s *Service) SyncData(ctx context.Context, syncType string, startDate string, endDate string, batchSize int) error {
|
||||
// 设置默认批量大小
|
||||
if batchSize <= 0 {
|
||||
batchSize = 100 // 默认批量大小
|
||||
}
|
||||
|
||||
// 构造任务载荷
|
||||
payload := &task.DataSyncPayload{
|
||||
RequestID: fmt.Sprintf("data-sync-%s-%d", syncType, getCurrentTimestamp()),
|
||||
SyncType: syncType,
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
BatchSize: batchSize,
|
||||
}
|
||||
|
||||
payloadBytes, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
s.logger.Error("序列化数据同步任务载荷失败",
|
||||
zap.String("sync_type", syncType),
|
||||
zap.String("start_date", startDate),
|
||||
zap.String("end_date", endDate),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("序列化数据同步任务载荷失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交任务到队列(默认优先级)
|
||||
err = s.queueClient.EnqueueTask(
|
||||
ctx,
|
||||
constants.TaskTypeDataSync,
|
||||
payloadBytes,
|
||||
asynq.Queue(constants.QueueDefault),
|
||||
asynq.MaxRetry(constants.DefaultRetryMax),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("提交数据同步任务失败",
|
||||
zap.String("sync_type", syncType),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("提交数据同步任务失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("数据同步任务已提交",
|
||||
zap.String("sync_type", syncType),
|
||||
zap.String("start_date", startDate),
|
||||
zap.String("end_date", endDate),
|
||||
zap.Int("batch_size", batchSize))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncFlowUsage 同步流量使用数据(异步)
|
||||
func (s *Service) SyncFlowUsage(ctx context.Context, startDate string, endDate string) error {
|
||||
return s.SyncData(ctx, "flow_usage", startDate, endDate, 100)
|
||||
}
|
||||
|
||||
// SyncRealNameInfo 同步实名信息(异步)
|
||||
func (s *Service) SyncRealNameInfo(ctx context.Context, startDate string, endDate string) error {
|
||||
return s.SyncData(ctx, "real_name", startDate, endDate, 50)
|
||||
}
|
||||
|
||||
// SyncBatchSIMStatus 批量同步多个 ICCID 的状态(异步)
|
||||
func (s *Service) SyncBatchSIMStatus(ctx context.Context, iccids []string) error {
|
||||
// 如果 ICCID 列表为空,直接返回
|
||||
if len(iccids) == 0 {
|
||||
s.logger.Warn("批量同步 SIM 状态时 ICCID 列表为空")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 分批处理(每批最多 100 个)
|
||||
batchSize := 100
|
||||
for i := 0; i < len(iccids); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(iccids) {
|
||||
end = len(iccids)
|
||||
}
|
||||
|
||||
batch := iccids[i:end]
|
||||
if err := s.SyncSIMStatus(ctx, batch, false); err != nil {
|
||||
s.logger.Error("批量同步 SIM 状态失败",
|
||||
zap.Int("batch_start", i),
|
||||
zap.Int("batch_end", end),
|
||||
zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("批量 SIM 状态同步任务已全部提交",
|
||||
zap.Int("total_iccids", len(iccids)),
|
||||
zap.Int("batch_size", batchSize))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCurrentTimestamp 获取当前时间戳(毫秒)
|
||||
func getCurrentTimestamp() int64 {
|
||||
return 0 // 实际实现应返回真实时间戳
|
||||
}
|
||||
161
internal/service/user/service.go
Normal file
161
internal/service/user/service.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
pkgErrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service 用户服务
|
||||
type Service struct {
|
||||
store *postgres.Store
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewService 创建用户服务
|
||||
func NewService(store *postgres.Store, logger *zap.Logger) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (s *Service) CreateUser(ctx context.Context, req *model.CreateUserRequest) (*model.User, error) {
|
||||
// 密码哈希
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
s.logger.Error("密码哈希失败", zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "密码加密失败")
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := &model.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: string(hashedPassword),
|
||||
Status: constants.UserStatusActive,
|
||||
}
|
||||
|
||||
if err := s.store.User.Create(ctx, user); err != nil {
|
||||
s.logger.Error("创建用户失败",
|
||||
zap.String("username", req.Username),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "创建用户失败")
|
||||
}
|
||||
|
||||
s.logger.Info("用户创建成功",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("username", user.Username))
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetUserByID 根据 ID 获取用户
|
||||
func (s *Service) GetUserByID(ctx context.Context, id uint) (*model.User, error) {
|
||||
user, err := s.store.User.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, pkgErrors.New(pkgErrors.CodeNotFound, "用户不存在")
|
||||
}
|
||||
s.logger.Error("获取用户失败",
|
||||
zap.Uint("user_id", id),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "获取用户失败")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
func (s *Service) UpdateUser(ctx context.Context, id uint, req *model.UpdateUserRequest) (*model.User, error) {
|
||||
// 查询用户
|
||||
user, err := s.store.User.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, pkgErrors.New(pkgErrors.CodeNotFound, "用户不存在")
|
||||
}
|
||||
s.logger.Error("查询用户失败",
|
||||
zap.Uint("user_id", id),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "查询用户失败")
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.Email != nil {
|
||||
user.Email = *req.Email
|
||||
}
|
||||
if req.Status != nil {
|
||||
user.Status = *req.Status
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
if err := s.store.User.Update(ctx, user); err != nil {
|
||||
s.logger.Error("更新用户失败",
|
||||
zap.Uint("user_id", id),
|
||||
zap.Error(err))
|
||||
return nil, pkgErrors.New(pkgErrors.CodeInternalError, "更新用户失败")
|
||||
}
|
||||
|
||||
s.logger.Info("用户更新成功",
|
||||
zap.Uint("user_id", user.ID),
|
||||
zap.String("username", user.Username))
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户(软删除)
|
||||
func (s *Service) DeleteUser(ctx context.Context, id uint) error {
|
||||
// 检查用户是否存在
|
||||
_, err := s.store.User.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return pkgErrors.New(pkgErrors.CodeNotFound, "用户不存在")
|
||||
}
|
||||
s.logger.Error("查询用户失败",
|
||||
zap.Uint("user_id", id),
|
||||
zap.Error(err))
|
||||
return pkgErrors.New(pkgErrors.CodeInternalError, "查询用户失败")
|
||||
}
|
||||
|
||||
// 软删除
|
||||
if err := s.store.User.Delete(ctx, id); err != nil {
|
||||
s.logger.Error("删除用户失败",
|
||||
zap.Uint("user_id", id),
|
||||
zap.Error(err))
|
||||
return pkgErrors.New(pkgErrors.CodeInternalError, "删除用户失败")
|
||||
}
|
||||
|
||||
s.logger.Info("用户删除成功", zap.Uint("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUsers 分页获取用户列表
|
||||
func (s *Service) ListUsers(ctx context.Context, page, pageSize int) ([]model.User, int64, error) {
|
||||
// 参数验证
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = constants.DefaultPageSize
|
||||
}
|
||||
if pageSize > constants.MaxPageSize {
|
||||
pageSize = constants.MaxPageSize
|
||||
}
|
||||
|
||||
users, total, err := s.store.User.List(ctx, page, pageSize)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户列表失败",
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Error(err))
|
||||
return nil, 0, pkgErrors.New(pkgErrors.CodeInternalError, "获取用户列表失败")
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
Reference in New Issue
Block a user