path
Build docker and publish / build (20.15.1) (push) Successful in 8m56s

This commit is contained in:
2026-05-19 19:35:44 -07:00
parent a1184ef5ed
commit fd522b6c71
31 changed files with 723 additions and 163 deletions
+94 -82
View File
@@ -1,108 +1,120 @@
package common
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net"
"strings"
"time"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net"
"strings"
"time"
"github.com/gin-gonic/gin"
logmessage "github.com/perfect-panel/server/internal/model/logmessage"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"github.com/gin-gonic/gin"
logmessage "github.com/perfect-panel/server/internal/model/logmessage"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type ReportLogMessageLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewReportLogMessageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ReportLogMessageLogic {
return &ReportLogMessageLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx }
return &ReportLogMessageLogic{Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *ReportLogMessageLogic) ReportLogMessage(req *types.ReportLogMessageRequest, c *gin.Context) (resp *types.ReportLogMessageResponse, err error) {
ip := clientIP(c)
ua := c.GetHeader("User-Agent")
locale := c.GetHeader("Accept-Language")
ip := clientIP(c)
ua := c.GetHeader("User-Agent")
locale := c.GetHeader("Accept-Language")
// 简单限流:设备ID优先,其次IP
limitKey := "logmsg:" + strings.TrimSpace(req.DeviceId)
if limitKey == "logmsg:" { limitKey = "logmsg:" + ip }
count, _ := l.svcCtx.Redis.Incr(l.ctx, limitKey).Result()
if count == 1 { _ = l.svcCtx.Redis.Expire(l.ctx, limitKey, 60*time.Second).Err() }
if count > 120 { // 每分钟最多120条
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "too many reports")
}
// 简单限流:设备ID优先,其次IP
limitKey := "logmsg:" + strings.TrimSpace(req.DeviceId)
if limitKey == "logmsg:" {
limitKey = "logmsg:" + ip
}
count, _ := l.svcCtx.Redis.Incr(l.ctx, limitKey).Result()
if count == 1 {
_ = l.svcCtx.Redis.Expire(l.ctx, limitKey, 60*time.Second).Err()
}
if count > 120 { // 每分钟最多120条
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TooManyRequests), "too many reports")
}
// 指纹生成
h := sha256.New()
h.Write([]byte(strings.Join([]string{req.Message, req.Stack, req.ErrorCode, req.AppVersion, req.Platform}, "|")))
digest := hex.EncodeToString(h.Sum(nil))
// 指纹生成
h := sha256.New()
h.Write([]byte(strings.Join([]string{req.Message, req.Stack, req.ErrorCode, req.AppVersion, req.Platform}, "|")))
digest := hex.EncodeToString(h.Sum(nil))
var ctxStr string
if req.Context != nil {
if b, e := json.Marshal(req.Context); e == nil {
ctxStr = string(b)
}
}
var occurredAt *time.Time
if req.OccurredAt > 0 {
t := time.UnixMilli(req.OccurredAt)
occurredAt = &t
}
var ctxStr string
if req.Context != nil {
if b, e := json.Marshal(req.Context); e == nil {
ctxStr = string(b)
}
}
var occurredAt *time.Time
if req.OccurredAt > 0 {
t := time.UnixMilli(req.OccurredAt)
occurredAt = &t
}
var userIdPtr *int64
if req.UserId > 0 { userIdPtr = &req.UserId }
var userIdPtr *int64
if req.UserId > 0 {
userIdPtr = &req.UserId
}
row := &logmessage.LogMessage{
Platform: req.Platform,
AppVersion: req.AppVersion,
OsName: req.OsName,
OsVersion: req.OsVersion,
DeviceId: req.DeviceId,
UserId: userIdPtr,
SessionId: req.SessionId,
Level: req.Level,
ErrorCode: req.ErrorCode,
Message: safeTruncate(req.Message, 1024*64),
Stack: safeTruncate(req.Stack, 1024*1024),
Context: ctxStr,
ClientIP: ip,
UserAgent: safeTruncate(ua, 255),
Locale: safeTruncate(locale, 16),
Digest: digest,
OccurredAt: occurredAt,
}
row := &logmessage.LogMessage{
Platform: safeTruncate(req.Platform, 32),
AppVersion: safeTruncate(req.AppVersion, 64),
OsName: safeTruncate(req.OsName, 64),
OsVersion: safeTruncate(req.OsVersion, 64),
DeviceId: safeTruncate(req.DeviceId, 255),
UserId: userIdPtr,
SessionId: safeTruncate(req.SessionId, 255),
Level: req.Level,
ErrorCode: safeTruncate(req.ErrorCode, 128),
Message: safeTruncate(req.Message, 1024*64),
Stack: safeTruncate(req.Stack, 1024*1024),
Context: ctxStr,
ClientIP: ip,
UserAgent: safeTruncate(ua, 255),
Locale: safeTruncate(locale, 16),
Digest: digest,
OccurredAt: occurredAt,
}
if err = l.svcCtx.LogMessageModel.Insert(l.ctx, row); err != nil {
// 唯一指纹冲突时尝试查询已有记录返回ID
ex, _, findErr := l.svcCtx.LogMessageModel.Filter(l.ctx, &logmessage.FilterParams{ Keyword: req.Message, Page: 1, Size: 1 })
if findErr == nil && len(ex) > 0 {
return &types.ReportLogMessageResponse{ Id: ex[0].Id }, nil
}
l.Errorf("[ReportLogMessage] insert error: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "insert log_message failed: %v", err)
}
return &types.ReportLogMessageResponse{ Id: row.Id }, nil
if err = l.svcCtx.LogMessageModel.Insert(l.ctx, row); err != nil {
// 唯一指纹冲突时尝试查询已有记录返回ID
ex, _, findErr := l.svcCtx.LogMessageModel.Filter(l.ctx, &logmessage.FilterParams{Keyword: req.Message, Page: 1, Size: 1})
if findErr == nil && len(ex) > 0 {
return &types.ReportLogMessageResponse{Id: ex[0].Id}, nil
}
l.Errorf("[ReportLogMessage] insert error: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "insert log_message failed: %v", err)
}
return &types.ReportLogMessageResponse{Id: row.Id}, nil
}
func safeTruncate(s string, n int) string {
if len(s) <= n { return s }
return s[:n]
if len(s) <= n {
return s
}
return s[:n]
}
func clientIP(c *gin.Context) string {
ip := c.ClientIP()
if ip != "" { return ip }
host, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err == nil && host != "" { return host }
return ""
ip := c.ClientIP()
if ip != "" {
return ip
}
host, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err == nil && host != "" {
return host
}
return ""
}
+48
View File
@@ -0,0 +1,48 @@
package common
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/log"
usermodel "github.com/perfect-panel/server/internal/model/user"
"github.com/pkg/errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func WriteCommissionLog(tx *gorm.DB, objectID int64, logType uint16, amount int64, orderNo string) error {
logInfo := log.Commission{
Type: logType,
Amount: amount,
OrderNo: orderNo,
Timestamp: time.Now().UnixMilli(),
}
content, err := logInfo.Marshal()
if err != nil {
return err
}
return tx.Model(log.SystemLog{}).Create(&log.SystemLog{
Type: log.TypeCommission.Uint8(),
Date: time.Now().Format(time.DateOnly),
ObjectID: objectID,
Content: string(content),
CreatedAt: time.Now(),
}).Error
}
func LoadPendingWithdrawalForUpdate(ctx context.Context, tx *gorm.DB, withdrawalID int64) (*usermodel.Withdrawal, error) {
var withdrawal usermodel.Withdrawal
if err := tx.WithContext(ctx).
Clauses(clause.Locking{Strength: "UPDATE"}).
Where("id = ?", withdrawalID).
First(&withdrawal).Error; err != nil {
return nil, err
}
if withdrawal.Status != 0 {
return nil, errors.New("withdrawal status invalid")
}
return &withdrawal, nil
}