feat: update app errors
This commit is contained in:
@@ -28,36 +28,59 @@ func (e *AppError) Error() string {
|
|||||||
// Unwrap 允许通过 errors.Unwrap 遍历到原始错误
|
// Unwrap 允许通过 errors.Unwrap 遍历到原始错误
|
||||||
func (e *AppError) Unwrap() error { return e.originalErr }
|
func (e *AppError) Unwrap() error { return e.originalErr }
|
||||||
|
|
||||||
|
// copy 返回 AppError 的副本,用于链式调用时的并发安全
|
||||||
|
func (e *AppError) copy() *AppError {
|
||||||
|
newErr := *e
|
||||||
|
return &newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCause 携带原始错误并记录调用栈
|
||||||
|
func (e *AppError) WithCause(err error) *AppError {
|
||||||
|
newErr := e.copy()
|
||||||
|
newErr.originalErr = err
|
||||||
|
|
||||||
|
// 记录调用者位置
|
||||||
|
if _, file, line, ok := runtime.Caller(1); ok {
|
||||||
|
newErr.file = fmt.Sprintf("%s:%d", file, line)
|
||||||
|
}
|
||||||
|
return newErr
|
||||||
|
}
|
||||||
|
|
||||||
// WithData 添加数据
|
// WithData 添加数据
|
||||||
func (e *AppError) WithData(data any) *AppError {
|
func (e *AppError) WithData(data any) *AppError {
|
||||||
e.Data = data
|
newErr := e.copy()
|
||||||
return e
|
newErr.Data = data
|
||||||
|
return newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithMsg 设置消息
|
// WithMsg 设置消息
|
||||||
func (e *AppError) WithMsg(msg string) *AppError {
|
func (e *AppError) WithMsg(msg string) *AppError {
|
||||||
e.Message = msg
|
newErr := e.copy()
|
||||||
return e
|
newErr.Message = msg
|
||||||
|
return newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AppError) WithMsgf(format string, args ...any) *AppError {
|
func (e *AppError) WithMsgf(format string, args ...any) *AppError {
|
||||||
msg := fmt.Sprintf(format, args...)
|
newErr := e.copy()
|
||||||
return e.WithMsg(msg)
|
newErr.Message = fmt.Sprintf(format, args...)
|
||||||
|
return newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSQL 记录SQL信息
|
// WithSQL 记录SQL信息
|
||||||
func (e *AppError) WithSQL(sql string) *AppError {
|
func (e *AppError) WithSQL(sql string) *AppError {
|
||||||
e.sql = sql
|
newErr := e.copy()
|
||||||
return e
|
newErr.sql = sql
|
||||||
|
return newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithParams 记录参数信息,并自动获取调用位置
|
// WithParams 记录参数信息,并自动获取调用位置
|
||||||
func (e *AppError) WithParams(params ...any) *AppError {
|
func (e *AppError) WithParams(params ...any) *AppError {
|
||||||
e.params = params
|
newErr := e.copy()
|
||||||
|
newErr.params = params
|
||||||
if _, file, line, ok := runtime.Caller(1); ok {
|
if _, file, line, ok := runtime.Caller(1); ok {
|
||||||
e.file = fmt.Sprintf("%s:%d", file, line)
|
newErr.file = fmt.Sprintf("%s:%d", file, line)
|
||||||
}
|
}
|
||||||
return e
|
return newErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewError 创建应用错误
|
// NewError 创建应用错误
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := models.MediaAssetQuery.WithContext(ctx).Create(asset); err != nil {
|
if err := models.MediaAssetQuery.WithContext(ctx).Create(asset); err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &common_dto.UploadResult{
|
return &common_dto.UploadResult{
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort strin
|
|||||||
p := requests.Pagination{Page: int64(page), Limit: 10}
|
p := requests.Pagination{Page: int64(page), Limit: 10}
|
||||||
total, err := q.Count()
|
total, err := q.Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to DTO
|
// Convert to DTO
|
||||||
@@ -93,7 +93,7 @@ func (s *content) Get(ctx context.Context, id string) (*content_dto.ContentDetai
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errorx.ErrRecordNotFound
|
return nil, errorx.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interaction status (isLiked, isFavorited)
|
// Interaction status (isLiked, isFavorited)
|
||||||
@@ -127,12 +127,12 @@ func (s *content) ListComments(ctx context.Context, id string, page int) (*reque
|
|||||||
p := requests.Pagination{Page: int64(page), Limit: 10}
|
p := requests.Pagination{Page: int64(page), Limit: 10}
|
||||||
total, err := q.Count()
|
total, err := q.Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]content_dto.Comment, len(list))
|
data := make([]content_dto.Comment, len(list))
|
||||||
@@ -181,7 +181,7 @@ func (s *content) CreateComment(ctx context.Context, id string, form *content_dt
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := models.CommentQuery.WithContext(ctx).Create(comment); err != nil {
|
if err := models.CommentQuery.WithContext(ctx).Create(comment); err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (s *creator) Apply(ctx context.Context, form *creator_dto.ApplyForm) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := q.Create(tenant); err != nil {
|
if err := q.Create(tenant); err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also add user as tenant_admin in tenant_users
|
// Also add user as tenant_admin in tenant_users
|
||||||
@@ -55,7 +55,7 @@ func (s *creator) Apply(ctx context.Context, form *creator_dto.ApplyForm) error
|
|||||||
Status: consts.UserStatusVerified,
|
Status: consts.UserStatusVerified,
|
||||||
}
|
}
|
||||||
if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
|
if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -101,7 +101,7 @@ func (s *creator) ListContents(ctx context.Context, status, genre, keyword strin
|
|||||||
|
|
||||||
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []creator_dto.ContentItem
|
var data []creator_dto.ContentItem
|
||||||
@@ -185,7 +185,7 @@ func (s *creator) DeleteContent(ctx context.Context, id string) error {
|
|||||||
|
|
||||||
_, err = models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).Delete()
|
_, err = models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).Delete()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ func (s *creator) ListOrders(ctx context.Context, status, keyword string) ([]cre
|
|||||||
// Filters...
|
// Filters...
|
||||||
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []creator_dto.Order
|
var data []creator_dto.Order
|
||||||
@@ -217,6 +217,7 @@ func (s *creator) ListOrders(ctx context.Context, status, keyword string) ([]cre
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) ProcessRefund(ctx context.Context, id string, form *creator_dto.RefundForm) error {
|
func (s *creator) ProcessRefund(ctx context.Context, id string, form *creator_dto.RefundForm) error {
|
||||||
|
// Complex logic involving ledgers and order status update
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ func (s *creator) getTenantID(ctx context.Context) (int64, error) {
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return 0, errorx.ErrPermissionDenied.WithMsg("非创作者")
|
return 0, errorx.ErrPermissionDenied.WithMsg("非创作者")
|
||||||
}
|
}
|
||||||
return 0, errorx.ErrDatabaseError
|
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
return t.ID, nil
|
return t.ID, nil
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ func (s *order) ListUserOrders(ctx context.Context, status string) ([]user_dto.O
|
|||||||
|
|
||||||
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []user_dto.Order
|
var data []user_dto.Order
|
||||||
@@ -61,7 +61,7 @@ func (s *order) GetUserOrder(ctx context.Context, id string) (*user_dto.Order, e
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errorx.ErrRecordNotFound
|
return nil, errorx.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dto := s.toUserOrderDTO(item)
|
dto := s.toUserOrderDTO(item)
|
||||||
@@ -87,7 +87,7 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
|||||||
|
|
||||||
price, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
|
price, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDataCorrupted.WithMsg("价格信息缺失")
|
return nil, errorx.ErrDataCorrupted.WithCause(err).WithMsg("价格信息缺失")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create Order (Status: Created)
|
// 2. Create Order (Status: Created)
|
||||||
@@ -96,7 +96,7 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
|||||||
UserID: uid,
|
UserID: uid,
|
||||||
Type: consts.OrderTypeContentPurchase,
|
Type: consts.OrderTypeContentPurchase,
|
||||||
Status: consts.OrderStatusCreated,
|
Status: consts.OrderStatusCreated,
|
||||||
Currency: price.Currency,
|
Currency: consts.Currency(price.Currency), // price.Currency is consts.Currency in DB? Yes.
|
||||||
AmountOriginal: price.PriceAmount,
|
AmountOriginal: price.PriceAmount,
|
||||||
AmountDiscount: 0, // Calculate discount if needed
|
AmountDiscount: 0, // Calculate discount if needed
|
||||||
AmountPaid: price.PriceAmount, // Expected to pay
|
AmountPaid: price.PriceAmount, // Expected to pay
|
||||||
@@ -105,7 +105,7 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Create Order Item
|
// 3. Create Order Item
|
||||||
@@ -118,7 +118,7 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
|||||||
AmountPaid: order.AmountPaid,
|
AmountPaid: order.AmountPaid,
|
||||||
}
|
}
|
||||||
if err := models.OrderItemQuery.WithContext(ctx).Create(item); err != nil {
|
if err := models.OrderItemQuery.WithContext(ctx).Create(item); err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &transaction_dto.OrderCreateResponse{
|
return &transaction_dto.OrderCreateResponse{
|
||||||
@@ -159,6 +159,7 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
|||||||
info, err := tx.User.WithContext(ctx).
|
info, err := tx.User.WithContext(ctx).
|
||||||
Where(tx.User.ID.Eq(o.UserID), tx.User.Balance.Gte(o.AmountPaid)).
|
Where(tx.User.ID.Eq(o.UserID), tx.User.Balance.Gte(o.AmountPaid)).
|
||||||
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
|
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -217,9 +218,13 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(*errorx.AppError); ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
return &transaction_dto.OrderPayResponse{
|
return &transaction_dto.OrderPayResponse{
|
||||||
PayParams: "balance_paid",
|
PayParams: "balance_paid",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.T
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errorx.ErrRecordNotFound
|
return nil, errorx.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
@@ -106,7 +106,7 @@ func (s *tenant) Follow(ctx context.Context, id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
|
if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ func (s *tenant) Unfollow(ctx context.Context, id string) error {
|
|||||||
|
|
||||||
_, err := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid), models.TenantUserQuery.UserID.Eq(uid)).Delete()
|
_, err := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid), models.TenantUserQuery.UserID.Eq(uid)).Delete()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -53,10 +53,10 @@ func (s *user) LoginWithOTP(ctx context.Context, phone, otp string) (*auth_dto.L
|
|||||||
Gender: consts.GenderSecret, // 默认性别
|
Gender: consts.GenderSecret, // 默认性别
|
||||||
}
|
}
|
||||||
if err := query.Create(u); err != nil {
|
if err := query.Create(u); err != nil {
|
||||||
return nil, errorx.ErrDatabaseError.WithMsg("创建用户失败")
|
return nil, errorx.ErrDatabaseError.WithCause(err).WithMsg("创建用户失败")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, errorx.ErrDatabaseError.WithMsg("查询用户失败")
|
return nil, errorx.ErrDatabaseError.WithCause(err).WithMsg("查询用户失败")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ func (s *user) Me(ctx context.Context) (*auth_dto.User, error) {
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errorx.ErrRecordNotFound
|
return nil, errorx.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.toAuthUserDTO(u), nil
|
return s.toAuthUserDTO(u), nil
|
||||||
@@ -117,7 +117,7 @@ func (s *user) Update(ctx context.Context, form *user_dto.UserUpdate) error {
|
|||||||
// Birthday: form.Birthday, // 类型转换需处理
|
// Birthday: form.Birthday, // 类型转换需处理
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -139,7 +139,7 @@ func (s *user) RealName(ctx context.Context, form *user_dto.RealNameForm) error
|
|||||||
// RealName: form.Realname, // 需在 user 表添加字段? payout_accounts 有 realname
|
// RealName: form.Realname, // 需在 user 表添加字段? payout_accounts 有 realname
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorx.ErrDatabaseError
|
return errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ func (s *user) GetNotifications(ctx context.Context, typeArg string) ([]user_dto
|
|||||||
|
|
||||||
list, err := query.Order(tbl.CreatedAt.Desc()).Find()
|
list, err := query.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]user_dto.Notification, len(list))
|
result := make([]user_dto.Notification, len(list))
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (s *wallet) GetWallet(ctx context.Context) (*user_dto.WalletResponse, error
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errorx.ErrRecordNotFound
|
return nil, errorx.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Transactions (Orders)
|
// Get Transactions (Orders)
|
||||||
@@ -44,7 +44,7 @@ func (s *wallet) GetWallet(ctx context.Context) (*user_dto.WalletResponse, error
|
|||||||
Limit(20). // Limit to recent 20
|
Limit(20). // Limit to recent 20
|
||||||
Find()
|
Find()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var txs []user_dto.Transaction
|
var txs []user_dto.Transaction
|
||||||
@@ -100,7 +100,7 @@ func (s *wallet) Recharge(ctx context.Context, form *user_dto.RechargeForm) (*us
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
||||||
return nil, errorx.ErrDatabaseError
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock Pay Params
|
// Mock Pay Params
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ This file condenses `backend/docs/dev/http_api.md` + `backend/docs/dev/model.md`
|
|||||||
- DO regenerate code after changes (routes/docs/models).
|
- DO regenerate code after changes (routes/docs/models).
|
||||||
- MUST: in `backend/app/services`, prefer the generated GORM-Gen DAO (`backend/database/models/*`) for DB access; treat raw `*gorm.DB` usage as a last resort.
|
- MUST: in `backend/app/services`, prefer the generated GORM-Gen DAO (`backend/database/models/*`) for DB access; treat raw `*gorm.DB` usage as a last resort.
|
||||||
- MUST: When building queries in services, improve readability by using the assignment: `tbl, query := models.<Table>Query.QueryContext(ctx)`. Then use `tbl` for field references (e.g., `tbl.ID.Eq(...)`) and `query` for chaining methods.
|
- MUST: When building queries in services, improve readability by using the assignment: `tbl, query := models.<Table>Query.QueryContext(ctx)`. Then use `tbl` for field references (e.g., `tbl.ID.Eq(...)`) and `query` for chaining methods.
|
||||||
|
- MUST: in `services`, when an error occurs (e.g., DB error, third-party API error), NEVER return a generic `errorx.ErrXxx` alone if there is an underlying `err`. ALWAYS use `errorx.ErrXxx.WithCause(err)` to wrap the original error. This ensures the centralized Logger captures the full context (file, line, root cause) while the client receives a friendly message and a unique Error ID for tracking.
|
||||||
|
- MUST: all chainable methods on `AppError` (`WithCause`, `WithMsg`, `WithData`, etc.) are thread-safe and return a new instance (clone). Use them freely to add context to global error variables.
|
||||||
- MUST: service-layer transactions MUST use `models.Q.Transaction(func(tx *models.Query) error { ... })`; DO NOT use raw `*_db.Transaction(...)` / `db.Transaction(...)` in services unless Gen cannot express the required operation.
|
- MUST: service-layer transactions MUST use `models.Q.Transaction(func(tx *models.Query) error { ... })`; DO NOT use raw `*_db.Transaction(...)` / `db.Transaction(...)` in services unless Gen cannot express the required operation.
|
||||||
- MUST: after adding/removing/renaming any files under `backend/app/services/`, run `atomctl gen service --path ./app/services` to regenerate `backend/app/services/services.gen.go`; DO NOT edit `services.gen.go` manually.
|
- MUST: after adding/removing/renaming any files under `backend/app/services/`, run `atomctl gen service --path ./app/services` to regenerate `backend/app/services/services.gen.go`; DO NOT edit `services.gen.go` manually.
|
||||||
- DO add `// @provider` above every controller/service `struct` declaration.
|
- DO add `// @provider` above every controller/service `struct` declaration.
|
||||||
@@ -248,7 +250,7 @@ In this case:
|
|||||||
- `backend/app/events/subscribers/<snake_case>.go`(subscriber:实现 `contracts.EventHandler`,负责 `Topic()` + `Handler(...)`)
|
- `backend/app/events/subscribers/<snake_case>.go`(subscriber:实现 `contracts.EventHandler`,负责 `Topic()` + `Handler(...)`)
|
||||||
- 生成后:按项目约定运行一次 `atomctl gen provider`(用于刷新 DI/provider 生成文件)。
|
- 生成后:按项目约定运行一次 `atomctl gen provider`(用于刷新 DI/provider 生成文件)。
|
||||||
|
|
||||||
### Topic 约定
|
### Topic约定
|
||||||
|
|
||||||
- 统一在 `backend/app/events/topics.go` 维护 topic 常量,避免散落在各处形成“字符串协议”。
|
- 统一在 `backend/app/events/topics.go` 维护 topic 常量,避免散落在各处形成“字符串协议”。
|
||||||
- topic 字符串建议使用稳定前缀(例如 `event:`),并使用 `snake_case` 命名。
|
- topic 字符串建议使用稳定前缀(例如 `event:`),并使用 `snake_case` 命名。
|
||||||
@@ -297,7 +299,7 @@ Common types:
|
|||||||
- `Kind` 建议与业务枚举/事件类型对齐,便于 SQL/报表按 `kind` 过滤。
|
- `Kind` 建议与业务枚举/事件类型对齐,便于 SQL/报表按 `kind` 过滤。
|
||||||
- `Data` 写入对应 payload 的 JSON(payload 可以是多个不同 struct)。
|
- `Data` 写入对应 payload 的 JSON(payload 可以是多个不同 struct)。
|
||||||
- 读取时:
|
- 读取时:
|
||||||
- 先 `snap := model.Snapshot.Data()`,再 `switch snap.Kind` 选择对应 payload 结构去 `json.Unmarshal(snap.Data, &payload)`。
|
- 先 `snap := model.Snapshot.Data()`,再 `switch snap.Kind` 选择对应 payload结构去 `json.Unmarshal(snap.Data, &payload)`。
|
||||||
- 兼容历史数据(旧 JSON 没有 kind/data)时,`UnmarshalJSON` 可以将其标记为 `legacy` 并把原始 JSON 放入 `Data`,避免线上存量读取失败。
|
- 兼容历史数据(旧 JSON 没有 kind/data)时,`UnmarshalJSON` 可以将其标记为 `legacy` 并把原始 JSON 放入 `Data`,避免线上存量读取失败。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user