270 lines
11 KiB
Go
270 lines
11 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)
|
|
}
|
|
|
|
// DeleteAchievementDefinition 采用归档删除,避免后台删除动作破坏用户已获得徽章和历史成就事实。
|
|
func (h *Handler) DeleteAchievementDefinition(c *gin.Context) {
|
|
achievementID := strings.TrimSpace(c.Param("achievement_id"))
|
|
if achievementID == "" {
|
|
response.BadRequest(c, "ID 参数不正确")
|
|
return
|
|
}
|
|
resp, err := h.activity.DeleteAchievementDefinition(c.Request.Context(), &activityv1.DeleteAchievementDefinitionRequest{
|
|
Meta: h.meta(c),
|
|
AchievementId: achievementID,
|
|
OperatorAdminId: int64(middleware.CurrentUserID(c)),
|
|
})
|
|
if err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
item := achievementFromProto(resp.GetAchievement())
|
|
h.auditLog(c, "delete-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 ""
|
|
}
|