diff --git a/backend/src/app/api/user_api.go b/backend/src/app/api/user_api.go new file mode 100644 index 0000000..12d2629 --- /dev/null +++ b/backend/src/app/api/user_api.go @@ -0,0 +1,46 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/mumu/cryptoSwap/src/app/service" + "github.com/mumu/cryptoSwap/src/core/log" + "github.com/mumu/cryptoSwap/src/core/result" + "go.uber.org/zap" +) + +type UserApi struct { + svc *service.UserService +} + +func NewUserApi() *UserApi { + return &UserApi{ + svc: service.NewUserService(), + } +} + +// GetProfile godoc +// @Summary 获取用户资料 +// @Description 根据钱包地址返回用户的积分、质押总量和活跃质押数量 +// @Tags user +// @Accept json +// @Produce json +// @Param Authorization header string false "Bearer token" +// @Param walletAddress query string false "用户地址" +// @Success 200 {object} result.Response{data=service.UserProfile} +// @Router /api/v1/user/profile [get] +func (u *UserApi) GetProfile(c *gin.Context) { + addr := extractWalletAddress(c) + if addr == "" { + result.Error(c, result.InvalidParameter) + return + } + + profile, err := u.svc.GetProfile(addr) + if err != nil { + log.Logger.Error("获取用户资料失败", zap.String("address", addr), zap.Error(err)) + result.Error(c, result.DBQueryFailed) + return + } + + result.OK(c, profile) +} diff --git a/backend/src/app/service/user_service.go b/backend/src/app/service/user_service.go new file mode 100644 index 0000000..397058f --- /dev/null +++ b/backend/src/app/service/user_service.go @@ -0,0 +1,75 @@ +package service + +import ( + "fmt" + "strings" + + "github.com/mumu/cryptoSwap/src/app/model" + "github.com/mumu/cryptoSwap/src/core/ctx" + "github.com/mumu/cryptoSwap/src/core/log" + "go.uber.org/zap" +) + +// UserProfile 用户资料 +type UserProfile struct { + Address string `json:"address"` + TotalStaked float64 `json:"totalStaked"` + Points float64 `json:"points"` + ActiveStakes int64 `json:"activeStakes"` +} + +type UserService struct{} + +func NewUserService() *UserService { + return &UserService{} +} + +// GetProfile 获取用户资料 +func (s *UserService) GetProfile(address string) (*UserProfile, error) { + addr := strings.ToLower(address) + + // 查询用户记录(可能跨多条链,累计积分) + var users []model.Users + if err := ctx.Ctx.DB.Where("address = ?", addr).Find(&users).Error; err != nil { + return nil, fmt.Errorf("查询用户信息失败: %v", err) + } + + // 汇总各链数据 + var totalStaked float64 + var totalPoints float64 + for _, u := range users { + totalStaked += float64(u.TotalAmount) / 1e18 + jf, exact := u.Jf.Float64() + if !exact { + log.Logger.Warn("积分精度丢失", zap.String("address", addr), zap.String("jf", u.Jf.String())) + } + totalPoints += jf + } + + // 统计活跃质押数量:质押数减去已提取数 + var stakedCount int64 + if err := ctx.Ctx.DB.Model(&model.UserOperationRecord{}). + Where("address = ? AND event_type = ?", addr, "Staked"). + Count(&stakedCount).Error; err != nil { + return nil, fmt.Errorf("统计质押记录失败: %v", err) + } + + var withdrawnCount int64 + if err := ctx.Ctx.DB.Model(&model.UserOperationRecord{}). + Where("address = ? AND event_type = ?", addr, "withdraw"). + Count(&withdrawnCount).Error; err != nil { + return nil, fmt.Errorf("统计提取记录失败: %v", err) + } + + activeStakes := stakedCount - withdrawnCount + if activeStakes < 0 { + activeStakes = 0 + } + + return &UserProfile{ + Address: address, + TotalStaked: totalStaked, + Points: totalPoints, + ActiveStakes: activeStakes, + }, nil +} diff --git a/backend/src/core/gin/router/router.go b/backend/src/core/gin/router/router.go index 24cae9f..5cdb29a 100644 --- a/backend/src/core/gin/router/router.go +++ b/backend/src/core/gin/router/router.go @@ -98,6 +98,10 @@ func ApiBind(r *gin.Engine, ctx *ctx.Context) { // 管理员更新默克尔根(需要运维调用,后续可加鉴权) v.POST("/airdrop/admin/updateMerkleRoot", airDropApi.AdminUpdateMerkleRoot) + // 用户资料接口 + userApi := api.NewUserApi() + v.GET("/user/profile", userApi.GetProfile) + // 质押相关接口(需要验证) stakeApi := api.NewStakeApi() // 质押代币