2026-05-14 17:44:16 +08:00

249 lines
10 KiB
Go

package achievementconfig
import (
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
)
type Handler struct {
activity activityclient.Client
audit shared.OperationLogger
}
func New(activity activityclient.Client, audit shared.OperationLogger) *Handler {
return &Handler{activity: activity, audit: audit}
}
type achievementRequest struct {
CollectionID string `json:"collection_id"`
CollectionType string `json:"collection_type"`
AchievementType string `json:"achievement_type"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
PrimaryBadgeResourceID int64 `json:"primary_badge_resource_id"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
AutoPinPolicy string `json:"auto_pin_policy"`
HonorVisibility bool `json:"honor_visibility"`
EffectiveFromMS int64 `json:"effective_from_ms"`
EffectiveToMS int64 `json:"effective_to_ms"`
SortOrder int32 `json:"sort_order"`
DisplayConfigJSON string `json:"display_config_json"`
Conditions []conditionRequest `json:"conditions"`
}
type conditionRequest struct {
MetricType string `json:"metric_type"`
TargetValue int64 `json:"target_value"`
TargetUnit string `json:"target_unit"`
DimensionFilterJSON string `json:"dimension_filter_json"`
}
type achievementDTO struct {
AchievementID string `json:"achievement_id"`
CollectionID string `json:"collection_id"`
CollectionType string `json:"collection_type"`
AchievementType string `json:"achievement_type"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
Version int64 `json:"version"`
PrimaryBadgeResourceID int64 `json:"primary_badge_resource_id"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
AutoPinPolicy string `json:"auto_pin_policy"`
HonorVisibility bool `json:"honor_visibility"`
EffectiveFromMS int64 `json:"effective_from_ms"`
EffectiveToMS int64 `json:"effective_to_ms"`
SortOrder int32 `json:"sort_order"`
DisplayConfigJSON string `json:"display_config_json"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
Conditions []conditionDTO `json:"conditions"`
}
type conditionDTO struct {
ConditionID string `json:"condition_id"`
AchievementID string `json:"achievement_id"`
Version int64 `json:"version"`
MetricType string `json:"metric_type"`
TargetValue int64 `json:"target_value"`
TargetUnit string `json:"target_unit"`
DimensionFilterJSON string `json:"dimension_filter_json"`
}
// ListAchievementDefinitions exposes the achievement rule catalog for operations; no user progress is mixed into this read.
func (h *Handler) ListAchievementDefinitions(c *gin.Context) {
options := shared.ListOptions(c)
resp, err := h.activity.ListAchievementDefinitions(c.Request.Context(), &activityv1.ListAchievementsRequest{
Meta: h.meta(c),
CollectionType: strings.TrimSpace(firstQuery(c, "collection_type", "collectionType")),
CollectionId: strings.TrimSpace(firstQuery(c, "collection_id", "collectionId")),
Page: int32(options.Page),
PageSize: int32(options.PageSize),
})
if err != nil {
response.ServerError(c, "获取成就配置失败")
return
}
items := make([]achievementDTO, 0, len(resp.GetAchievements()))
for _, item := range resp.GetAchievements() {
items = append(items, achievementFromProto(item.GetDefinition()))
}
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
// CreateAchievementDefinition writes one current-version achievement rule; reward badges are granted through the selected resource group.
func (h *Handler) CreateAchievementDefinition(c *gin.Context) {
var req achievementRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "成就参数不正确")
return
}
resp, err := h.activity.UpsertAchievementDefinition(c.Request.Context(), req.toProto(c, "", h.meta(c)))
if err != nil {
response.BadRequest(c, err.Error())
return
}
item := achievementFromProto(resp.GetAchievement())
h.auditLog(c, "create-achievement", item.AchievementID, item.Title)
response.Created(c, item)
}
// UpdateAchievementDefinition rewrites the definition and condition set; activity-service owns versioning and unlock semantics.
func (h *Handler) UpdateAchievementDefinition(c *gin.Context) {
achievementID := strings.TrimSpace(c.Param("achievement_id"))
if achievementID == "" {
response.BadRequest(c, "ID 参数不正确")
return
}
var req achievementRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "成就参数不正确")
return
}
resp, err := h.activity.UpsertAchievementDefinition(c.Request.Context(), req.toProto(c, achievementID, h.meta(c)))
if err != nil {
response.BadRequest(c, err.Error())
return
}
item := achievementFromProto(resp.GetAchievement())
h.auditLog(c, "update-achievement", item.AchievementID, item.Title)
response.OK(c, item)
}
func (r achievementRequest) toProto(c *gin.Context, achievementID string, meta *activityv1.RequestMeta) *activityv1.UpsertAchievementDefinitionRequest {
conditions := make([]*activityv1.AchievementCondition, 0, len(r.Conditions))
for _, condition := range r.Conditions {
conditions = append(conditions, &activityv1.AchievementCondition{
MetricType: strings.TrimSpace(condition.MetricType),
TargetValue: condition.TargetValue,
TargetUnit: strings.TrimSpace(condition.TargetUnit),
DimensionFilterJson: strings.TrimSpace(condition.DimensionFilterJSON),
})
}
return &activityv1.UpsertAchievementDefinitionRequest{
Meta: meta,
AchievementId: strings.TrimSpace(achievementID),
CollectionId: strings.TrimSpace(r.CollectionID),
CollectionType: strings.TrimSpace(r.CollectionType),
AchievementType: strings.TrimSpace(r.AchievementType),
Title: strings.TrimSpace(r.Title),
Description: strings.TrimSpace(r.Description),
Status: strings.TrimSpace(r.Status),
PrimaryBadgeResourceId: r.PrimaryBadgeResourceID,
RewardResourceGroupId: r.RewardResourceGroupID,
AutoPinPolicy: strings.TrimSpace(r.AutoPinPolicy),
HonorVisibility: r.HonorVisibility,
EffectiveFromMs: r.EffectiveFromMS,
EffectiveToMs: r.EffectiveToMS,
SortOrder: r.SortOrder,
DisplayConfigJson: strings.TrimSpace(r.DisplayConfigJSON),
OperatorAdminId: int64(middleware.CurrentUserID(c)),
Conditions: conditions,
}
}
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: middleware.CurrentRequestID(c),
Caller: "admin-server",
AppCode: appctx.FromContext(c.Request.Context()),
SentAtMs: time.Now().UnixMilli(),
}
}
func (h *Handler) auditLog(c *gin.Context, action string, achievementID string, detail string) {
if h.audit == nil {
return
}
shared.OperationLogWithResourceID(c, h.audit, action, "achievement_definitions", achievementID, "success", detail)
}
func achievementFromProto(item *activityv1.AchievementDefinition) achievementDTO {
if item == nil {
return achievementDTO{}
}
conditions := make([]conditionDTO, 0, len(item.GetConditions()))
for _, condition := range item.GetConditions() {
conditions = append(conditions, conditionFromProto(condition))
}
return achievementDTO{
AchievementID: item.GetAchievementId(),
CollectionID: item.GetCollectionId(),
CollectionType: item.GetCollectionType(),
AchievementType: item.GetAchievementType(),
Title: item.GetTitle(),
Description: item.GetDescription(),
Status: item.GetStatus(),
Version: item.GetVersion(),
PrimaryBadgeResourceID: item.GetPrimaryBadgeResourceId(),
RewardResourceGroupID: item.GetRewardResourceGroupId(),
AutoPinPolicy: item.GetAutoPinPolicy(),
HonorVisibility: item.GetHonorVisibility(),
EffectiveFromMS: item.GetEffectiveFromMs(),
EffectiveToMS: item.GetEffectiveToMs(),
SortOrder: item.GetSortOrder(),
DisplayConfigJSON: item.GetDisplayConfigJson(),
CreatedByAdminID: item.GetCreatedByAdminId(),
UpdatedByAdminID: item.GetUpdatedByAdminId(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
Conditions: conditions,
}
}
func conditionFromProto(item *activityv1.AchievementCondition) conditionDTO {
if item == nil {
return conditionDTO{}
}
return conditionDTO{
ConditionID: item.GetConditionId(),
AchievementID: item.GetAchievementId(),
Version: item.GetVersion(),
MetricType: item.GetMetricType(),
TargetValue: item.GetTargetValue(),
TargetUnit: item.GetTargetUnit(),
DimensionFilterJSON: item.GetDimensionFilterJson(),
}
}
func firstQuery(c *gin.Context, keys ...string) string {
for _, key := range keys {
if value := c.Query(key); value != "" {
return value
}
}
return ""
}