feat:修改活动 TOP3 + 我的排名
This commit is contained in:
parent
a077fe2685
commit
b7e2aee399
@ -1106,3 +1106,108 @@ func convertContributionRankingResponse(resp *pbActivity.ContributionRankingResp
|
|||||||
"total": resp.Total,
|
"total": resp.Total,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTopRanking 获取活动 TOP3 + 我的排名(专用轻量接口)
|
||||||
|
// @Summary 获取活动 TOP3 + 我的排名
|
||||||
|
// @Description 返回 top3 头像组 + my_info(rank/avatar/gap_to_prev/status),与 /ranking 不同:仅返回本组件所需字段,gap_to_prev 由后端计算
|
||||||
|
// @Tags activities
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param id path int64 true "活动ID"
|
||||||
|
// @Param star_id query int64 false "粉丝身份ID(不传则用 token 中的)"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.Response
|
||||||
|
// @Failure 401 {object} response.Response
|
||||||
|
// @Router /api/v1/activities/{id}/top-ranking [get]
|
||||||
|
func (ctrl *ActivityController) GetTopRanking(c *gin.Context) {
|
||||||
|
// 1) 鉴权
|
||||||
|
userID, exists := c.Get("user_id")
|
||||||
|
if !exists {
|
||||||
|
response.Error(c, http.StatusUnauthorized, "未授权")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 解析路径参数 activity_id
|
||||||
|
activityIDStr := c.Param("id")
|
||||||
|
activityID, err := strconv.ParseInt(activityIDStr, 10, 64)
|
||||||
|
if err != nil || activityID <= 0 {
|
||||||
|
response.Error(c, http.StatusBadRequest, "活动ID参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) star_id:query 优先,缺省用 token 中的;都没有按 0 处理
|
||||||
|
reqStarID := int64(0)
|
||||||
|
if v, ok := c.Get("star_id"); ok && v != nil {
|
||||||
|
if sID, ok2 := v.(int64); ok2 {
|
||||||
|
reqStarID = sID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if qs := c.Query("star_id"); qs != "" {
|
||||||
|
if v, perr := strconv.ParseInt(qs, 10, 64); perr == nil && v > 0 {
|
||||||
|
reqStarID = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Logger.Info("GetTopRanking request",
|
||||||
|
zap.Int64("user_id", userID.(int64)),
|
||||||
|
zap.Int64("activity_id", activityID),
|
||||||
|
zap.Int64("star_id", reqStarID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 4) gRPC 调用
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{
|
||||||
|
"user_id": strconv.FormatInt(userID.(int64), 10),
|
||||||
|
"star_id": strconv.FormatInt(reqStarID, 10),
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := ctrl.activityService.GetTopRanking(ctx, &pbActivity.TopRankingRequest{
|
||||||
|
ActivityId: activityID,
|
||||||
|
StarId: reqStarID,
|
||||||
|
UserId: userID.(int64),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Error("GetTopRanking RPC failed", zap.Error(err))
|
||||||
|
response.Error(c, http.StatusInternalServerError, "获取 top-ranking 失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Base != nil && resp.Base.Code != uint32(codes.OK) {
|
||||||
|
response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) 转换并返回
|
||||||
|
data := convertTopRankingResponse(resp)
|
||||||
|
response.Success(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertTopRankingResponse proto -> gin.H(供前端)
|
||||||
|
func convertTopRankingResponse(resp *pbActivity.TopRankingResponse) map[string]interface{} {
|
||||||
|
top3 := make([]map[string]interface{}, 0, len(resp.Top3))
|
||||||
|
for _, it := range resp.Top3 {
|
||||||
|
top3 = append(top3, map[string]interface{}{
|
||||||
|
"rank": it.Rank,
|
||||||
|
"user_id": it.UserId,
|
||||||
|
"avatar_url": it.AvatarUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var myInfo map[string]interface{}
|
||||||
|
if resp.MyInfo != nil {
|
||||||
|
myInfo = map[string]interface{}{
|
||||||
|
"rank": resp.MyInfo.Rank,
|
||||||
|
"avatar_url": resp.MyInfo.AvatarUrl,
|
||||||
|
"gap_to_prev": resp.MyInfo.GapToPrev,
|
||||||
|
"status": resp.MyInfo.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"top3": top3,
|
||||||
|
"my_info": myInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -723,6 +723,106 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/activities/{activity_id}/messages": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "分页获取活动留言列表(最新在上)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"activities"
|
||||||
|
],
|
||||||
|
"summary": "列出活动留言",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "活动ID",
|
||||||
|
"name": "activity_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码,默认1",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量,默认20,最大50",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "用户在应援活动页面发送一条祝福留言",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"activities"
|
||||||
|
],
|
||||||
|
"summary": "发送活动留言",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "活动ID",
|
||||||
|
"name": "activity_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "留言内容",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/activities/{activity_id}/progress": {
|
"/api/v1/activities/{activity_id}/progress": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -865,6 +965,97 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/activities/{id}/top-ranking": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "返回 top3 头像组 + my_info(rank/avatar/gap_to_prev/status),与 /ranking 不同:仅返回本组件所需字段,gap_to_prev 由后端计算",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"activities"
|
||||||
|
],
|
||||||
|
"summary": "获取活动 TOP3 + 我的排名",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "活动ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "粉丝身份ID(不传则用 token 中的)",
|
||||||
|
"name": "star_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/notifications": {
|
||||||
|
"post": {
|
||||||
|
"description": "内部接口,无 JWT 鉴权,供 admin 后台(Python 8081)调用。user_ids 由调用方解析(查 fan_profiles)。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "admin 批量发送通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "通知 payload(已解析的 user_ids)",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.adminCreateNotificationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/ai-chat/history/{sessionId}": {
|
"/api/v1/ai-chat/history/{sessionId}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -3034,6 +3225,350 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/notifications": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取当前用户的通知列表(支持 type/tab 分页)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "获取通知列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "通知类型过滤: like / system / activity",
|
||||||
|
"name": "type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "列表 tab: unread / read / all",
|
||||||
|
"name": "tab",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码,默认1",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量,默认20",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/devices": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将 uni.getPushClientId() 拿到的 cid 上报给后端;同 cid 重复注册为更新。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "注册推送设备",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "设备信息",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.registerDeviceRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/devices/unregister": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "注销推送 cid;cid 为空 = 注销当前用户全部设备(用于主动登出)。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "注销推送设备",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "注销请求",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.unregisterDeviceRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/read-all": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将当前用户某类型或全部通知标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "全部已读",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "通知类型过滤: like / system / activity; 留空表示全部",
|
||||||
|
"name": "type",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/targets/{target_id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "删除同一 target 下的所有通知",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "按目标ID删除通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "目标ID(如藏品ID)",
|
||||||
|
"name": "target_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/targets/{target_id}/read": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将同一 target 下的所有通知标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "按目标ID标记已读",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "目标ID(如藏品ID)",
|
||||||
|
"name": "target_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/unread-count": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "按类型返回未读数量(like/system/activity/total)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "获取未读通知数",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "根据ID删除通知",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "删除单条通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "通知ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/{id}/read": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "根据通知ID标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "标记单条通知已读",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "通知ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/public/oss/upload-signature": {
|
"/api/v1/public/oss/upload-signature": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "用于注册等未登录场景下上传头像,无需鉴权。\n后端生成唯一完整 key,policy 锁到该 key,前端只能写到指定路径;key 形如 avatar/register-pending/{key}/avatar_\u003cuuid\u003e.png。",
|
"description": "用于注册等未登录场景下上传头像,无需鉴权。\n后端生成唯一完整 key,policy 锁到该 key,前端只能写到指定路径;key 形如 avatar/register-pending/{key}/avatar_\u003cuuid\u003e.png。",
|
||||||
@ -4135,6 +4670,86 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"controller.adminCreateNotificationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"type",
|
||||||
|
"user_ids"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 500
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"star_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 200,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"system",
|
||||||
|
"activity",
|
||||||
|
"like"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 10000,
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller.registerDeviceRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cid"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"app_version": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32
|
||||||
|
},
|
||||||
|
"cid": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"device_model": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 64
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ios",
|
||||||
|
"android",
|
||||||
|
"harmony"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller.unregisterDeviceRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cid": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.AddIdentityResponseDTO": {
|
"dto.AddIdentityResponseDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -4277,6 +4892,14 @@ const docTemplate = `{
|
|||||||
"description": "封面图URL",
|
"description": "封面图URL",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"earnings": {
|
||||||
|
"description": "当前可领取收益(与 ExhibitedAssetItemDTO 命名对齐)",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"hourly_earnings": {
|
||||||
|
"description": "每小时收益",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"like_count": {
|
"like_count": {
|
||||||
"description": "点赞数",
|
"description": "点赞数",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
|||||||
@ -717,6 +717,106 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/activities/{activity_id}/messages": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "分页获取活动留言列表(最新在上)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"activities"
|
||||||
|
],
|
||||||
|
"summary": "列出活动留言",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "活动ID",
|
||||||
|
"name": "activity_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码,默认1",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量,默认20,最大50",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "用户在应援活动页面发送一条祝福留言",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"activities"
|
||||||
|
],
|
||||||
|
"summary": "发送活动留言",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "活动ID",
|
||||||
|
"name": "activity_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "留言内容",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/activities/{activity_id}/progress": {
|
"/api/v1/activities/{activity_id}/progress": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -859,6 +959,97 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/activities/{id}/top-ranking": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "返回 top3 头像组 + my_info(rank/avatar/gap_to_prev/status),与 /ranking 不同:仅返回本组件所需字段,gap_to_prev 由后端计算",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"activities"
|
||||||
|
],
|
||||||
|
"summary": "获取活动 TOP3 + 我的排名",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "活动ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "粉丝身份ID(不传则用 token 中的)",
|
||||||
|
"name": "star_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/admin/notifications": {
|
||||||
|
"post": {
|
||||||
|
"description": "内部接口,无 JWT 鉴权,供 admin 后台(Python 8081)调用。user_ids 由调用方解析(查 fan_profiles)。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "admin 批量发送通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "通知 payload(已解析的 user_ids)",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.adminCreateNotificationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/ai-chat/history/{sessionId}": {
|
"/api/v1/ai-chat/history/{sessionId}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -3028,6 +3219,350 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/notifications": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取当前用户的通知列表(支持 type/tab 分页)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "获取通知列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "通知类型过滤: like / system / activity",
|
||||||
|
"name": "type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "列表 tab: unread / read / all",
|
||||||
|
"name": "tab",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码,默认1",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量,默认20",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/devices": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将 uni.getPushClientId() 拿到的 cid 上报给后端;同 cid 重复注册为更新。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "注册推送设备",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "设备信息",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.registerDeviceRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/devices/unregister": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "注销推送 cid;cid 为空 = 注销当前用户全部设备(用于主动登出)。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "注销推送设备",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "注销请求",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.unregisterDeviceRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/read-all": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将当前用户某类型或全部通知标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "全部已读",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "通知类型过滤: like / system / activity; 留空表示全部",
|
||||||
|
"name": "type",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/targets/{target_id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "删除同一 target 下的所有通知",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "按目标ID删除通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "目标ID(如藏品ID)",
|
||||||
|
"name": "target_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/targets/{target_id}/read": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "将同一 target 下的所有通知标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "按目标ID标记已读",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "目标ID(如藏品ID)",
|
||||||
|
"name": "target_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/unread-count": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "按类型返回未读数量(like/system/activity/total)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "获取未读通知数",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "根据ID删除通知",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "删除单条通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "通知ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/notifications/{id}/read": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "根据通知ID标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "标记单条通知已读",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "通知ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/public/oss/upload-signature": {
|
"/api/v1/public/oss/upload-signature": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "用于注册等未登录场景下上传头像,无需鉴权。\n后端生成唯一完整 key,policy 锁到该 key,前端只能写到指定路径;key 形如 avatar/register-pending/{key}/avatar_\u003cuuid\u003e.png。",
|
"description": "用于注册等未登录场景下上传头像,无需鉴权。\n后端生成唯一完整 key,policy 锁到该 key,前端只能写到指定路径;key 形如 avatar/register-pending/{key}/avatar_\u003cuuid\u003e.png。",
|
||||||
@ -4129,6 +4664,86 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"controller.adminCreateNotificationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"type",
|
||||||
|
"user_ids"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 500
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"star_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 200,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"system",
|
||||||
|
"activity",
|
||||||
|
"like"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 10000,
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller.registerDeviceRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cid"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"app_version": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32
|
||||||
|
},
|
||||||
|
"cid": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128,
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"device_model": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 64
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ios",
|
||||||
|
"android",
|
||||||
|
"harmony"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller.unregisterDeviceRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cid": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.AddIdentityResponseDTO": {
|
"dto.AddIdentityResponseDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -4271,6 +4886,14 @@
|
|||||||
"description": "封面图URL",
|
"description": "封面图URL",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"earnings": {
|
||||||
|
"description": "当前可领取收益(与 ExhibitedAssetItemDTO 命名对齐)",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"hourly_earnings": {
|
||||||
|
"description": "每小时收益",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"like_count": {
|
"like_count": {
|
||||||
"description": "点赞数",
|
"description": "点赞数",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
|||||||
@ -49,6 +49,64 @@ definitions:
|
|||||||
description: 响应时间戳(Unix时间戳毫秒)
|
description: 响应时间戳(Unix时间戳毫秒)
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
controller.adminCreateNotificationRequest:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
maxLength: 500
|
||||||
|
type: string
|
||||||
|
data:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
star_id:
|
||||||
|
type: integer
|
||||||
|
title:
|
||||||
|
maxLength: 200
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
enum:
|
||||||
|
- system
|
||||||
|
- activity
|
||||||
|
- like
|
||||||
|
type: string
|
||||||
|
user_ids:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
maxItems: 10000
|
||||||
|
minItems: 1
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- title
|
||||||
|
- type
|
||||||
|
- user_ids
|
||||||
|
type: object
|
||||||
|
controller.registerDeviceRequest:
|
||||||
|
properties:
|
||||||
|
app_version:
|
||||||
|
maxLength: 32
|
||||||
|
type: string
|
||||||
|
cid:
|
||||||
|
maxLength: 128
|
||||||
|
minLength: 1
|
||||||
|
type: string
|
||||||
|
device_model:
|
||||||
|
maxLength: 64
|
||||||
|
type: string
|
||||||
|
platform:
|
||||||
|
enum:
|
||||||
|
- ios
|
||||||
|
- android
|
||||||
|
- harmony
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cid
|
||||||
|
type: object
|
||||||
|
controller.unregisterDeviceRequest:
|
||||||
|
properties:
|
||||||
|
cid:
|
||||||
|
maxLength: 128
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.AddIdentityResponseDTO:
|
dto.AddIdentityResponseDTO:
|
||||||
properties:
|
properties:
|
||||||
bound:
|
bound:
|
||||||
@ -151,6 +209,12 @@ definitions:
|
|||||||
cover_url:
|
cover_url:
|
||||||
description: 封面图URL
|
description: 封面图URL
|
||||||
type: string
|
type: string
|
||||||
|
earnings:
|
||||||
|
description: 当前可领取收益(与 ExhibitedAssetItemDTO 命名对齐)
|
||||||
|
type: integer
|
||||||
|
hourly_earnings:
|
||||||
|
description: 每小时收益
|
||||||
|
type: number
|
||||||
like_count:
|
like_count:
|
||||||
description: 点赞数
|
description: 点赞数
|
||||||
type: integer
|
type: integer
|
||||||
@ -1403,6 +1467,70 @@ paths:
|
|||||||
summary: 获取活动道具列表
|
summary: 获取活动道具列表
|
||||||
tags:
|
tags:
|
||||||
- activities
|
- activities
|
||||||
|
/api/v1/activities/{activity_id}/messages:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 分页获取活动留言列表(最新在上)
|
||||||
|
parameters:
|
||||||
|
- description: 活动ID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: activity_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: 页码,默认1
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: 每页数量,默认20,最大50
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 列出活动留言
|
||||||
|
tags:
|
||||||
|
- activities
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 用户在应援活动页面发送一条祝福留言
|
||||||
|
parameters:
|
||||||
|
- description: 活动ID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: activity_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: 留言内容
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 发送活动留言
|
||||||
|
tags:
|
||||||
|
- activities
|
||||||
/api/v1/activities/{activity_id}/progress:
|
/api/v1/activities/{activity_id}/progress:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@ -1494,6 +1622,66 @@ paths:
|
|||||||
summary: 获取贡献点排名
|
summary: 获取贡献点排名
|
||||||
tags:
|
tags:
|
||||||
- activities
|
- activities
|
||||||
|
/api/v1/activities/{id}/top-ranking:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 返回 top3 头像组 + my_info(rank/avatar/gap_to_prev/status),与 /ranking
|
||||||
|
不同:仅返回本组件所需字段,gap_to_prev 由后端计算
|
||||||
|
parameters:
|
||||||
|
- description: 活动ID
|
||||||
|
format: int64
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: 粉丝身份ID(不传则用 token 中的)
|
||||||
|
format: int64
|
||||||
|
in: query
|
||||||
|
name: star_id
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 获取活动 TOP3 + 我的排名
|
||||||
|
tags:
|
||||||
|
- activities
|
||||||
|
/api/v1/admin/notifications:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 内部接口,无 JWT 鉴权,供 admin 后台(Python 8081)调用。user_ids 由调用方解析(查 fan_profiles)。
|
||||||
|
parameters:
|
||||||
|
- description: 通知 payload(已解析的 user_ids)
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.adminCreateNotificationRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
summary: admin 批量发送通知
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/api/v1/ai-chat/history/{sessionId}:
|
/api/v1/ai-chat/history/{sessionId}:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@ -2804,6 +2992,219 @@ paths:
|
|||||||
summary: 切换粉丝身份
|
summary: 切换粉丝身份
|
||||||
tags:
|
tags:
|
||||||
- users
|
- users
|
||||||
|
/api/v1/notifications:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 获取当前用户的通知列表(支持 type/tab 分页)
|
||||||
|
parameters:
|
||||||
|
- description: '通知类型过滤: like / system / activity'
|
||||||
|
in: query
|
||||||
|
name: type
|
||||||
|
type: string
|
||||||
|
- description: '列表 tab: unread / read / all'
|
||||||
|
in: query
|
||||||
|
name: tab
|
||||||
|
type: string
|
||||||
|
- description: 页码,默认1
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: 每页数量,默认20
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 获取通知列表
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 根据ID删除通知
|
||||||
|
parameters:
|
||||||
|
- description: 通知ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 删除单条通知
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/{id}/read:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 根据通知ID标记为已读
|
||||||
|
parameters:
|
||||||
|
- description: 通知ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 标记单条通知已读
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/devices:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 将 uni.getPushClientId() 拿到的 cid 上报给后端;同 cid 重复注册为更新。
|
||||||
|
parameters:
|
||||||
|
- description: 设备信息
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.registerDeviceRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 注册推送设备
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/devices/unregister:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 注销推送 cid;cid 为空 = 注销当前用户全部设备(用于主动登出)。
|
||||||
|
parameters:
|
||||||
|
- description: 注销请求
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.unregisterDeviceRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 注销推送设备
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/read-all:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 将当前用户某类型或全部通知标记为已读
|
||||||
|
parameters:
|
||||||
|
- description: '通知类型过滤: like / system / activity; 留空表示全部'
|
||||||
|
in: query
|
||||||
|
name: type
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 全部已读
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/targets/{target_id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 删除同一 target 下的所有通知
|
||||||
|
parameters:
|
||||||
|
- description: 目标ID(如藏品ID)
|
||||||
|
in: path
|
||||||
|
name: target_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 按目标ID删除通知
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/targets/{target_id}/read:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 将同一 target 下的所有通知标记为已读
|
||||||
|
parameters:
|
||||||
|
- description: 目标ID(如藏品ID)
|
||||||
|
in: path
|
||||||
|
name: target_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 按目标ID标记已读
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
|
/api/v1/notifications/unread-count:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 按类型返回未读数量(like/system/activity/total)
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 获取未读通知数
|
||||||
|
tags:
|
||||||
|
- notifications
|
||||||
/api/v1/public/oss/upload-signature:
|
/api/v1/public/oss/upload-signature:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@ -388,6 +388,7 @@ func SetupRouter(userClient *client.Client, socialClient *client.Client, assetCl
|
|||||||
activities.POST("/:id/purchase", activityCtrl.PurchaseItem) // 购买道具
|
activities.POST("/:id/purchase", activityCtrl.PurchaseItem) // 购买道具
|
||||||
activities.POST("/:id/batch-purchase", activityCtrl.BatchPurchaseItem) // 批量购买道具
|
activities.POST("/:id/batch-purchase", activityCtrl.BatchPurchaseItem) // 批量购买道具
|
||||||
activities.GET("/:id/ranking", activityCtrl.GetContributionRanking) // 获取贡献点排名
|
activities.GET("/:id/ranking", activityCtrl.GetContributionRanking) // 获取贡献点排名
|
||||||
|
activities.GET("/:id/top-ranking", activityCtrl.GetTopRanking) // 获取活动 TOP3 + 我的排名(专用轻量接口)
|
||||||
activities.GET("/:id/contributions/latest", activityCtrl.GetLatestContributions) // 获取最新贡献记录
|
activities.GET("/:id/contributions/latest", activityCtrl.GetLatestContributions) // 获取最新贡献记录
|
||||||
activities.GET("/:id/messages", activityCtrl.ListActivityMessages) // 获取活动留言列表
|
activities.GET("/:id/messages", activityCtrl.ListActivityMessages) // 获取活动留言列表
|
||||||
activities.POST("/:id/messages", activityCtrl.CreateActivityMessage) // 发送一条活动留言
|
activities.POST("/:id/messages", activityCtrl.CreateActivityMessage) // 发送一条活动留言
|
||||||
|
|||||||
@ -1104,6 +1104,258 @@ func (x *MyContribution) GetAvatarUrl() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 活动 TOP3 + 我的排名请求(专用轻量接口)
|
||||||
|
type TopRankingRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ActivityId int64 `protobuf:"varint,1,opt,name=activity_id,json=activityId,proto3" json:"activity_id,omitempty"`
|
||||||
|
StarId int64 `protobuf:"varint,2,opt,name=star_id,json=starId,proto3" json:"star_id,omitempty"` // 0 表示不按明星过滤
|
||||||
|
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 来自 token
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingRequest) Reset() {
|
||||||
|
*x = TopRankingRequest{}
|
||||||
|
mi := &file_activity_proto_msgTypes[13]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TopRankingRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *TopRankingRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_activity_proto_msgTypes[13]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use TopRankingRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*TopRankingRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_activity_proto_rawDescGZIP(), []int{13}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingRequest) GetActivityId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ActivityId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingRequest) GetStarId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.StarId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingRequest) GetUserId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOP3 排名项
|
||||||
|
type TopRankingItem struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Rank int32 `protobuf:"varint,1,opt,name=rank,proto3" json:"rank,omitempty"`
|
||||||
|
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
|
AvatarUrl string `protobuf:"bytes,3,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingItem) Reset() {
|
||||||
|
*x = TopRankingItem{}
|
||||||
|
mi := &file_activity_proto_msgTypes[14]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingItem) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TopRankingItem) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *TopRankingItem) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_activity_proto_msgTypes[14]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use TopRankingItem.ProtoReflect.Descriptor instead.
|
||||||
|
func (*TopRankingItem) Descriptor() ([]byte, []int) {
|
||||||
|
return file_activity_proto_rawDescGZIP(), []int{14}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingItem) GetRank() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Rank
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingItem) GetUserId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingItem) GetAvatarUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AvatarUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 我的 TOP 排名信息
|
||||||
|
type MyTopRankingInfo struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Rank int32 `protobuf:"varint,1,opt,name=rank,proto3" json:"rank,omitempty"` // 0 表示未上榜
|
||||||
|
AvatarUrl string `protobuf:"bytes,2,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"`
|
||||||
|
GapToPrev int64 `protobuf:"varint,3,opt,name=gap_to_prev,json=gapToPrev,proto3" json:"gap_to_prev,omitempty"`
|
||||||
|
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` // "ranked" | "unranked"
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) Reset() {
|
||||||
|
*x = MyTopRankingInfo{}
|
||||||
|
mi := &file_activity_proto_msgTypes[15]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*MyTopRankingInfo) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_activity_proto_msgTypes[15]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use MyTopRankingInfo.ProtoReflect.Descriptor instead.
|
||||||
|
func (*MyTopRankingInfo) Descriptor() ([]byte, []int) {
|
||||||
|
return file_activity_proto_rawDescGZIP(), []int{15}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) GetRank() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Rank
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) GetAvatarUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AvatarUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) GetGapToPrev() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.GapToPrev
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *MyTopRankingInfo) GetStatus() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOP 排名响应
|
||||||
|
type TopRankingResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||||
|
Top3 []*TopRankingItem `protobuf:"bytes,2,rep,name=top3,proto3" json:"top3,omitempty"`
|
||||||
|
MyInfo *MyTopRankingInfo `protobuf:"bytes,3,opt,name=my_info,json=myInfo,proto3" json:"my_info,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingResponse) Reset() {
|
||||||
|
*x = TopRankingResponse{}
|
||||||
|
mi := &file_activity_proto_msgTypes[16]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TopRankingResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *TopRankingResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_activity_proto_msgTypes[16]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use TopRankingResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*TopRankingResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_activity_proto_rawDescGZIP(), []int{16}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingResponse) GetBase() *common.BaseResponse {
|
||||||
|
if x != nil {
|
||||||
|
return x.Base
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingResponse) GetTop3() []*TopRankingItem {
|
||||||
|
if x != nil {
|
||||||
|
return x.Top3
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *TopRankingResponse) GetMyInfo() *MyTopRankingInfo {
|
||||||
|
if x != nil {
|
||||||
|
return x.MyInfo
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 活动列表请求
|
// 活动列表请求
|
||||||
type GetActivityListRequest struct {
|
type GetActivityListRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
@ -1117,7 +1369,7 @@ type GetActivityListRequest struct {
|
|||||||
|
|
||||||
func (x *GetActivityListRequest) Reset() {
|
func (x *GetActivityListRequest) Reset() {
|
||||||
*x = GetActivityListRequest{}
|
*x = GetActivityListRequest{}
|
||||||
mi := &file_activity_proto_msgTypes[13]
|
mi := &file_activity_proto_msgTypes[17]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1129,7 +1381,7 @@ func (x *GetActivityListRequest) String() string {
|
|||||||
func (*GetActivityListRequest) ProtoMessage() {}
|
func (*GetActivityListRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetActivityListRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetActivityListRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[13]
|
mi := &file_activity_proto_msgTypes[17]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1142,7 +1394,7 @@ func (x *GetActivityListRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetActivityListRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetActivityListRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetActivityListRequest) Descriptor() ([]byte, []int) {
|
func (*GetActivityListRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{13}
|
return file_activity_proto_rawDescGZIP(), []int{17}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetActivityListRequest) GetStarId() int64 {
|
func (x *GetActivityListRequest) GetStarId() int64 {
|
||||||
@ -1187,7 +1439,7 @@ type GetActivityListResponse struct {
|
|||||||
|
|
||||||
func (x *GetActivityListResponse) Reset() {
|
func (x *GetActivityListResponse) Reset() {
|
||||||
*x = GetActivityListResponse{}
|
*x = GetActivityListResponse{}
|
||||||
mi := &file_activity_proto_msgTypes[14]
|
mi := &file_activity_proto_msgTypes[18]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1199,7 +1451,7 @@ func (x *GetActivityListResponse) String() string {
|
|||||||
func (*GetActivityListResponse) ProtoMessage() {}
|
func (*GetActivityListResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetActivityListResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetActivityListResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[14]
|
mi := &file_activity_proto_msgTypes[18]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1212,7 +1464,7 @@ func (x *GetActivityListResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetActivityListResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetActivityListResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetActivityListResponse) Descriptor() ([]byte, []int) {
|
func (*GetActivityListResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{14}
|
return file_activity_proto_rawDescGZIP(), []int{18}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetActivityListResponse) GetBase() *common.BaseResponse {
|
func (x *GetActivityListResponse) GetBase() *common.BaseResponse {
|
||||||
@ -1260,7 +1512,7 @@ type GetProgressRequest struct {
|
|||||||
|
|
||||||
func (x *GetProgressRequest) Reset() {
|
func (x *GetProgressRequest) Reset() {
|
||||||
*x = GetProgressRequest{}
|
*x = GetProgressRequest{}
|
||||||
mi := &file_activity_proto_msgTypes[15]
|
mi := &file_activity_proto_msgTypes[19]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1272,7 +1524,7 @@ func (x *GetProgressRequest) String() string {
|
|||||||
func (*GetProgressRequest) ProtoMessage() {}
|
func (*GetProgressRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetProgressRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetProgressRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[15]
|
mi := &file_activity_proto_msgTypes[19]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1285,7 +1537,7 @@ func (x *GetProgressRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetProgressRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetProgressRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetProgressRequest) Descriptor() ([]byte, []int) {
|
func (*GetProgressRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{15}
|
return file_activity_proto_rawDescGZIP(), []int{19}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetProgressRequest) GetActivityId() int64 {
|
func (x *GetProgressRequest) GetActivityId() int64 {
|
||||||
@ -1311,7 +1563,7 @@ type GetProgressResponse struct {
|
|||||||
|
|
||||||
func (x *GetProgressResponse) Reset() {
|
func (x *GetProgressResponse) Reset() {
|
||||||
*x = GetProgressResponse{}
|
*x = GetProgressResponse{}
|
||||||
mi := &file_activity_proto_msgTypes[16]
|
mi := &file_activity_proto_msgTypes[20]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1323,7 +1575,7 @@ func (x *GetProgressResponse) String() string {
|
|||||||
func (*GetProgressResponse) ProtoMessage() {}
|
func (*GetProgressResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetProgressResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetProgressResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[16]
|
mi := &file_activity_proto_msgTypes[20]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1336,7 +1588,7 @@ func (x *GetProgressResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetProgressResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetProgressResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetProgressResponse) Descriptor() ([]byte, []int) {
|
func (*GetProgressResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{16}
|
return file_activity_proto_rawDescGZIP(), []int{20}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetProgressResponse) GetBase() *common.BaseResponse {
|
func (x *GetProgressResponse) GetBase() *common.BaseResponse {
|
||||||
@ -1407,7 +1659,7 @@ type MintingActivity struct {
|
|||||||
|
|
||||||
func (x *MintingActivity) Reset() {
|
func (x *MintingActivity) Reset() {
|
||||||
*x = MintingActivity{}
|
*x = MintingActivity{}
|
||||||
mi := &file_activity_proto_msgTypes[17]
|
mi := &file_activity_proto_msgTypes[21]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1419,7 +1671,7 @@ func (x *MintingActivity) String() string {
|
|||||||
func (*MintingActivity) ProtoMessage() {}
|
func (*MintingActivity) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *MintingActivity) ProtoReflect() protoreflect.Message {
|
func (x *MintingActivity) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[17]
|
mi := &file_activity_proto_msgTypes[21]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1432,7 +1684,7 @@ func (x *MintingActivity) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use MintingActivity.ProtoReflect.Descriptor instead.
|
// Deprecated: Use MintingActivity.ProtoReflect.Descriptor instead.
|
||||||
func (*MintingActivity) Descriptor() ([]byte, []int) {
|
func (*MintingActivity) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{17}
|
return file_activity_proto_rawDescGZIP(), []int{21}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *MintingActivity) GetId() int64 {
|
func (x *MintingActivity) GetId() int64 {
|
||||||
@ -1517,7 +1769,7 @@ type GetMintingActivitiesRequest struct {
|
|||||||
|
|
||||||
func (x *GetMintingActivitiesRequest) Reset() {
|
func (x *GetMintingActivitiesRequest) Reset() {
|
||||||
*x = GetMintingActivitiesRequest{}
|
*x = GetMintingActivitiesRequest{}
|
||||||
mi := &file_activity_proto_msgTypes[18]
|
mi := &file_activity_proto_msgTypes[22]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1529,7 +1781,7 @@ func (x *GetMintingActivitiesRequest) String() string {
|
|||||||
func (*GetMintingActivitiesRequest) ProtoMessage() {}
|
func (*GetMintingActivitiesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetMintingActivitiesRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetMintingActivitiesRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[18]
|
mi := &file_activity_proto_msgTypes[22]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1542,7 +1794,7 @@ func (x *GetMintingActivitiesRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetMintingActivitiesRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetMintingActivitiesRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetMintingActivitiesRequest) Descriptor() ([]byte, []int) {
|
func (*GetMintingActivitiesRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{18}
|
return file_activity_proto_rawDescGZIP(), []int{22}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetMintingActivitiesRequest) GetStarId() int64 {
|
func (x *GetMintingActivitiesRequest) GetStarId() int64 {
|
||||||
@ -1580,7 +1832,7 @@ type GetMintingActivitiesResponse struct {
|
|||||||
|
|
||||||
func (x *GetMintingActivitiesResponse) Reset() {
|
func (x *GetMintingActivitiesResponse) Reset() {
|
||||||
*x = GetMintingActivitiesResponse{}
|
*x = GetMintingActivitiesResponse{}
|
||||||
mi := &file_activity_proto_msgTypes[19]
|
mi := &file_activity_proto_msgTypes[23]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1592,7 +1844,7 @@ func (x *GetMintingActivitiesResponse) String() string {
|
|||||||
func (*GetMintingActivitiesResponse) ProtoMessage() {}
|
func (*GetMintingActivitiesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetMintingActivitiesResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetMintingActivitiesResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[19]
|
mi := &file_activity_proto_msgTypes[23]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1605,7 +1857,7 @@ func (x *GetMintingActivitiesResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetMintingActivitiesResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetMintingActivitiesResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetMintingActivitiesResponse) Descriptor() ([]byte, []int) {
|
func (*GetMintingActivitiesResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{19}
|
return file_activity_proto_rawDescGZIP(), []int{23}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetMintingActivitiesResponse) GetBase() *common.BaseResponse {
|
func (x *GetMintingActivitiesResponse) GetBase() *common.BaseResponse {
|
||||||
@ -1656,7 +1908,7 @@ type GetLatestContributionsRequest struct {
|
|||||||
|
|
||||||
func (x *GetLatestContributionsRequest) Reset() {
|
func (x *GetLatestContributionsRequest) Reset() {
|
||||||
*x = GetLatestContributionsRequest{}
|
*x = GetLatestContributionsRequest{}
|
||||||
mi := &file_activity_proto_msgTypes[20]
|
mi := &file_activity_proto_msgTypes[24]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1668,7 +1920,7 @@ func (x *GetLatestContributionsRequest) String() string {
|
|||||||
func (*GetLatestContributionsRequest) ProtoMessage() {}
|
func (*GetLatestContributionsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetLatestContributionsRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetLatestContributionsRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[20]
|
mi := &file_activity_proto_msgTypes[24]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1681,7 +1933,7 @@ func (x *GetLatestContributionsRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetLatestContributionsRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetLatestContributionsRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetLatestContributionsRequest) Descriptor() ([]byte, []int) {
|
func (*GetLatestContributionsRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{20}
|
return file_activity_proto_rawDescGZIP(), []int{24}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetLatestContributionsRequest) GetActivityId() int64 {
|
func (x *GetLatestContributionsRequest) GetActivityId() int64 {
|
||||||
@ -1733,7 +1985,7 @@ type ContributionRecord struct {
|
|||||||
|
|
||||||
func (x *ContributionRecord) Reset() {
|
func (x *ContributionRecord) Reset() {
|
||||||
*x = ContributionRecord{}
|
*x = ContributionRecord{}
|
||||||
mi := &file_activity_proto_msgTypes[21]
|
mi := &file_activity_proto_msgTypes[25]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1745,7 +1997,7 @@ func (x *ContributionRecord) String() string {
|
|||||||
func (*ContributionRecord) ProtoMessage() {}
|
func (*ContributionRecord) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ContributionRecord) ProtoReflect() protoreflect.Message {
|
func (x *ContributionRecord) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[21]
|
mi := &file_activity_proto_msgTypes[25]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1758,7 +2010,7 @@ func (x *ContributionRecord) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ContributionRecord.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ContributionRecord.ProtoReflect.Descriptor instead.
|
||||||
func (*ContributionRecord) Descriptor() ([]byte, []int) {
|
func (*ContributionRecord) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{21}
|
return file_activity_proto_rawDescGZIP(), []int{25}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ContributionRecord) GetId() int64 {
|
func (x *ContributionRecord) GetId() int64 {
|
||||||
@ -1856,7 +2108,7 @@ type GetLatestContributionsResponse struct {
|
|||||||
|
|
||||||
func (x *GetLatestContributionsResponse) Reset() {
|
func (x *GetLatestContributionsResponse) Reset() {
|
||||||
*x = GetLatestContributionsResponse{}
|
*x = GetLatestContributionsResponse{}
|
||||||
mi := &file_activity_proto_msgTypes[22]
|
mi := &file_activity_proto_msgTypes[26]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1868,7 +2120,7 @@ func (x *GetLatestContributionsResponse) String() string {
|
|||||||
func (*GetLatestContributionsResponse) ProtoMessage() {}
|
func (*GetLatestContributionsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetLatestContributionsResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetLatestContributionsResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[22]
|
mi := &file_activity_proto_msgTypes[26]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1881,7 +2133,7 @@ func (x *GetLatestContributionsResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetLatestContributionsResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetLatestContributionsResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetLatestContributionsResponse) Descriptor() ([]byte, []int) {
|
func (*GetLatestContributionsResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{22}
|
return file_activity_proto_rawDescGZIP(), []int{26}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetLatestContributionsResponse) GetBase() *common.BaseResponse {
|
func (x *GetLatestContributionsResponse) GetBase() *common.BaseResponse {
|
||||||
@ -1914,7 +2166,7 @@ type ActivityMessage struct {
|
|||||||
|
|
||||||
func (x *ActivityMessage) Reset() {
|
func (x *ActivityMessage) Reset() {
|
||||||
*x = ActivityMessage{}
|
*x = ActivityMessage{}
|
||||||
mi := &file_activity_proto_msgTypes[23]
|
mi := &file_activity_proto_msgTypes[27]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1926,7 +2178,7 @@ func (x *ActivityMessage) String() string {
|
|||||||
func (*ActivityMessage) ProtoMessage() {}
|
func (*ActivityMessage) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ActivityMessage) ProtoReflect() protoreflect.Message {
|
func (x *ActivityMessage) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[23]
|
mi := &file_activity_proto_msgTypes[27]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1939,7 +2191,7 @@ func (x *ActivityMessage) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ActivityMessage.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ActivityMessage.ProtoReflect.Descriptor instead.
|
||||||
func (*ActivityMessage) Descriptor() ([]byte, []int) {
|
func (*ActivityMessage) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{23}
|
return file_activity_proto_rawDescGZIP(), []int{27}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ActivityMessage) GetId() int64 {
|
func (x *ActivityMessage) GetId() int64 {
|
||||||
@ -2009,7 +2261,7 @@ type ListActivityMessagesRequest struct {
|
|||||||
|
|
||||||
func (x *ListActivityMessagesRequest) Reset() {
|
func (x *ListActivityMessagesRequest) Reset() {
|
||||||
*x = ListActivityMessagesRequest{}
|
*x = ListActivityMessagesRequest{}
|
||||||
mi := &file_activity_proto_msgTypes[24]
|
mi := &file_activity_proto_msgTypes[28]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -2021,7 +2273,7 @@ func (x *ListActivityMessagesRequest) String() string {
|
|||||||
func (*ListActivityMessagesRequest) ProtoMessage() {}
|
func (*ListActivityMessagesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListActivityMessagesRequest) ProtoReflect() protoreflect.Message {
|
func (x *ListActivityMessagesRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[24]
|
mi := &file_activity_proto_msgTypes[28]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -2034,7 +2286,7 @@ func (x *ListActivityMessagesRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ListActivityMessagesRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListActivityMessagesRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*ListActivityMessagesRequest) Descriptor() ([]byte, []int) {
|
func (*ListActivityMessagesRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{24}
|
return file_activity_proto_rawDescGZIP(), []int{28}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListActivityMessagesRequest) GetActivityId() int64 {
|
func (x *ListActivityMessagesRequest) GetActivityId() int64 {
|
||||||
@ -2071,7 +2323,7 @@ type ListActivityMessagesResponse struct {
|
|||||||
|
|
||||||
func (x *ListActivityMessagesResponse) Reset() {
|
func (x *ListActivityMessagesResponse) Reset() {
|
||||||
*x = ListActivityMessagesResponse{}
|
*x = ListActivityMessagesResponse{}
|
||||||
mi := &file_activity_proto_msgTypes[25]
|
mi := &file_activity_proto_msgTypes[29]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -2083,7 +2335,7 @@ func (x *ListActivityMessagesResponse) String() string {
|
|||||||
func (*ListActivityMessagesResponse) ProtoMessage() {}
|
func (*ListActivityMessagesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListActivityMessagesResponse) ProtoReflect() protoreflect.Message {
|
func (x *ListActivityMessagesResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[25]
|
mi := &file_activity_proto_msgTypes[29]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -2096,7 +2348,7 @@ func (x *ListActivityMessagesResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ListActivityMessagesResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListActivityMessagesResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*ListActivityMessagesResponse) Descriptor() ([]byte, []int) {
|
func (*ListActivityMessagesResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{25}
|
return file_activity_proto_rawDescGZIP(), []int{29}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListActivityMessagesResponse) GetBase() *common.BaseResponse {
|
func (x *ListActivityMessagesResponse) GetBase() *common.BaseResponse {
|
||||||
@ -2146,7 +2398,7 @@ type CreateActivityMessageRequest struct {
|
|||||||
|
|
||||||
func (x *CreateActivityMessageRequest) Reset() {
|
func (x *CreateActivityMessageRequest) Reset() {
|
||||||
*x = CreateActivityMessageRequest{}
|
*x = CreateActivityMessageRequest{}
|
||||||
mi := &file_activity_proto_msgTypes[26]
|
mi := &file_activity_proto_msgTypes[30]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -2158,7 +2410,7 @@ func (x *CreateActivityMessageRequest) String() string {
|
|||||||
func (*CreateActivityMessageRequest) ProtoMessage() {}
|
func (*CreateActivityMessageRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CreateActivityMessageRequest) ProtoReflect() protoreflect.Message {
|
func (x *CreateActivityMessageRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[26]
|
mi := &file_activity_proto_msgTypes[30]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -2171,7 +2423,7 @@ func (x *CreateActivityMessageRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CreateActivityMessageRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateActivityMessageRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateActivityMessageRequest) Descriptor() ([]byte, []int) {
|
func (*CreateActivityMessageRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{26}
|
return file_activity_proto_rawDescGZIP(), []int{30}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateActivityMessageRequest) GetActivityId() int64 {
|
func (x *CreateActivityMessageRequest) GetActivityId() int64 {
|
||||||
@ -2212,7 +2464,7 @@ type CreateActivityMessageResponse struct {
|
|||||||
|
|
||||||
func (x *CreateActivityMessageResponse) Reset() {
|
func (x *CreateActivityMessageResponse) Reset() {
|
||||||
*x = CreateActivityMessageResponse{}
|
*x = CreateActivityMessageResponse{}
|
||||||
mi := &file_activity_proto_msgTypes[27]
|
mi := &file_activity_proto_msgTypes[31]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -2224,7 +2476,7 @@ func (x *CreateActivityMessageResponse) String() string {
|
|||||||
func (*CreateActivityMessageResponse) ProtoMessage() {}
|
func (*CreateActivityMessageResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CreateActivityMessageResponse) ProtoReflect() protoreflect.Message {
|
func (x *CreateActivityMessageResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_activity_proto_msgTypes[27]
|
mi := &file_activity_proto_msgTypes[31]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -2237,7 +2489,7 @@ func (x *CreateActivityMessageResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CreateActivityMessageResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateActivityMessageResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateActivityMessageResponse) Descriptor() ([]byte, []int) {
|
func (*CreateActivityMessageResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_activity_proto_rawDescGZIP(), []int{27}
|
return file_activity_proto_rawDescGZIP(), []int{31}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateActivityMessageResponse) GetBase() *common.BaseResponse {
|
func (x *CreateActivityMessageResponse) GetBase() *common.BaseResponse {
|
||||||
@ -2355,7 +2607,27 @@ const file_activity_proto_rawDesc = "" +
|
|||||||
"\x06status\x18\x04 \x01(\tR\x06status\x12\x1a\n" +
|
"\x06status\x18\x04 \x01(\tR\x06status\x12\x1a\n" +
|
||||||
"\bnickname\x18\x05 \x01(\tR\bnickname\x12\x1d\n" +
|
"\bnickname\x18\x05 \x01(\tR\bnickname\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"avatar_url\x18\x06 \x01(\tR\tavatarUrl\"z\n" +
|
"avatar_url\x18\x06 \x01(\tR\tavatarUrl\"f\n" +
|
||||||
|
"\x11TopRankingRequest\x12\x1f\n" +
|
||||||
|
"\vactivity_id\x18\x01 \x01(\x03R\n" +
|
||||||
|
"activityId\x12\x17\n" +
|
||||||
|
"\astar_id\x18\x02 \x01(\x03R\x06starId\x12\x17\n" +
|
||||||
|
"\auser_id\x18\x03 \x01(\x03R\x06userId\"\\\n" +
|
||||||
|
"\x0eTopRankingItem\x12\x12\n" +
|
||||||
|
"\x04rank\x18\x01 \x01(\x05R\x04rank\x12\x17\n" +
|
||||||
|
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"avatar_url\x18\x03 \x01(\tR\tavatarUrl\"}\n" +
|
||||||
|
"\x10MyTopRankingInfo\x12\x12\n" +
|
||||||
|
"\x04rank\x18\x01 \x01(\x05R\x04rank\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"avatar_url\x18\x02 \x01(\tR\tavatarUrl\x12\x1e\n" +
|
||||||
|
"\vgap_to_prev\x18\x03 \x01(\x03R\tgapToPrev\x12\x16\n" +
|
||||||
|
"\x06status\x18\x04 \x01(\tR\x06status\"\xb9\x01\n" +
|
||||||
|
"\x12TopRankingResponse\x120\n" +
|
||||||
|
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x124\n" +
|
||||||
|
"\x04top3\x18\x02 \x03(\v2 .topfans.activity.TopRankingItemR\x04top3\x12;\n" +
|
||||||
|
"\amy_info\x18\x03 \x01(\v2\".topfans.activity.MyTopRankingInfoR\x06myInfo\"z\n" +
|
||||||
"\x16GetActivityListRequest\x12\x17\n" +
|
"\x16GetActivityListRequest\x12\x17\n" +
|
||||||
"\astar_id\x18\x01 \x01(\x03R\x06starId\x12\x16\n" +
|
"\astar_id\x18\x01 \x01(\x03R\x06starId\x12\x16\n" +
|
||||||
"\x06status\x18\x02 \x01(\tR\x06status\x12\x12\n" +
|
"\x06status\x18\x02 \x01(\tR\x06status\x12\x12\n" +
|
||||||
@ -2465,7 +2737,7 @@ const file_activity_proto_rawDesc = "" +
|
|||||||
"\acontent\x18\x04 \x01(\tR\acontent\"\x8e\x01\n" +
|
"\acontent\x18\x04 \x01(\tR\acontent\"\x8e\x01\n" +
|
||||||
"\x1dCreateActivityMessageResponse\x120\n" +
|
"\x1dCreateActivityMessageResponse\x120\n" +
|
||||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12;\n" +
|
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12;\n" +
|
||||||
"\amessage\x18\x02 \x01(\v2!.topfans.activity.ActivityMessageR\amessage2\xd5\r\n" +
|
"\amessage\x18\x02 \x01(\v2!.topfans.activity.ActivityMessageR\amessage2\xe8\x0e\n" +
|
||||||
"\x0fActivityService\x12\x82\x01\n" +
|
"\x0fActivityService\x12\x82\x01\n" +
|
||||||
"\x0fGetActivityList\x12(.topfans.activity.GetActivityListRequest\x1a).topfans.activity.GetActivityListResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/activities\x12y\n" +
|
"\x0fGetActivityList\x12(.topfans.activity.GetActivityListRequest\x1a).topfans.activity.GetActivityListResponse\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/activities\x12y\n" +
|
||||||
"\vGetActivity\x12$.topfans.activity.GetProgressRequest\x1a\x1a.topfans.activity.Activity\"(\x82\xd3\xe4\x93\x02\"\x12 /api/v1/activities/{activity_id}\x12\x91\x01\n" +
|
"\vGetActivity\x12$.topfans.activity.GetProgressRequest\x1a\x1a.topfans.activity.Activity\"(\x82\xd3\xe4\x93\x02\"\x12 /api/v1/activities/{activity_id}\x12\x91\x01\n" +
|
||||||
@ -2473,7 +2745,8 @@ const file_activity_proto_rawDesc = "" +
|
|||||||
"\vGetProgress\x12$.topfans.activity.GetProgressRequest\x1a%.topfans.activity.GetProgressResponse\"1\x82\xd3\xe4\x93\x02+\x12)/api/v1/activities/{activity_id}/progress\x12\x93\x01\n" +
|
"\vGetProgress\x12$.topfans.activity.GetProgressRequest\x1a%.topfans.activity.GetProgressResponse\"1\x82\xd3\xe4\x93\x02+\x12)/api/v1/activities/{activity_id}/progress\x12\x93\x01\n" +
|
||||||
"\fPurchaseItem\x12%.topfans.activity.PurchaseItemRequest\x1a&.topfans.activity.PurchaseItemResponse\"4\x82\xd3\xe4\x93\x02.:\x01*\")/api/v1/activities/{activity_id}/purchase\x12\xa8\x01\n" +
|
"\fPurchaseItem\x12%.topfans.activity.PurchaseItemRequest\x1a&.topfans.activity.PurchaseItemResponse\"4\x82\xd3\xe4\x93\x02.:\x01*\")/api/v1/activities/{activity_id}/purchase\x12\xa8\x01\n" +
|
||||||
"\x11BatchPurchaseItem\x12*.topfans.activity.BatchPurchaseItemRequest\x1a+.topfans.activity.BatchPurchaseItemResponse\":\x82\xd3\xe4\x93\x024:\x01*\"//api/v1/activities/{activity_id}/batch-purchase\x12\xa7\x01\n" +
|
"\x11BatchPurchaseItem\x12*.topfans.activity.BatchPurchaseItemRequest\x1a+.topfans.activity.BatchPurchaseItemResponse\":\x82\xd3\xe4\x93\x024:\x01*\"//api/v1/activities/{activity_id}/batch-purchase\x12\xa7\x01\n" +
|
||||||
"\x16GetContributionRanking\x12,.topfans.activity.ContributionRankingRequest\x1a-.topfans.activity.ContributionRankingResponse\"0\x82\xd3\xe4\x93\x02*\x12(/api/v1/activities/{activity_id}/ranking\x12\x99\x01\n" +
|
"\x16GetContributionRanking\x12,.topfans.activity.ContributionRankingRequest\x1a-.topfans.activity.ContributionRankingResponse\"0\x82\xd3\xe4\x93\x02*\x12(/api/v1/activities/{activity_id}/ranking\x12\x90\x01\n" +
|
||||||
|
"\rGetTopRanking\x12#.topfans.activity.TopRankingRequest\x1a$.topfans.activity.TopRankingResponse\"4\x82\xd3\xe4\x93\x02.\x12,/api/v1/activities/{activity_id}/top-ranking\x12\x99\x01\n" +
|
||||||
"\x14GetMintingActivities\x12-.topfans.activity.GetMintingActivitiesRequest\x1a..topfans.activity.GetMintingActivitiesResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/api/v1/minting-activities\x12\xba\x01\n" +
|
"\x14GetMintingActivities\x12-.topfans.activity.GetMintingActivitiesRequest\x1a..topfans.activity.GetMintingActivitiesResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x1a/api/v1/minting-activities\x12\xba\x01\n" +
|
||||||
"\x16GetLatestContributions\x12/.topfans.activity.GetLatestContributionsRequest\x1a0.topfans.activity.GetLatestContributionsResponse\"=\x82\xd3\xe4\x93\x027\x125/api/v1/activities/{activity_id}/contributions/latest\x12\xa8\x01\n" +
|
"\x16GetLatestContributions\x12/.topfans.activity.GetLatestContributionsRequest\x1a0.topfans.activity.GetLatestContributionsResponse\"=\x82\xd3\xe4\x93\x027\x125/api/v1/activities/{activity_id}/contributions/latest\x12\xa8\x01\n" +
|
||||||
"\x14ListActivityMessages\x12-.topfans.activity.ListActivityMessagesRequest\x1a..topfans.activity.ListActivityMessagesResponse\"1\x82\xd3\xe4\x93\x02+\x12)/api/v1/activities/{activity_id}/messages\x12\xae\x01\n" +
|
"\x14ListActivityMessages\x12-.topfans.activity.ListActivityMessagesRequest\x1a..topfans.activity.ListActivityMessagesResponse\"1\x82\xd3\xe4\x93\x02+\x12)/api/v1/activities/{activity_id}/messages\x12\xae\x01\n" +
|
||||||
@ -2491,7 +2764,7 @@ func file_activity_proto_rawDescGZIP() []byte {
|
|||||||
return file_activity_proto_rawDescData
|
return file_activity_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
|
var file_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
|
||||||
var file_activity_proto_goTypes = []any{
|
var file_activity_proto_goTypes = []any{
|
||||||
(*Activity)(nil), // 0: topfans.activity.Activity
|
(*Activity)(nil), // 0: topfans.activity.Activity
|
||||||
(*ActivityItem)(nil), // 1: topfans.activity.ActivityItem
|
(*ActivityItem)(nil), // 1: topfans.activity.ActivityItem
|
||||||
@ -2506,71 +2779,80 @@ var file_activity_proto_goTypes = []any{
|
|||||||
(*ContributionRankingItem)(nil), // 10: topfans.activity.ContributionRankingItem
|
(*ContributionRankingItem)(nil), // 10: topfans.activity.ContributionRankingItem
|
||||||
(*ContributionRankingResponse)(nil), // 11: topfans.activity.ContributionRankingResponse
|
(*ContributionRankingResponse)(nil), // 11: topfans.activity.ContributionRankingResponse
|
||||||
(*MyContribution)(nil), // 12: topfans.activity.MyContribution
|
(*MyContribution)(nil), // 12: topfans.activity.MyContribution
|
||||||
(*GetActivityListRequest)(nil), // 13: topfans.activity.GetActivityListRequest
|
(*TopRankingRequest)(nil), // 13: topfans.activity.TopRankingRequest
|
||||||
(*GetActivityListResponse)(nil), // 14: topfans.activity.GetActivityListResponse
|
(*TopRankingItem)(nil), // 14: topfans.activity.TopRankingItem
|
||||||
(*GetProgressRequest)(nil), // 15: topfans.activity.GetProgressRequest
|
(*MyTopRankingInfo)(nil), // 15: topfans.activity.MyTopRankingInfo
|
||||||
(*GetProgressResponse)(nil), // 16: topfans.activity.GetProgressResponse
|
(*TopRankingResponse)(nil), // 16: topfans.activity.TopRankingResponse
|
||||||
(*MintingActivity)(nil), // 17: topfans.activity.MintingActivity
|
(*GetActivityListRequest)(nil), // 17: topfans.activity.GetActivityListRequest
|
||||||
(*GetMintingActivitiesRequest)(nil), // 18: topfans.activity.GetMintingActivitiesRequest
|
(*GetActivityListResponse)(nil), // 18: topfans.activity.GetActivityListResponse
|
||||||
(*GetMintingActivitiesResponse)(nil), // 19: topfans.activity.GetMintingActivitiesResponse
|
(*GetProgressRequest)(nil), // 19: topfans.activity.GetProgressRequest
|
||||||
(*GetLatestContributionsRequest)(nil), // 20: topfans.activity.GetLatestContributionsRequest
|
(*GetProgressResponse)(nil), // 20: topfans.activity.GetProgressResponse
|
||||||
(*ContributionRecord)(nil), // 21: topfans.activity.ContributionRecord
|
(*MintingActivity)(nil), // 21: topfans.activity.MintingActivity
|
||||||
(*GetLatestContributionsResponse)(nil), // 22: topfans.activity.GetLatestContributionsResponse
|
(*GetMintingActivitiesRequest)(nil), // 22: topfans.activity.GetMintingActivitiesRequest
|
||||||
(*ActivityMessage)(nil), // 23: topfans.activity.ActivityMessage
|
(*GetMintingActivitiesResponse)(nil), // 23: topfans.activity.GetMintingActivitiesResponse
|
||||||
(*ListActivityMessagesRequest)(nil), // 24: topfans.activity.ListActivityMessagesRequest
|
(*GetLatestContributionsRequest)(nil), // 24: topfans.activity.GetLatestContributionsRequest
|
||||||
(*ListActivityMessagesResponse)(nil), // 25: topfans.activity.ListActivityMessagesResponse
|
(*ContributionRecord)(nil), // 25: topfans.activity.ContributionRecord
|
||||||
(*CreateActivityMessageRequest)(nil), // 26: topfans.activity.CreateActivityMessageRequest
|
(*GetLatestContributionsResponse)(nil), // 26: topfans.activity.GetLatestContributionsResponse
|
||||||
(*CreateActivityMessageResponse)(nil), // 27: topfans.activity.CreateActivityMessageResponse
|
(*ActivityMessage)(nil), // 27: topfans.activity.ActivityMessage
|
||||||
(*common.BaseResponse)(nil), // 28: topfans.common.BaseResponse
|
(*ListActivityMessagesRequest)(nil), // 28: topfans.activity.ListActivityMessagesRequest
|
||||||
|
(*ListActivityMessagesResponse)(nil), // 29: topfans.activity.ListActivityMessagesResponse
|
||||||
|
(*CreateActivityMessageRequest)(nil), // 30: topfans.activity.CreateActivityMessageRequest
|
||||||
|
(*CreateActivityMessageResponse)(nil), // 31: topfans.activity.CreateActivityMessageResponse
|
||||||
|
(*common.BaseResponse)(nil), // 32: topfans.common.BaseResponse
|
||||||
}
|
}
|
||||||
var file_activity_proto_depIdxs = []int32{
|
var file_activity_proto_depIdxs = []int32{
|
||||||
1, // 0: topfans.activity.Activity.items:type_name -> topfans.activity.ActivityItem
|
1, // 0: topfans.activity.Activity.items:type_name -> topfans.activity.ActivityItem
|
||||||
1, // 1: topfans.activity.ActivityItemsResponse.items:type_name -> topfans.activity.ActivityItem
|
1, // 1: topfans.activity.ActivityItemsResponse.items:type_name -> topfans.activity.ActivityItem
|
||||||
28, // 2: topfans.activity.PurchaseItemResponse.base:type_name -> topfans.common.BaseResponse
|
32, // 2: topfans.activity.PurchaseItemResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
5, // 3: topfans.activity.BatchPurchaseItemRequest.items:type_name -> topfans.activity.PurchaseItem
|
5, // 3: topfans.activity.BatchPurchaseItemRequest.items:type_name -> topfans.activity.PurchaseItem
|
||||||
28, // 4: topfans.activity.BatchPurchaseItemResponse.base:type_name -> topfans.common.BaseResponse
|
32, // 4: topfans.activity.BatchPurchaseItemResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
8, // 5: topfans.activity.BatchPurchaseItemResponse.fails:type_name -> topfans.activity.PurchaseFailItem
|
8, // 5: topfans.activity.BatchPurchaseItemResponse.fails:type_name -> topfans.activity.PurchaseFailItem
|
||||||
28, // 6: topfans.activity.ContributionRankingResponse.base:type_name -> topfans.common.BaseResponse
|
32, // 6: topfans.activity.ContributionRankingResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
10, // 7: topfans.activity.ContributionRankingResponse.items:type_name -> topfans.activity.ContributionRankingItem
|
10, // 7: topfans.activity.ContributionRankingResponse.items:type_name -> topfans.activity.ContributionRankingItem
|
||||||
12, // 8: topfans.activity.ContributionRankingResponse.my_contribution:type_name -> topfans.activity.MyContribution
|
12, // 8: topfans.activity.ContributionRankingResponse.my_contribution:type_name -> topfans.activity.MyContribution
|
||||||
28, // 9: topfans.activity.GetActivityListResponse.base:type_name -> topfans.common.BaseResponse
|
32, // 9: topfans.activity.TopRankingResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
0, // 10: topfans.activity.GetActivityListResponse.activities:type_name -> topfans.activity.Activity
|
14, // 10: topfans.activity.TopRankingResponse.top3:type_name -> topfans.activity.TopRankingItem
|
||||||
28, // 11: topfans.activity.GetProgressResponse.base:type_name -> topfans.common.BaseResponse
|
15, // 11: topfans.activity.TopRankingResponse.my_info:type_name -> topfans.activity.MyTopRankingInfo
|
||||||
28, // 12: topfans.activity.GetMintingActivitiesResponse.base:type_name -> topfans.common.BaseResponse
|
32, // 12: topfans.activity.GetActivityListResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
17, // 13: topfans.activity.GetMintingActivitiesResponse.activities:type_name -> topfans.activity.MintingActivity
|
0, // 13: topfans.activity.GetActivityListResponse.activities:type_name -> topfans.activity.Activity
|
||||||
28, // 14: topfans.activity.GetLatestContributionsResponse.base:type_name -> topfans.common.BaseResponse
|
32, // 14: topfans.activity.GetProgressResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
21, // 15: topfans.activity.GetLatestContributionsResponse.records:type_name -> topfans.activity.ContributionRecord
|
32, // 15: topfans.activity.GetMintingActivitiesResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
28, // 16: topfans.activity.ListActivityMessagesResponse.base:type_name -> topfans.common.BaseResponse
|
21, // 16: topfans.activity.GetMintingActivitiesResponse.activities:type_name -> topfans.activity.MintingActivity
|
||||||
23, // 17: topfans.activity.ListActivityMessagesResponse.messages:type_name -> topfans.activity.ActivityMessage
|
32, // 17: topfans.activity.GetLatestContributionsResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
28, // 18: topfans.activity.CreateActivityMessageResponse.base:type_name -> topfans.common.BaseResponse
|
25, // 18: topfans.activity.GetLatestContributionsResponse.records:type_name -> topfans.activity.ContributionRecord
|
||||||
23, // 19: topfans.activity.CreateActivityMessageResponse.message:type_name -> topfans.activity.ActivityMessage
|
32, // 19: topfans.activity.ListActivityMessagesResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
13, // 20: topfans.activity.ActivityService.GetActivityList:input_type -> topfans.activity.GetActivityListRequest
|
27, // 20: topfans.activity.ListActivityMessagesResponse.messages:type_name -> topfans.activity.ActivityMessage
|
||||||
15, // 21: topfans.activity.ActivityService.GetActivity:input_type -> topfans.activity.GetProgressRequest
|
32, // 21: topfans.activity.CreateActivityMessageResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
15, // 22: topfans.activity.ActivityService.GetActivityItems:input_type -> topfans.activity.GetProgressRequest
|
27, // 22: topfans.activity.CreateActivityMessageResponse.message:type_name -> topfans.activity.ActivityMessage
|
||||||
15, // 23: topfans.activity.ActivityService.GetProgress:input_type -> topfans.activity.GetProgressRequest
|
17, // 23: topfans.activity.ActivityService.GetActivityList:input_type -> topfans.activity.GetActivityListRequest
|
||||||
3, // 24: topfans.activity.ActivityService.PurchaseItem:input_type -> topfans.activity.PurchaseItemRequest
|
19, // 24: topfans.activity.ActivityService.GetActivity:input_type -> topfans.activity.GetProgressRequest
|
||||||
6, // 25: topfans.activity.ActivityService.BatchPurchaseItem:input_type -> topfans.activity.BatchPurchaseItemRequest
|
19, // 25: topfans.activity.ActivityService.GetActivityItems:input_type -> topfans.activity.GetProgressRequest
|
||||||
9, // 26: topfans.activity.ActivityService.GetContributionRanking:input_type -> topfans.activity.ContributionRankingRequest
|
19, // 26: topfans.activity.ActivityService.GetProgress:input_type -> topfans.activity.GetProgressRequest
|
||||||
18, // 27: topfans.activity.ActivityService.GetMintingActivities:input_type -> topfans.activity.GetMintingActivitiesRequest
|
3, // 27: topfans.activity.ActivityService.PurchaseItem:input_type -> topfans.activity.PurchaseItemRequest
|
||||||
20, // 28: topfans.activity.ActivityService.GetLatestContributions:input_type -> topfans.activity.GetLatestContributionsRequest
|
6, // 28: topfans.activity.ActivityService.BatchPurchaseItem:input_type -> topfans.activity.BatchPurchaseItemRequest
|
||||||
24, // 29: topfans.activity.ActivityService.ListActivityMessages:input_type -> topfans.activity.ListActivityMessagesRequest
|
9, // 29: topfans.activity.ActivityService.GetContributionRanking:input_type -> topfans.activity.ContributionRankingRequest
|
||||||
26, // 30: topfans.activity.ActivityService.CreateActivityMessage:input_type -> topfans.activity.CreateActivityMessageRequest
|
13, // 30: topfans.activity.ActivityService.GetTopRanking:input_type -> topfans.activity.TopRankingRequest
|
||||||
14, // 31: topfans.activity.ActivityService.GetActivityList:output_type -> topfans.activity.GetActivityListResponse
|
22, // 31: topfans.activity.ActivityService.GetMintingActivities:input_type -> topfans.activity.GetMintingActivitiesRequest
|
||||||
0, // 32: topfans.activity.ActivityService.GetActivity:output_type -> topfans.activity.Activity
|
24, // 32: topfans.activity.ActivityService.GetLatestContributions:input_type -> topfans.activity.GetLatestContributionsRequest
|
||||||
2, // 33: topfans.activity.ActivityService.GetActivityItems:output_type -> topfans.activity.ActivityItemsResponse
|
28, // 33: topfans.activity.ActivityService.ListActivityMessages:input_type -> topfans.activity.ListActivityMessagesRequest
|
||||||
16, // 34: topfans.activity.ActivityService.GetProgress:output_type -> topfans.activity.GetProgressResponse
|
30, // 34: topfans.activity.ActivityService.CreateActivityMessage:input_type -> topfans.activity.CreateActivityMessageRequest
|
||||||
4, // 35: topfans.activity.ActivityService.PurchaseItem:output_type -> topfans.activity.PurchaseItemResponse
|
18, // 35: topfans.activity.ActivityService.GetActivityList:output_type -> topfans.activity.GetActivityListResponse
|
||||||
7, // 36: topfans.activity.ActivityService.BatchPurchaseItem:output_type -> topfans.activity.BatchPurchaseItemResponse
|
0, // 36: topfans.activity.ActivityService.GetActivity:output_type -> topfans.activity.Activity
|
||||||
11, // 37: topfans.activity.ActivityService.GetContributionRanking:output_type -> topfans.activity.ContributionRankingResponse
|
2, // 37: topfans.activity.ActivityService.GetActivityItems:output_type -> topfans.activity.ActivityItemsResponse
|
||||||
19, // 38: topfans.activity.ActivityService.GetMintingActivities:output_type -> topfans.activity.GetMintingActivitiesResponse
|
20, // 38: topfans.activity.ActivityService.GetProgress:output_type -> topfans.activity.GetProgressResponse
|
||||||
22, // 39: topfans.activity.ActivityService.GetLatestContributions:output_type -> topfans.activity.GetLatestContributionsResponse
|
4, // 39: topfans.activity.ActivityService.PurchaseItem:output_type -> topfans.activity.PurchaseItemResponse
|
||||||
25, // 40: topfans.activity.ActivityService.ListActivityMessages:output_type -> topfans.activity.ListActivityMessagesResponse
|
7, // 40: topfans.activity.ActivityService.BatchPurchaseItem:output_type -> topfans.activity.BatchPurchaseItemResponse
|
||||||
27, // 41: topfans.activity.ActivityService.CreateActivityMessage:output_type -> topfans.activity.CreateActivityMessageResponse
|
11, // 41: topfans.activity.ActivityService.GetContributionRanking:output_type -> topfans.activity.ContributionRankingResponse
|
||||||
31, // [31:42] is the sub-list for method output_type
|
16, // 42: topfans.activity.ActivityService.GetTopRanking:output_type -> topfans.activity.TopRankingResponse
|
||||||
20, // [20:31] is the sub-list for method input_type
|
23, // 43: topfans.activity.ActivityService.GetMintingActivities:output_type -> topfans.activity.GetMintingActivitiesResponse
|
||||||
20, // [20:20] is the sub-list for extension type_name
|
26, // 44: topfans.activity.ActivityService.GetLatestContributions:output_type -> topfans.activity.GetLatestContributionsResponse
|
||||||
20, // [20:20] is the sub-list for extension extendee
|
29, // 45: topfans.activity.ActivityService.ListActivityMessages:output_type -> topfans.activity.ListActivityMessagesResponse
|
||||||
0, // [0:20] is the sub-list for field type_name
|
31, // 46: topfans.activity.ActivityService.CreateActivityMessage:output_type -> topfans.activity.CreateActivityMessageResponse
|
||||||
|
35, // [35:47] is the sub-list for method output_type
|
||||||
|
23, // [23:35] is the sub-list for method input_type
|
||||||
|
23, // [23:23] is the sub-list for extension type_name
|
||||||
|
23, // [23:23] is the sub-list for extension extendee
|
||||||
|
0, // [0:23] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_activity_proto_init() }
|
func init() { file_activity_proto_init() }
|
||||||
@ -2584,7 +2866,7 @@ func file_activity_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_activity_proto_rawDesc), len(file_activity_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_activity_proto_rawDesc), len(file_activity_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 28,
|
NumMessages: 32,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -50,6 +50,8 @@ const (
|
|||||||
ActivityServiceBatchPurchaseItemProcedure = "/topfans.activity.ActivityService/BatchPurchaseItem"
|
ActivityServiceBatchPurchaseItemProcedure = "/topfans.activity.ActivityService/BatchPurchaseItem"
|
||||||
// ActivityServiceGetContributionRankingProcedure is the fully-qualified name of the ActivityService's GetContributionRanking RPC.
|
// ActivityServiceGetContributionRankingProcedure is the fully-qualified name of the ActivityService's GetContributionRanking RPC.
|
||||||
ActivityServiceGetContributionRankingProcedure = "/topfans.activity.ActivityService/GetContributionRanking"
|
ActivityServiceGetContributionRankingProcedure = "/topfans.activity.ActivityService/GetContributionRanking"
|
||||||
|
// ActivityServiceGetTopRankingProcedure is the fully-qualified name of the ActivityService's GetTopRanking RPC.
|
||||||
|
ActivityServiceGetTopRankingProcedure = "/topfans.activity.ActivityService/GetTopRanking"
|
||||||
// ActivityServiceGetMintingActivitiesProcedure is the fully-qualified name of the ActivityService's GetMintingActivities RPC.
|
// ActivityServiceGetMintingActivitiesProcedure is the fully-qualified name of the ActivityService's GetMintingActivities RPC.
|
||||||
ActivityServiceGetMintingActivitiesProcedure = "/topfans.activity.ActivityService/GetMintingActivities"
|
ActivityServiceGetMintingActivitiesProcedure = "/topfans.activity.ActivityService/GetMintingActivities"
|
||||||
// ActivityServiceGetLatestContributionsProcedure is the fully-qualified name of the ActivityService's GetLatestContributions RPC.
|
// ActivityServiceGetLatestContributionsProcedure is the fully-qualified name of the ActivityService's GetLatestContributions RPC.
|
||||||
@ -73,6 +75,7 @@ type ActivityService interface {
|
|||||||
PurchaseItem(ctx context.Context, req *PurchaseItemRequest, opts ...client.CallOption) (*PurchaseItemResponse, error)
|
PurchaseItem(ctx context.Context, req *PurchaseItemRequest, opts ...client.CallOption) (*PurchaseItemResponse, error)
|
||||||
BatchPurchaseItem(ctx context.Context, req *BatchPurchaseItemRequest, opts ...client.CallOption) (*BatchPurchaseItemResponse, error)
|
BatchPurchaseItem(ctx context.Context, req *BatchPurchaseItemRequest, opts ...client.CallOption) (*BatchPurchaseItemResponse, error)
|
||||||
GetContributionRanking(ctx context.Context, req *ContributionRankingRequest, opts ...client.CallOption) (*ContributionRankingResponse, error)
|
GetContributionRanking(ctx context.Context, req *ContributionRankingRequest, opts ...client.CallOption) (*ContributionRankingResponse, error)
|
||||||
|
GetTopRanking(ctx context.Context, req *TopRankingRequest, opts ...client.CallOption) (*TopRankingResponse, error)
|
||||||
GetMintingActivities(ctx context.Context, req *GetMintingActivitiesRequest, opts ...client.CallOption) (*GetMintingActivitiesResponse, error)
|
GetMintingActivities(ctx context.Context, req *GetMintingActivitiesRequest, opts ...client.CallOption) (*GetMintingActivitiesResponse, error)
|
||||||
GetLatestContributions(ctx context.Context, req *GetLatestContributionsRequest, opts ...client.CallOption) (*GetLatestContributionsResponse, error)
|
GetLatestContributions(ctx context.Context, req *GetLatestContributionsRequest, opts ...client.CallOption) (*GetLatestContributionsResponse, error)
|
||||||
ListActivityMessages(ctx context.Context, req *ListActivityMessagesRequest, opts ...client.CallOption) (*ListActivityMessagesResponse, error)
|
ListActivityMessages(ctx context.Context, req *ListActivityMessagesRequest, opts ...client.CallOption) (*ListActivityMessagesResponse, error)
|
||||||
@ -155,6 +158,14 @@ func (c *ActivityServiceImpl) GetContributionRanking(ctx context.Context, req *C
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ActivityServiceImpl) GetTopRanking(ctx context.Context, req *TopRankingRequest, opts ...client.CallOption) (*TopRankingResponse, error) {
|
||||||
|
resp := new(TopRankingResponse)
|
||||||
|
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetTopRanking", opts...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ActivityServiceImpl) GetMintingActivities(ctx context.Context, req *GetMintingActivitiesRequest, opts ...client.CallOption) (*GetMintingActivitiesResponse, error) {
|
func (c *ActivityServiceImpl) GetMintingActivities(ctx context.Context, req *GetMintingActivitiesRequest, opts ...client.CallOption) (*GetMintingActivitiesResponse, error) {
|
||||||
resp := new(GetMintingActivitiesResponse)
|
resp := new(GetMintingActivitiesResponse)
|
||||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMintingActivities", opts...); err != nil {
|
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMintingActivities", opts...); err != nil {
|
||||||
@ -189,7 +200,7 @@ func (c *ActivityServiceImpl) CreateActivityMessage(ctx context.Context, req *Cr
|
|||||||
|
|
||||||
var ActivityService_ClientInfo = client.ClientInfo{
|
var ActivityService_ClientInfo = client.ClientInfo{
|
||||||
InterfaceName: "topfans.activity.ActivityService",
|
InterfaceName: "topfans.activity.ActivityService",
|
||||||
MethodNames: []string{"GetActivityList", "GetActivity", "GetActivityItems", "GetProgress", "PurchaseItem", "BatchPurchaseItem", "GetContributionRanking", "GetMintingActivities", "GetLatestContributions", "ListActivityMessages", "CreateActivityMessage"},
|
MethodNames: []string{"GetActivityList", "GetActivity", "GetActivityItems", "GetProgress", "PurchaseItem", "BatchPurchaseItem", "GetContributionRanking", "GetTopRanking", "GetMintingActivities", "GetLatestContributions", "ListActivityMessages", "CreateActivityMessage"},
|
||||||
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||||
dubboCli := dubboCliRaw.(*ActivityServiceImpl)
|
dubboCli := dubboCliRaw.(*ActivityServiceImpl)
|
||||||
dubboCli.conn = conn
|
dubboCli.conn = conn
|
||||||
@ -205,6 +216,7 @@ type ActivityServiceHandler interface {
|
|||||||
PurchaseItem(context.Context, *PurchaseItemRequest) (*PurchaseItemResponse, error)
|
PurchaseItem(context.Context, *PurchaseItemRequest) (*PurchaseItemResponse, error)
|
||||||
BatchPurchaseItem(context.Context, *BatchPurchaseItemRequest) (*BatchPurchaseItemResponse, error)
|
BatchPurchaseItem(context.Context, *BatchPurchaseItemRequest) (*BatchPurchaseItemResponse, error)
|
||||||
GetContributionRanking(context.Context, *ContributionRankingRequest) (*ContributionRankingResponse, error)
|
GetContributionRanking(context.Context, *ContributionRankingRequest) (*ContributionRankingResponse, error)
|
||||||
|
GetTopRanking(context.Context, *TopRankingRequest) (*TopRankingResponse, error)
|
||||||
GetMintingActivities(context.Context, *GetMintingActivitiesRequest) (*GetMintingActivitiesResponse, error)
|
GetMintingActivities(context.Context, *GetMintingActivitiesRequest) (*GetMintingActivitiesResponse, error)
|
||||||
GetLatestContributions(context.Context, *GetLatestContributionsRequest) (*GetLatestContributionsResponse, error)
|
GetLatestContributions(context.Context, *GetLatestContributionsRequest) (*GetLatestContributionsResponse, error)
|
||||||
ListActivityMessages(context.Context, *ListActivityMessagesRequest) (*ListActivityMessagesResponse, error)
|
ListActivityMessages(context.Context, *ListActivityMessagesRequest) (*ListActivityMessagesResponse, error)
|
||||||
@ -328,6 +340,21 @@ var ActivityService_ServiceInfo = server.ServiceInfo{
|
|||||||
return triple_protocol.NewResponse(res), nil
|
return triple_protocol.NewResponse(res), nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "GetTopRanking",
|
||||||
|
Type: constant.CallUnary,
|
||||||
|
ReqInitFunc: func() interface{} {
|
||||||
|
return new(TopRankingRequest)
|
||||||
|
},
|
||||||
|
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||||
|
req := args[0].(*TopRankingRequest)
|
||||||
|
res, err := handler.(ActivityServiceHandler).GetTopRanking(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return triple_protocol.NewResponse(res), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "GetMintingActivities",
|
Name: "GetMintingActivities",
|
||||||
Type: constant.CallUnary,
|
Type: constant.CallUnary,
|
||||||
|
|||||||
@ -136,6 +136,35 @@ message MyContribution {
|
|||||||
string avatar_url = 6;
|
string avatar_url = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 活动 TOP3 + 我的排名请求(专用轻量接口)
|
||||||
|
message TopRankingRequest {
|
||||||
|
int64 activity_id = 1;
|
||||||
|
int64 star_id = 2; // 0 表示不按明星过滤
|
||||||
|
int64 user_id = 3; // 来自 token
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOP3 排名项
|
||||||
|
message TopRankingItem {
|
||||||
|
int32 rank = 1;
|
||||||
|
int64 user_id = 2;
|
||||||
|
string avatar_url = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 我的 TOP 排名信息
|
||||||
|
message MyTopRankingInfo {
|
||||||
|
int32 rank = 1; // 0 表示未上榜
|
||||||
|
string avatar_url = 2;
|
||||||
|
int64 gap_to_prev = 3;
|
||||||
|
string status = 4; // "ranked" | "unranked"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOP 排名响应
|
||||||
|
message TopRankingResponse {
|
||||||
|
topfans.common.BaseResponse base = 1;
|
||||||
|
repeated TopRankingItem top3 = 2;
|
||||||
|
MyTopRankingInfo my_info = 3;
|
||||||
|
}
|
||||||
|
|
||||||
// 活动列表请求
|
// 活动列表请求
|
||||||
message GetActivityListRequest {
|
message GetActivityListRequest {
|
||||||
int64 star_id = 1;
|
int64 star_id = 1;
|
||||||
@ -322,6 +351,13 @@ service ActivityService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取活动 TOP3 + 我的排名(专用轻量接口)
|
||||||
|
rpc GetTopRanking(TopRankingRequest) returns (TopRankingResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/api/v1/activities/{activity_id}/top-ranking"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 获取铸造活动列表(用于运营banner)
|
// 获取铸造活动列表(用于运营banner)
|
||||||
rpc GetMintingActivities(GetMintingActivitiesRequest) returns (GetMintingActivitiesResponse) {
|
rpc GetMintingActivities(GetMintingActivitiesRequest) returns (GetMintingActivitiesResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
|
|||||||
@ -223,6 +223,39 @@ func (p *ActivityProvider) GetContributionRanking(ctx context.Context, req *pb.C
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTopRanking 获取活动 TOP3 + 我的排名(专用轻量接口)
|
||||||
|
func (p *ActivityProvider) GetTopRanking(ctx context.Context, req *pb.TopRankingRequest) (*pb.TopRankingResponse, error) {
|
||||||
|
logger.Logger.Info("Received GetTopRanking request",
|
||||||
|
zap.Int64("activity_id", req.ActivityId),
|
||||||
|
zap.Int64("star_id", req.StarId),
|
||||||
|
zap.Int64("user_id", req.UserId),
|
||||||
|
)
|
||||||
|
|
||||||
|
resp, err := p.activityService.GetTopRanking(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Error("GetTopRanking failed", zap.Error(err))
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(appErrors.ToGRPCCode(err)),
|
||||||
|
Message: err.Error(),
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Logger.Debug("GetTopRanking successful",
|
||||||
|
zap.Int("top3_count", len(resp.Top3)),
|
||||||
|
zap.Int32("my_rank", func() int32 {
|
||||||
|
if resp.MyInfo != nil {
|
||||||
|
return resp.MyInfo.Rank
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetMintingActivities 获取铸造活动列表(用于运营banner)
|
// GetMintingActivities 获取铸造活动列表(用于运营banner)
|
||||||
func (p *ActivityProvider) GetMintingActivities(ctx context.Context, req *pb.GetMintingActivitiesRequest) (*pb.GetMintingActivitiesResponse, error) {
|
func (p *ActivityProvider) GetMintingActivities(ctx context.Context, req *pb.GetMintingActivitiesRequest) (*pb.GetMintingActivitiesResponse, error) {
|
||||||
logger.Logger.Info("Received GetMintingActivities request",
|
logger.Logger.Info("Received GetMintingActivities request",
|
||||||
|
|||||||
@ -45,6 +45,18 @@ type ActivityRepository interface {
|
|||||||
|
|
||||||
// GetLatestContributions 获取最新贡献记录(用于实时显示)
|
// GetLatestContributions 获取最新贡献记录(用于实时显示)
|
||||||
GetLatestContributions(activityID int64, sinceTimestamp int64, sinceID int64, limit int) ([]*models.ActivityContribution, error)
|
GetLatestContributions(activityID int64, sinceTimestamp int64, sinceID int64, limit int) ([]*models.ActivityContribution, error)
|
||||||
|
|
||||||
|
// GetTop3 获取活动 TOP3(专用轻量接口)
|
||||||
|
// 返回 0~3 行,按 total_contribution DESC, id ASC 排序
|
||||||
|
GetTop3(activityID, starID int64) ([]*models.ActivityUserStats, error)
|
||||||
|
|
||||||
|
// GetUserStatsForRanking 获取用户在活动中的统计(返回 nil 表示未参与)
|
||||||
|
GetUserStatsForRanking(activityID, userID, starID int64) (*models.ActivityUserStats, error)
|
||||||
|
|
||||||
|
// GetUserStatsByRank 获取活动指定排名位置的统计;offset 从 0 开始
|
||||||
|
// rank=N 时,offset=N-1
|
||||||
|
// 用于"我排名 N 时,查 N-1 名"的场景
|
||||||
|
GetUserStatsByRank(activityID, starID int64, offset int) (*models.ActivityUserStats, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// activityRepository Activity仓库实现
|
// activityRepository Activity仓库实现
|
||||||
@ -291,6 +303,73 @@ func (r *activityRepository) GetUserRank(userID, activityID, starID int64) (int,
|
|||||||
return int(count) + 1, nil
|
return int(count) + 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTop3 获取活动 TOP3(按 total_contribution DESC, id ASC;starID<=0 不过滤)
|
||||||
|
func (r *activityRepository) GetTop3(activityID, starID int64) ([]*models.ActivityUserStats, error) {
|
||||||
|
if activityID <= 0 {
|
||||||
|
return nil, errors.New("activity_id must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := r.db.Model(&models.ActivityUserStats{}).Where("activity_id = ?", activityID)
|
||||||
|
if starID > 0 {
|
||||||
|
query = query.Where("star_id = ?", starID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats []*models.ActivityUserStats
|
||||||
|
if err := query.Order("total_contribution DESC, id ASC").Limit(3).Find(&stats).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserStatsForRanking 获取用户在活动中的统计;未找到返回 (nil, nil)
|
||||||
|
func (r *activityRepository) GetUserStatsForRanking(activityID, userID, starID int64) (*models.ActivityUserStats, error) {
|
||||||
|
if activityID <= 0 || userID <= 0 {
|
||||||
|
return nil, errors.New("activity_id and user_id must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := r.db.Where("activity_id = ? AND user_id = ?", activityID, userID)
|
||||||
|
if starID > 0 {
|
||||||
|
query = query.Where("star_id = ?", starID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats models.ActivityUserStats
|
||||||
|
if err := query.First(&stats).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserStatsByRank 获取活动指定排名位置的统计;offset 从 0 开始
|
||||||
|
// 返回 (nil, nil) 表示该位置无数据(如超过总人数)
|
||||||
|
func (r *activityRepository) GetUserStatsByRank(activityID, starID int64, offset int) (*models.ActivityUserStats, error) {
|
||||||
|
if activityID <= 0 {
|
||||||
|
return nil, errors.New("activity_id must be greater than 0")
|
||||||
|
}
|
||||||
|
if offset < 0 {
|
||||||
|
return nil, errors.New("offset must be >= 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := r.db.Model(&models.ActivityUserStats{}).Where("activity_id = ?", activityID)
|
||||||
|
if starID > 0 {
|
||||||
|
query = query.Where("star_id = ?", starID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats models.ActivityUserStats
|
||||||
|
if err := query.Order("total_contribution DESC, id ASC").
|
||||||
|
Offset(offset).
|
||||||
|
Limit(1).
|
||||||
|
First(&stats).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetLatestContributions 获取最新贡献记录(用于实时显示)
|
// GetLatestContributions 获取最新贡献记录(用于实时显示)
|
||||||
func (r *activityRepository) GetLatestContributions(activityID int64, sinceTimestamp int64, sinceID int64, limit int) ([]*models.ActivityContribution, error) {
|
func (r *activityRepository) GetLatestContributions(activityID int64, sinceTimestamp int64, sinceID int64, limit int) ([]*models.ActivityContribution, error) {
|
||||||
if activityID <= 0 {
|
if activityID <= 0 {
|
||||||
|
|||||||
@ -0,0 +1,161 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/topfans/backend/pkg/database"
|
||||||
|
"github.com/topfans/backend/pkg/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupTestDB 设置测试数据库(与 assetService 共用同一实例)
|
||||||
|
func setupTestDB(t *testing.T) *gorm.DB {
|
||||||
|
config := database.Config{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 5432,
|
||||||
|
User: "haihuizhu",
|
||||||
|
Password: "admin",
|
||||||
|
DBName: "top-fans",
|
||||||
|
SSLMode: "disable",
|
||||||
|
TimeZone: "Asia/Shanghai",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := database.Init(config); err != nil {
|
||||||
|
t.Skipf("Skipping test: failed to connect to test database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
if err := db.AutoMigrate(&models.Activity{}, &models.ActivityUserStats{}, &models.ActivityContribution{}); err != nil {
|
||||||
|
t.Logf("Warning: failed to migrate activity tables: %v", err)
|
||||||
|
}
|
||||||
|
cleanupActivityTestDB(t, db)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupActivityTestDB 清理测试数据
|
||||||
|
func cleanupActivityTestDB(_ *testing.T, db *gorm.DB) {
|
||||||
|
db.Exec("DELETE FROM activity_user_stats WHERE activity_id IN (SELECT id FROM activities WHERE title LIKE 'test_top_ranking_%')")
|
||||||
|
db.Exec("DELETE FROM activity_contributions WHERE activity_id IN (SELECT id FROM activities WHERE title LIKE 'test_top_ranking_%')")
|
||||||
|
db.Exec("DELETE FROM activities WHERE title LIKE 'test_top_ranking_%'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTestActivity 创建测试活动
|
||||||
|
func createTestActivity(t *testing.T, db *gorm.DB, title string, starID int64) *models.Activity {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
act := &models.Activity{
|
||||||
|
Title: title,
|
||||||
|
StarID: starID,
|
||||||
|
Status: "active",
|
||||||
|
StartTime: now - 3600,
|
||||||
|
EndTime: now + 3600,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
if err := db.Create(act).Error; err != nil {
|
||||||
|
t.Fatalf("Failed to create test activity: %v", err)
|
||||||
|
}
|
||||||
|
return act
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTestStats 创建用户活动统计
|
||||||
|
func createTestStats(t *testing.T, db *gorm.DB, activityID, userID, starID, totalContribution int64) *models.ActivityUserStats {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
stats := &models.ActivityUserStats{
|
||||||
|
ActivityID: activityID,
|
||||||
|
UserID: userID,
|
||||||
|
StarID: starID,
|
||||||
|
TotalContribution: totalContribution,
|
||||||
|
TotalCrystalSpent: 0,
|
||||||
|
TotalItems: 0,
|
||||||
|
LastContributeAt: now,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
if err := db.Create(stats).Error; err != nil {
|
||||||
|
t.Fatalf("Failed to create test stats: %v", err)
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3_Empty 测试活动没有任何 stats 时返回空切片
|
||||||
|
func TestGetTop3_Empty(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer cleanupActivityTestDB(t, db)
|
||||||
|
|
||||||
|
repo := NewActivityRepository()
|
||||||
|
act := createTestActivity(t, db, "test_top_ranking_empty", 9999)
|
||||||
|
stats, err := repo.GetTop3(act.ID, 0)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3_LessThan3 测试只有 1 行时返回 1 行
|
||||||
|
func TestGetTop3_LessThan3(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer cleanupActivityTestDB(t, db)
|
||||||
|
|
||||||
|
repo := NewActivityRepository()
|
||||||
|
act := createTestActivity(t, db, "test_top_ranking_one", 9999)
|
||||||
|
createTestStats(t, db, act.ID, 1001, 9999, 500)
|
||||||
|
|
||||||
|
stats, err := repo.GetTop3(act.ID, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, stats, 1)
|
||||||
|
assert.Equal(t, int64(1001), stats[0].UserID)
|
||||||
|
assert.Equal(t, int64(500), stats[0].TotalContribution)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3_FullWithStar 测试 3 行且带 star_id 过滤
|
||||||
|
func TestGetTop3_FullWithStar(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer cleanupActivityTestDB(t, db)
|
||||||
|
|
||||||
|
repo := NewActivityRepository()
|
||||||
|
act := createTestActivity(t, db, "test_top_ranking_full", 7777)
|
||||||
|
|
||||||
|
// 制造 5 条:3 条 star=7777, 2 条 star=8888;应只返回 star=7777 的前 3
|
||||||
|
createTestStats(t, db, act.ID, 1001, 7777, 900)
|
||||||
|
createTestStats(t, db, act.ID, 1002, 7777, 800)
|
||||||
|
createTestStats(t, db, act.ID, 1003, 7777, 700)
|
||||||
|
createTestStats(t, db, act.ID, 2001, 8888, 9999)
|
||||||
|
createTestStats(t, db, act.ID, 2002, 8888, 9998)
|
||||||
|
|
||||||
|
stats, err := repo.GetTop3(act.ID, 7777)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, stats, 3)
|
||||||
|
assert.Equal(t, int64(1001), stats[0].UserID)
|
||||||
|
assert.Equal(t, int64(1002), stats[1].UserID)
|
||||||
|
assert.Equal(t, int64(1003), stats[2].UserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetUserStatsForRanking_NotFound 测试未找到时返回 (nil, nil)
|
||||||
|
func TestGetUserStatsForRanking_NotFound(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer cleanupActivityTestDB(t, db)
|
||||||
|
|
||||||
|
repo := NewActivityRepository()
|
||||||
|
act := createTestActivity(t, db, "test_top_ranking_notfound", 6666)
|
||||||
|
|
||||||
|
stats, err := repo.GetUserStatsForRanking(act.ID, 99999, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetUserStatsForRanking_Found 测试找到时返回正确 stats
|
||||||
|
func TestGetUserStatsForRanking_Found(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer cleanupActivityTestDB(t, db)
|
||||||
|
|
||||||
|
repo := NewActivityRepository()
|
||||||
|
act := createTestActivity(t, db, "test_top_ranking_found", 6666)
|
||||||
|
createTestStats(t, db, act.ID, 1001, 6666, 1500)
|
||||||
|
|
||||||
|
stats, err := repo.GetUserStatsForRanking(act.ID, 1001, 6666)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, stats)
|
||||||
|
assert.Equal(t, int64(1500), stats.TotalContribution)
|
||||||
|
assert.Equal(t, int64(1001), stats.UserID)
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@ -56,6 +57,9 @@ type ActivityService interface {
|
|||||||
// GetContributionRanking 获取贡献点排名
|
// GetContributionRanking 获取贡献点排名
|
||||||
GetContributionRanking(ctx context.Context, req *pb.ContributionRankingRequest) (*pb.ContributionRankingResponse, error)
|
GetContributionRanking(ctx context.Context, req *pb.ContributionRankingRequest) (*pb.ContributionRankingResponse, error)
|
||||||
|
|
||||||
|
// GetTopRanking 获取活动 TOP3 + 我的排名(专用轻量接口)
|
||||||
|
GetTopRanking(ctx context.Context, req *pb.TopRankingRequest) (*pb.TopRankingResponse, error)
|
||||||
|
|
||||||
// GetMintingActivities 获取铸造活动列表(用于运营banner)
|
// GetMintingActivities 获取铸造活动列表(用于运营banner)
|
||||||
GetMintingActivities(ctx context.Context, req *pb.GetMintingActivitiesRequest) (*pb.GetMintingActivitiesResponse, error)
|
GetMintingActivities(ctx context.Context, req *pb.GetMintingActivitiesRequest) (*pb.GetMintingActivitiesResponse, error)
|
||||||
|
|
||||||
@ -69,6 +73,13 @@ type ActivityService interface {
|
|||||||
ListActivityMessages(ctx context.Context, req *pb.ListActivityMessagesRequest) (*pb.ListActivityMessagesResponse, error)
|
ListActivityMessages(ctx context.Context, req *pb.ListActivityMessagesRequest) (*pb.ListActivityMessagesResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// top3CacheClient 缓存读写所需的最小 Redis 接口
|
||||||
|
// 用小接口便于测试时 mock;真实 *redis.Client 满足此接口
|
||||||
|
type top3CacheClient interface {
|
||||||
|
Get(ctx context.Context, key string) *redis.StringCmd
|
||||||
|
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd
|
||||||
|
}
|
||||||
|
|
||||||
// activityService 活动Service实现
|
// activityService 活动Service实现
|
||||||
type activityService struct {
|
type activityService struct {
|
||||||
activityRepo repository.ActivityRepository
|
activityRepo repository.ActivityRepository
|
||||||
@ -76,17 +87,23 @@ type activityService struct {
|
|||||||
messagesRepo repository.ActivityMessagesRepository
|
messagesRepo repository.ActivityMessagesRepository
|
||||||
userRPCClient client.UserRPCClient
|
userRPCClient client.UserRPCClient
|
||||||
redisClient *redis.Client
|
redisClient *redis.Client
|
||||||
|
cache top3CacheClient
|
||||||
messageCfg *config.ActivityMessageConfig
|
messageCfg *config.ActivityMessageConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewActivityService 创建活动Service实例
|
// NewActivityService 创建活动Service实例
|
||||||
func NewActivityService(activityRepo repository.ActivityRepository, mintingActivityRepo repository.MintingActivityRepository, userRPCClient client.UserRPCClient, redisClient *redis.Client) ActivityService {
|
func NewActivityService(activityRepo repository.ActivityRepository, mintingActivityRepo repository.MintingActivityRepository, userRPCClient client.UserRPCClient, redisClient *redis.Client) ActivityService {
|
||||||
|
var cache top3CacheClient
|
||||||
|
if redisClient != nil {
|
||||||
|
cache = redisClient
|
||||||
|
}
|
||||||
return &activityService{
|
return &activityService{
|
||||||
activityRepo: activityRepo,
|
activityRepo: activityRepo,
|
||||||
mintingActivityRepo: mintingActivityRepo,
|
mintingActivityRepo: mintingActivityRepo,
|
||||||
messagesRepo: repository.NewActivityMessagesRepository(),
|
messagesRepo: repository.NewActivityMessagesRepository(),
|
||||||
userRPCClient: userRPCClient,
|
userRPCClient: userRPCClient,
|
||||||
redisClient: redisClient,
|
redisClient: redisClient,
|
||||||
|
cache: cache,
|
||||||
messageCfg: config.LoadMessageConfig(),
|
messageCfg: config.LoadMessageConfig(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -918,6 +935,201 @@ func (s *activityService) GetContributionRanking(ctx context.Context, req *pb.Co
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// starIDOrAll 缓存 key 用:starID<=0 用 "all" 占位,避免跨明星命中
|
||||||
|
func starIDOrAll(starID int64) string {
|
||||||
|
if starID <= 0 {
|
||||||
|
return "all"
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(starID, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTop3WithCache 带 Redis 缓存的 top3 查询;TTL=30s,反序列化失败/Redis 故障 fallback DB
|
||||||
|
// 返回 (items, cacheHit, error)
|
||||||
|
func (s *activityService) getTop3WithCache(ctx context.Context, activityID, starID int64) ([]*pb.TopRankingItem, bool, error) {
|
||||||
|
key := fmt.Sprintf("activity:top3:%d:%s", activityID, starIDOrAll(starID))
|
||||||
|
|
||||||
|
// 1. 读 Redis(无客户端或故障时不阻塞,直接回源)
|
||||||
|
if s.cache != nil {
|
||||||
|
cached, err := s.cache.Get(ctx, key).Result()
|
||||||
|
if err == nil && cached != "" {
|
||||||
|
var items []*pb.TopRankingItem
|
||||||
|
if json.Unmarshal([]byte(cached), &items) == nil {
|
||||||
|
return items, true, nil
|
||||||
|
}
|
||||||
|
logger.Logger.Warn("top3 cache corrupted, falling back to DB", zap.String("key", key))
|
||||||
|
} else if err != redis.Nil {
|
||||||
|
logger.Logger.Warn("top3 cache get failed", zap.String("key", key), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 回源 DB
|
||||||
|
stats, err := s.activityRepo.GetTop3(activityID, starID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转 proto
|
||||||
|
items := make([]*pb.TopRankingItem, len(stats))
|
||||||
|
for i := range stats {
|
||||||
|
items[i] = &pb.TopRankingItem{
|
||||||
|
Rank: int32(i + 1),
|
||||||
|
UserId: stats[i].UserID,
|
||||||
|
AvatarUrl: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 写回 Redis(异步失败仅 WARN)
|
||||||
|
if s.cache != nil {
|
||||||
|
if data, jerr := json.Marshal(items); jerr == nil {
|
||||||
|
if serr := s.cache.Set(ctx, key, data, 30*time.Second).Err(); serr != nil {
|
||||||
|
logger.Logger.Warn("top3 cache set failed", zap.String("key", key), zap.Error(serr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopRanking 获取活动 TOP3 + 我的排名(专用轻量接口)
|
||||||
|
func (s *activityService) GetTopRanking(ctx context.Context, req *pb.TopRankingRequest) (*pb.TopRankingResponse, error) {
|
||||||
|
logger.Logger.Info("GetTopRanking request",
|
||||||
|
zap.Int64("activity_id", req.ActivityId),
|
||||||
|
zap.Int64("star_id", req.StarId),
|
||||||
|
zap.Int64("user_id", req.UserId),
|
||||||
|
)
|
||||||
|
|
||||||
|
if req.ActivityId <= 0 {
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(codes.InvalidArgument),
|
||||||
|
Message: "activity_id is required",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if req.UserId <= 0 {
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(codes.Unauthenticated),
|
||||||
|
Message: "user_id is required",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) 取 top3(带缓存)
|
||||||
|
top3Items, cacheHit, err := s.getTop3WithCache(ctx, req.ActivityId, req.StarId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Error("getTop3WithCache failed", zap.Error(err))
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(codes.Internal),
|
||||||
|
Message: "获取 top3 失败: " + err.Error(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
logger.Logger.Debug("top3 fetched", zap.Int("size", len(top3Items)), zap.Bool("cache_hit", cacheHit))
|
||||||
|
|
||||||
|
// 2) 批量补全 top3 头像(单点失败 WARN 继续)
|
||||||
|
for _, it := range top3Items {
|
||||||
|
if profile, perr := s.userRPCClient.GetFanProfile(it.UserId, req.StarId); perr == nil && profile != nil {
|
||||||
|
it.AvatarUrl = profile.AvatarUrl
|
||||||
|
} else if perr != nil {
|
||||||
|
logger.Logger.Warn("GetFanProfile failed for top3 user",
|
||||||
|
zap.Int64("user_id", it.UserId), zap.Error(perr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 判定 my_info
|
||||||
|
myStats, err := s.activityRepo.GetUserStatsForRanking(req.ActivityId, req.UserId, req.StarId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Error("GetUserStatsForRanking failed", zap.Error(err))
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(codes.Internal),
|
||||||
|
Message: "获取用户统计失败: " + err.Error(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前用户头像(无论 ranked/unranked 都填充)
|
||||||
|
var myAvatar string
|
||||||
|
if profile, perr := s.userRPCClient.GetFanProfile(req.UserId, req.StarId); perr == nil && profile != nil {
|
||||||
|
myAvatar = profile.AvatarUrl
|
||||||
|
} else if perr != nil {
|
||||||
|
logger.Logger.Warn("GetFanProfile failed for current user",
|
||||||
|
zap.Int64("user_id", req.UserId), zap.Error(perr))
|
||||||
|
}
|
||||||
|
|
||||||
|
myInfo := &pb.MyTopRankingInfo{
|
||||||
|
Rank: 0,
|
||||||
|
AvatarUrl: myAvatar,
|
||||||
|
GapToPrev: 0,
|
||||||
|
Status: "unranked",
|
||||||
|
}
|
||||||
|
|
||||||
|
if myStats == nil {
|
||||||
|
logger.Logger.Debug("user not ranked in activity",
|
||||||
|
zap.Int64("user_id", req.UserId), zap.Int64("activity_id", req.ActivityId))
|
||||||
|
} else {
|
||||||
|
// 计算 rank(复用现有方法,starID<=0 时 GetUserRank 会拒绝,需要兜底)
|
||||||
|
var myRank int
|
||||||
|
if req.StarId > 0 {
|
||||||
|
rank, rerr := s.activityRepo.GetUserRank(req.UserId, req.ActivityId, req.StarId)
|
||||||
|
if rerr != nil {
|
||||||
|
logger.Logger.Error("GetUserRank failed", zap.Error(rerr))
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(codes.Internal),
|
||||||
|
Message: "获取用户排名失败: " + rerr.Error(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
myRank = rank
|
||||||
|
} else {
|
||||||
|
// starID<=0:无法跨明星唯一排名,降级:算"全局未限明星"下的排名
|
||||||
|
myRank = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
gap := int64(0)
|
||||||
|
if myRank <= 1 {
|
||||||
|
gap = 0
|
||||||
|
} else if myRank >= 2 && myRank <= 3 {
|
||||||
|
// 从 top3 数组算 O(1)
|
||||||
|
prev := top3Items[myRank-2]
|
||||||
|
if prev != nil {
|
||||||
|
// 同一 user 不可能出现:这种边界不存在,top3 数组下标永远对应 N-1 名
|
||||||
|
// 但 gap 应该是 prev.contribution - myStats.contribution;这里 top3 缓存里没存 contribution
|
||||||
|
// 设计文档说从 top3 数组 O(1) 算,所以我们用 stats 二次查(只在 rank<=3 时多查一次)
|
||||||
|
if sStats, serr := s.activityRepo.GetUserStatsForRanking(req.ActivityId, prev.UserId, req.StarId); serr == nil && sStats != nil {
|
||||||
|
gap = sStats.TotalContribution - myStats.TotalContribution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// rank > 3:多一次 OFFSET 查询第 N-1 名
|
||||||
|
offset := myRank - 2
|
||||||
|
stats, oerr := s.activityRepo.GetUserStatsByRank(req.ActivityId, req.StarId, offset)
|
||||||
|
if oerr == nil && stats != nil {
|
||||||
|
gap = stats.TotalContribution - myStats.TotalContribution
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gap < 0 {
|
||||||
|
gap = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
myInfo.Rank = int32(myRank)
|
||||||
|
myInfo.GapToPrev = gap
|
||||||
|
myInfo.Status = "ranked"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.TopRankingResponse{
|
||||||
|
Base: &pbCommon.BaseResponse{
|
||||||
|
Code: uint32(codes.OK),
|
||||||
|
Message: "ok",
|
||||||
|
},
|
||||||
|
Top3: top3Items,
|
||||||
|
MyInfo: myInfo,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// convertActivity 转换Activity模型到proto
|
// convertActivity 转换Activity模型到proto
|
||||||
func (s *activityService) convertActivity(activity *models.Activity) *pb.Activity {
|
func (s *activityService) convertActivity(activity *models.Activity) *pb.Activity {
|
||||||
items := make([]*pb.ActivityItem, len(activity.Items))
|
items := make([]*pb.ActivityItem, len(activity.Items))
|
||||||
|
|||||||
@ -0,0 +1,189 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/topfans/backend/pkg/models"
|
||||||
|
pb "github.com/topfans/backend/pkg/proto/activity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakeCacheClient 内存版 cache(支持命中/未命中/脏数据/Redis 故障/set 失败)
|
||||||
|
type fakeCacheClient struct {
|
||||||
|
store map[string]string
|
||||||
|
getErr error // 非 nil 模拟 Redis 故障
|
||||||
|
setErr error
|
||||||
|
setCalled bool
|
||||||
|
lastSetKey string
|
||||||
|
lastSetValue interface{}
|
||||||
|
lastSetTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeCache() *fakeCacheClient {
|
||||||
|
return &fakeCacheClient{store: map[string]string{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCacheClient) Get(ctx context.Context, key string) *redis.StringCmd {
|
||||||
|
cmd := redis.NewStringCmd(ctx, key)
|
||||||
|
if f.getErr != nil {
|
||||||
|
cmd.SetErr(f.getErr)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
if v, ok := f.store[key]; ok {
|
||||||
|
cmd.SetVal(v)
|
||||||
|
} else {
|
||||||
|
cmd.SetErr(redis.Nil)
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCacheClient) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd {
|
||||||
|
f.setCalled = true
|
||||||
|
f.lastSetKey = key
|
||||||
|
f.lastSetValue = value
|
||||||
|
f.lastSetTTL = expiration
|
||||||
|
cmd := redis.NewStatusCmd(ctx, key, value, expiration)
|
||||||
|
if f.setErr != nil {
|
||||||
|
cmd.SetErr(f.setErr)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
cmd.SetVal("OK")
|
||||||
|
if s, ok := value.(string); ok {
|
||||||
|
f.store[key] = s
|
||||||
|
} else if b, err := json.Marshal(value); err == nil {
|
||||||
|
f.store[key] = string(b)
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCacheTestService(repo *mockActivityRepo, cache *fakeCacheClient) *activityService {
|
||||||
|
return &activityService{
|
||||||
|
activityRepo: repo,
|
||||||
|
userRPCClient: &mockUserRPC{},
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_Hit 缓存命中 → 不调 DB
|
||||||
|
func TestGetTop3WithCache_Hit(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{{UserID: 999}}, // 不应被调用
|
||||||
|
}
|
||||||
|
cache := newFakeCache()
|
||||||
|
cached := []*pb.TopRankingItem{
|
||||||
|
{Rank: 1, UserId: 1001, AvatarUrl: "https://cdn/1001.jpg"},
|
||||||
|
{Rank: 2, UserId: 1002, AvatarUrl: "https://cdn/1002.jpg"},
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(cached)
|
||||||
|
cache.store["activity:top3:100:7"] = string(b)
|
||||||
|
|
||||||
|
svc := newCacheTestService(repo, cache)
|
||||||
|
items, hit, err := svc.getTop3WithCache(context.Background(), 100, 7)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hit, "应命中缓存")
|
||||||
|
assert.Len(t, items, 2)
|
||||||
|
assert.Equal(t, int64(1001), items[0].UserId)
|
||||||
|
assert.Equal(t, 0, repo.getTop3CallCount, "命中缓存不应查 DB")
|
||||||
|
assert.False(t, cache.setCalled, "命中缓存不应回写")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_Miss 缓存未命中 → 回源 DB + 写回
|
||||||
|
func TestGetTop3WithCache_Miss(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 1001, TotalContribution: 900},
|
||||||
|
{UserID: 1002, TotalContribution: 800},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cache := newFakeCache()
|
||||||
|
|
||||||
|
svc := newCacheTestService(repo, cache)
|
||||||
|
items, hit, err := svc.getTop3WithCache(context.Background(), 100, 7)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hit)
|
||||||
|
assert.Len(t, items, 2)
|
||||||
|
assert.Equal(t, 1, repo.getTop3CallCount, "miss 必须查一次 DB")
|
||||||
|
assert.True(t, cache.setCalled, "miss 后必须回写")
|
||||||
|
assert.Equal(t, "activity:top3:100:7", cache.lastSetKey)
|
||||||
|
assert.Equal(t, 30*time.Second, cache.lastSetTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_CorruptedJSON 脏数据 → 当 miss 处理,覆盖写
|
||||||
|
func TestGetTop3WithCache_CorruptedJSON(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{{UserID: 1001}},
|
||||||
|
}
|
||||||
|
cache := newFakeCache()
|
||||||
|
cache.store["activity:top3:100:7"] = "not-a-json{"
|
||||||
|
|
||||||
|
svc := newCacheTestService(repo, cache)
|
||||||
|
items, hit, err := svc.getTop3WithCache(context.Background(), 100, 7)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hit, "脏数据视为 miss")
|
||||||
|
assert.Len(t, items, 1)
|
||||||
|
assert.True(t, cache.setCalled, "必须覆盖写入")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_RedisDown Get 报错(非 nil)→ 记 WARN + 回源 DB
|
||||||
|
func TestGetTop3WithCache_RedisDown(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{{UserID: 1001}},
|
||||||
|
}
|
||||||
|
cache := newFakeCache()
|
||||||
|
cache.getErr = errors.New("connection refused")
|
||||||
|
|
||||||
|
svc := newCacheTestService(repo, cache)
|
||||||
|
items, hit, err := svc.getTop3WithCache(context.Background(), 100, 7)
|
||||||
|
assert.NoError(t, err, "Redis 故障不应让接口失败")
|
||||||
|
assert.False(t, hit)
|
||||||
|
assert.Len(t, items, 1, "回源 DB 仍应返回结果")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_SetFailure Set 失败 → 仍返回 DB 结果
|
||||||
|
func TestGetTop3WithCache_SetFailure(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{{UserID: 1001}},
|
||||||
|
}
|
||||||
|
cache := newFakeCache()
|
||||||
|
cache.setErr = errors.New("write timeout")
|
||||||
|
|
||||||
|
svc := newCacheTestService(repo, cache)
|
||||||
|
items, hit, err := svc.getTop3WithCache(context.Background(), 100, 7)
|
||||||
|
assert.NoError(t, err, "Set 失败不应让接口失败")
|
||||||
|
assert.False(t, hit)
|
||||||
|
assert.Len(t, items, 1, "Set 失败也要返回 DB 结果")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_NilCache cache=nil → 跳过 Redis,直接走 DB
|
||||||
|
func TestGetTop3WithCache_NilCache(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{{UserID: 1001}},
|
||||||
|
}
|
||||||
|
svc := &activityService{
|
||||||
|
activityRepo: repo,
|
||||||
|
userRPCClient: &mockUserRPC{},
|
||||||
|
cache: nil,
|
||||||
|
}
|
||||||
|
items, hit, err := svc.getTop3WithCache(context.Background(), 100, 7)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hit)
|
||||||
|
assert.Len(t, items, 1)
|
||||||
|
assert.Equal(t, 1, repo.getTop3CallCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetTop3WithCache_StarIDZeroKey starID<=0 时 key 用 "all" 占位
|
||||||
|
func TestGetTop3WithCache_StarIDZeroKey(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{{UserID: 1001}},
|
||||||
|
}
|
||||||
|
cache := newFakeCache()
|
||||||
|
|
||||||
|
svc := newCacheTestService(repo, cache)
|
||||||
|
_, _, _ = svc.getTop3WithCache(context.Background(), 100, 0)
|
||||||
|
assert.Equal(t, "activity:top3:100:all", cache.lastSetKey)
|
||||||
|
}
|
||||||
@ -0,0 +1,297 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/topfans/backend/pkg/logger"
|
||||||
|
"github.com/topfans/backend/pkg/models"
|
||||||
|
pb "github.com/topfans/backend/pkg/proto/activity"
|
||||||
|
"github.com/topfans/backend/services/activityService/client"
|
||||||
|
"github.com/topfans/backend/services/activityService/repository"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if logger.Logger == nil {
|
||||||
|
logger.Logger = zap.NewNop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- Mocks --------------------
|
||||||
|
|
||||||
|
// mockActivityRepo 内存版 ActivityRepository;只实现本测试需要的方法
|
||||||
|
type mockActivityRepo struct {
|
||||||
|
top3Stats []*models.ActivityUserStats
|
||||||
|
top3Err error
|
||||||
|
getTop3CallCount int
|
||||||
|
userStats *models.ActivityUserStats // 缺省返回(单用户场景)
|
||||||
|
statsByUserID map[int64]*models.ActivityUserStats // 区分 userID 时的查询
|
||||||
|
userStatsErr error
|
||||||
|
rank int
|
||||||
|
rankErr error
|
||||||
|
byRankStats *models.ActivityUserStats
|
||||||
|
byRankErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockActivityRepo) GetTop3(activityID, starID int64) ([]*models.ActivityUserStats, error) {
|
||||||
|
m.getTop3CallCount++
|
||||||
|
if m.top3Err != nil {
|
||||||
|
return nil, m.top3Err
|
||||||
|
}
|
||||||
|
return m.top3Stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockActivityRepo) GetUserStatsForRanking(activityID, userID, starID int64) (*models.ActivityUserStats, error) {
|
||||||
|
if m.userStatsErr != nil {
|
||||||
|
return nil, m.userStatsErr
|
||||||
|
}
|
||||||
|
if m.statsByUserID != nil {
|
||||||
|
if s, ok := m.statsByUserID[userID]; ok {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
return nil, nil // 未找到视为未参与
|
||||||
|
}
|
||||||
|
return m.userStats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockActivityRepo) GetUserRank(userID, activityID, starID int64) (int, error) {
|
||||||
|
return m.rank, m.rankErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockActivityRepo) GetUserStatsByRank(activityID, starID int64, offset int) (*models.ActivityUserStats, error) {
|
||||||
|
return m.byRankStats, m.byRankErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他未使用方法(no-op,保证实现接口)
|
||||||
|
func (m *mockActivityRepo) CreateActivity(*models.Activity) error { return nil }
|
||||||
|
func (m *mockActivityRepo) GetActivityByID(int64) (*models.Activity, error) { return nil, nil }
|
||||||
|
func (m *mockActivityRepo) GetActivitiesByStar(int64, string, int, int) ([]*models.Activity, int64, error) {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
func (m *mockActivityRepo) UpdateActivityProgress(int64, int64) error { return nil }
|
||||||
|
func (m *mockActivityRepo) GetActivityItems(int64) ([]*models.ActivityItem, error) { return nil, nil }
|
||||||
|
func (m *mockActivityRepo) GetActivityItemByType(int64, string) (*models.ActivityItem, error) { return nil, nil }
|
||||||
|
func (m *mockActivityRepo) CreateContribution(*models.ActivityContribution) error { return nil }
|
||||||
|
func (m *mockActivityRepo) GetUserStats(int64, int64, int64) (*models.ActivityUserStats, error) {
|
||||||
|
return m.userStats, m.userStatsErr
|
||||||
|
}
|
||||||
|
func (m *mockActivityRepo) UpdateUserStats(*models.ActivityUserStats) error { return nil }
|
||||||
|
func (m *mockActivityRepo) GetRanking(int64, int64, int, int) ([]*models.ActivityUserStats, int64, error) {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
func (m *mockActivityRepo) GetLatestContributions(int64, int64, int64, int) ([]*models.ActivityContribution, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockUserRPC 内存版 UserRPCClient
|
||||||
|
type mockUserRPC struct {
|
||||||
|
profiles map[int64]*client.FanProfile
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockUserRPC) GetFanProfile(userID, starID int64) (*client.FanProfile, error) {
|
||||||
|
if m.err != nil {
|
||||||
|
return nil, m.err
|
||||||
|
}
|
||||||
|
if p, ok := m.profiles[userID]; ok {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("profile not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockUserRPC) UpdateCrystalBalance(int64, int64, int64) (int64, error) { return 0, nil }
|
||||||
|
|
||||||
|
// -------------------- Tests --------------------
|
||||||
|
|
||||||
|
func newTestService(repo repository.ActivityRepository, urpc client.UserRPCClient) *activityService {
|
||||||
|
return &activityService{
|
||||||
|
activityRepo: repo,
|
||||||
|
userRPCClient: urpc,
|
||||||
|
// cache=nil → getTop3WithCache 跳过缓存,直接走 DB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_UnrankedUser(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 1001, TotalContribution: 900},
|
||||||
|
{UserID: 1002, TotalContribution: 800},
|
||||||
|
},
|
||||||
|
userStats: nil, // 未参与
|
||||||
|
}
|
||||||
|
urpc := &mockUserRPC{profiles: map[int64]*client.FanProfile{
|
||||||
|
2001: {UserID: 2001, AvatarUrl: "https://cdn/2001.jpg"},
|
||||||
|
}}
|
||||||
|
|
||||||
|
svc := newTestService(repo, urpc)
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100,
|
||||||
|
StarId: 7,
|
||||||
|
UserId: 2001,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
|
assert.Equal(t, uint32(0), resp.Base.Code)
|
||||||
|
assert.Len(t, resp.Top3, 2)
|
||||||
|
assert.NotNil(t, resp.MyInfo)
|
||||||
|
assert.Equal(t, int32(0), resp.MyInfo.Rank)
|
||||||
|
assert.Equal(t, "unranked", resp.MyInfo.Status)
|
||||||
|
assert.Equal(t, int64(0), resp.MyInfo.GapToPrev)
|
||||||
|
assert.Equal(t, "https://cdn/2001.jpg", resp.MyInfo.AvatarUrl,
|
||||||
|
"未上榜用户也应该拿到自己的头像")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_Rank1(t *testing.T) {
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 2001, TotalContribution: 1500},
|
||||||
|
},
|
||||||
|
userStats: &models.ActivityUserStats{UserID: 2001, TotalContribution: 1500},
|
||||||
|
rank: 1,
|
||||||
|
}
|
||||||
|
urpc := &mockUserRPC{profiles: map[int64]*client.FanProfile{
|
||||||
|
2001: {UserID: 2001, AvatarUrl: "https://cdn/2001.jpg"},
|
||||||
|
}}
|
||||||
|
|
||||||
|
svc := newTestService(repo, urpc)
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100, StarId: 7, UserId: 2001,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int32(1), resp.MyInfo.Rank)
|
||||||
|
assert.Equal(t, "ranked", resp.MyInfo.Status)
|
||||||
|
assert.Equal(t, int64(0), resp.MyInfo.GapToPrev, "rank=1 时 gap 必为 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_RankInTop3(t *testing.T) {
|
||||||
|
// 我是 rank=3;top3 = [1001(900), 1002(800), 2001(700)]
|
||||||
|
// gap = 1002.contribution(800) - 2001.contribution(700) = 100
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 1001, TotalContribution: 900},
|
||||||
|
{UserID: 1002, TotalContribution: 800},
|
||||||
|
{UserID: 2001, TotalContribution: 700},
|
||||||
|
},
|
||||||
|
statsByUserID: map[int64]*models.ActivityUserStats{
|
||||||
|
2001: {UserID: 2001, TotalContribution: 700},
|
||||||
|
1002: {UserID: 1002, TotalContribution: 800}, // prev(我上一名)
|
||||||
|
},
|
||||||
|
rank: 3,
|
||||||
|
}
|
||||||
|
urpc := &mockUserRPC{profiles: map[int64]*client.FanProfile{
|
||||||
|
1001: {UserID: 1001, AvatarUrl: "https://cdn/1001.jpg"},
|
||||||
|
1002: {UserID: 1002, AvatarUrl: "https://cdn/1002.jpg"},
|
||||||
|
2001: {UserID: 2001, AvatarUrl: "https://cdn/2001.jpg"},
|
||||||
|
}}
|
||||||
|
|
||||||
|
svc := newTestService(repo, urpc)
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100, StarId: 7, UserId: 2001,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int32(3), resp.MyInfo.Rank)
|
||||||
|
assert.Equal(t, "ranked", resp.MyInfo.Status)
|
||||||
|
// 注:rank<=3 路径走的是 O(1) 算 top3 数组,内部会调 GetUserStatsForRanking
|
||||||
|
// 取出 prev(1002).contribution = 800, gap = 800 - 700 = 100
|
||||||
|
assert.Equal(t, int64(100), resp.MyInfo.GapToPrev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_RankBeyondTop3(t *testing.T) {
|
||||||
|
// rank=10; top3 数组长度=3,需走 OFFSET 查询第 9 名(偏移 8)
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 1001, TotalContribution: 900},
|
||||||
|
{UserID: 1002, TotalContribution: 800},
|
||||||
|
{UserID: 1003, TotalContribution: 700},
|
||||||
|
},
|
||||||
|
userStats: &models.ActivityUserStats{UserID: 2001, TotalContribution: 100},
|
||||||
|
rank: 10,
|
||||||
|
byRankStats: &models.ActivityUserStats{UserID: 1099, TotalContribution: 500},
|
||||||
|
// gap = 500 - 100 = 400
|
||||||
|
}
|
||||||
|
urpc := &mockUserRPC{profiles: map[int64]*client.FanProfile{
|
||||||
|
1001: {UserID: 1001, AvatarUrl: "https://cdn/1001.jpg"},
|
||||||
|
1002: {UserID: 1002, AvatarUrl: "https://cdn/1002.jpg"},
|
||||||
|
1003: {UserID: 1003, AvatarUrl: "https://cdn/1003.jpg"},
|
||||||
|
2001: {UserID: 2001, AvatarUrl: "https://cdn/2001.jpg"},
|
||||||
|
}}
|
||||||
|
|
||||||
|
svc := newTestService(repo, urpc)
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100, StarId: 7, UserId: 2001,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int32(10), resp.MyInfo.Rank)
|
||||||
|
assert.Equal(t, int64(400), resp.MyInfo.GapToPrev, "rank>3 走 OFFSET 查询")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_GapClampedToZero(t *testing.T) {
|
||||||
|
// 自己贡献被并发刷成比上一名还高(异常);gap 应被 clamp 到 0
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 2001, TotalContribution: 9999},
|
||||||
|
},
|
||||||
|
userStats: &models.ActivityUserStats{UserID: 2001, TotalContribution: 9999},
|
||||||
|
rank: 1, // 自己第一名,gap 自然 0
|
||||||
|
}
|
||||||
|
urpc := &mockUserRPC{profiles: map[int64]*client.FanProfile{
|
||||||
|
2001: {UserID: 2001, AvatarUrl: "https://cdn/2001.jpg"},
|
||||||
|
}}
|
||||||
|
svc := newTestService(repo, urpc)
|
||||||
|
resp, _ := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100, StarId: 7, UserId: 2001,
|
||||||
|
})
|
||||||
|
assert.Equal(t, int64(0), resp.MyInfo.GapToPrev)
|
||||||
|
assert.GreaterOrEqual(t, resp.MyInfo.GapToPrev, int64(0),
|
||||||
|
"gap 永远非负")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_FanProfileFailure(t *testing.T) {
|
||||||
|
// GetFanProfile 全失败 → avatar_url 空字符串,其他字段正常
|
||||||
|
repo := &mockActivityRepo{
|
||||||
|
top3Stats: []*models.ActivityUserStats{
|
||||||
|
{UserID: 1001, TotalContribution: 900},
|
||||||
|
},
|
||||||
|
userStats: &models.ActivityUserStats{UserID: 2001, TotalContribution: 100},
|
||||||
|
rank: 1,
|
||||||
|
}
|
||||||
|
urpc := &mockUserRPC{err: errors.New("rpc down")}
|
||||||
|
|
||||||
|
svc := newTestService(repo, urpc)
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100, StarId: 7, UserId: 2001,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, uint32(0), resp.Base.Code, "RPC 失败不应阻塞接口")
|
||||||
|
for _, it := range resp.Top3 {
|
||||||
|
assert.Equal(t, "", it.AvatarUrl)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "", resp.MyInfo.AvatarUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_InvalidActivityID(t *testing.T) {
|
||||||
|
svc := newTestService(&mockActivityRepo{}, &mockUserRPC{})
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 0, UserId: 1,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEqual(t, uint32(0), resp.Base.Code, "activity_id<=0 应返回错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTopRanking_MissingUserID(t *testing.T) {
|
||||||
|
svc := newTestService(&mockActivityRepo{}, &mockUserRPC{})
|
||||||
|
resp, err := svc.GetTopRanking(context.Background(), &pb.TopRankingRequest{
|
||||||
|
ActivityId: 100, UserId: 0,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEqual(t, uint32(0), resp.Base.Code, "user_id=0 应返回未授权")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStarIDOrAll(t *testing.T) {
|
||||||
|
assert.Equal(t, "all", starIDOrAll(0))
|
||||||
|
assert.Equal(t, "all", starIDOrAll(-1))
|
||||||
|
assert.Equal(t, "123", starIDOrAll(123))
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
||||||
# VITE_API_BASE_URL=http://192.168.110.60:8080
|
VITE_API_BASE_URL=http://192.168.110.60:8080
|
||||||
VITE_API_BASE_URL=https://api.topfans.online
|
# VITE_API_BASE_URL=https://api.topfans.online
|
||||||
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
||||||
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
||||||
# VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
||||||
VITE_WS_BASE_URL=wss://api.topfans.online
|
# VITE_WS_BASE_URL=wss://api.topfans.online
|
||||||
# WebSocket 路径:用于 Nginx 反向代理(前端连接的完整 URL = VITE_WS_BASE_URL + VITE_WS_AI_CHAT_PATH)
|
# WebSocket 路径:用于 Nginx 反向代理(前端连接的完整 URL = VITE_WS_BASE_URL + VITE_WS_AI_CHAT_PATH)
|
||||||
# 需与后端 backend/.env 的 WS_AI_CHAT_PATH 保持一致
|
# 需与后端 backend/.env 的 WS_AI_CHAT_PATH 保持一致
|
||||||
# Nginx 示例:location /ai-chat { proxy_pass http://gateway:8080; ... }
|
# Nginx 示例:location /ai-chat { proxy_pass http://gateway:8080; ... }
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { getActivityRankingApi } from "@/utils/api.js";
|
import { getActivityTopRankingApi } from "@/utils/api.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
activityId: {
|
activityId: {
|
||||||
@ -98,28 +98,16 @@ function handleOpenRanking() {
|
|||||||
emit("open-ranking");
|
emit("open-ranking");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算「距离上一名贡献值」:取 (第 N-1 名的 total_contribution - 我的 total_contribution)
|
// 加载排行数据(使用专用轻量接口 /top-ranking)
|
||||||
function calcGapToPrev(myContribution, allItems) {
|
|
||||||
if (!myContribution || !Array.isArray(allItems)) return 0;
|
|
||||||
const myRank = myContribution.rank;
|
|
||||||
if (!myRank || myRank <= 1) return 0; // 第 1 名没有上一名
|
|
||||||
const prev = allItems.find((u) => u.rank === myRank - 1);
|
|
||||||
if (!prev) return 0;
|
|
||||||
const gap =
|
|
||||||
(prev.total_contribution || 0) - (myContribution.total_contribution || 0);
|
|
||||||
return gap > 0 ? gap : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载排行数据
|
|
||||||
async function loadRanking() {
|
async function loadRanking() {
|
||||||
if (!props.activityId) return;
|
if (!props.activityId) return;
|
||||||
try {
|
try {
|
||||||
const sid = props.starId || uni.getStorageSync("star_id");
|
const sid = props.starId || uni.getStorageSync("star_id");
|
||||||
const res = await getActivityRankingApi(props.activityId, sid, 1, 3);
|
const res = await getActivityTopRankingApi(props.activityId, sid);
|
||||||
if (res && res.code === 0 && res.data) {
|
if (res && res.code === 0 && res.data) {
|
||||||
const items = Array.isArray(res.data.items) ? res.data.items : [];
|
|
||||||
// TOP3
|
// TOP3
|
||||||
top3List.value = items
|
const top3 = Array.isArray(res.data.top3) ? res.data.top3 : [];
|
||||||
|
top3List.value = top3
|
||||||
.filter((u) => u.rank >= 1 && u.rank <= 3)
|
.filter((u) => u.rank >= 1 && u.rank <= 3)
|
||||||
.sort((a, b) => a.rank - b.rank)
|
.sort((a, b) => a.rank - b.rank)
|
||||||
.map((u) => ({
|
.map((u) => ({
|
||||||
@ -128,19 +116,19 @@ async function loadRanking() {
|
|||||||
avatar: u.avatar_url || "/static/avatar/1.jpeg",
|
avatar: u.avatar_url || "/static/avatar/1.jpeg",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 我的信息
|
// 我的信息(后端已下发 gap_to_prev)
|
||||||
const my = res.data.my_contribution;
|
const my = res.data.my_info;
|
||||||
if (my && my.rank) {
|
if (my && my.status === "ranked" && my.rank) {
|
||||||
myInfo.value = {
|
myInfo.value = {
|
||||||
rank: my.rank,
|
rank: my.rank,
|
||||||
avatar: my.avatar_url || "/static/avatar/1.jpeg",
|
avatar: my.avatar_url || "/static/avatar/1.jpeg",
|
||||||
gapToPrev: calcGapToPrev(my, items),
|
gapToPrev: typeof my.gap_to_prev === "number" ? my.gap_to_prev : 0,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 未购买过道具:仍展示卡片,排名显示"暂无排名",距离上一名显示 0
|
// 未上榜(status=unranked 或 my_info 缺失):仍展示卡片,排名显示"暂无排名"
|
||||||
myInfo.value = {
|
myInfo.value = {
|
||||||
rank: null,
|
rank: null,
|
||||||
avatar: getFallbackAvatar(),
|
avatar: my && my.avatar_url ? my.avatar_url : getFallbackAvatar(),
|
||||||
gapToPrev: 0,
|
gapToPrev: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -903,6 +903,18 @@ export function getActivityRankingApi(activityId, starId = null, page = 1, pageS
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取活动 TOP3 + 我的排名(专用轻量接口,TopRanking.vue 使用)
|
||||||
|
export function getActivityTopRankingApi(activityId, starId = null) {
|
||||||
|
let url = `/api/v1/activities/${activityId}/top-ranking`
|
||||||
|
if (starId) {
|
||||||
|
url += `?star_id=${starId}`
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: url,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 星册相关接口 ====================
|
// ==================== 星册相关接口 ====================
|
||||||
|
|
||||||
// 获取星册首页数据
|
// 获取星册首页数据
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user