280 lines
9.6 KiB
Go
280 lines
9.6 KiB
Go
package userbadge
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type badgePictureRow struct {
|
|
BadgeID int64 `gorm:"column:badge_id"`
|
|
SourceType sql.NullString `gorm:"column:source_type"`
|
|
DisplayType sql.NullString `gorm:"column:display_type"`
|
|
BadgeLevel sql.NullInt64 `gorm:"column:badge_level"`
|
|
Milestone sql.NullInt64 `gorm:"column:milestone"`
|
|
BadgeName sql.NullString `gorm:"column:badge_name"`
|
|
BadgeKey sql.NullString `gorm:"column:badge_key"`
|
|
SelectURL sql.NullString `gorm:"column:select_url"`
|
|
NotSelectURL sql.NullString `gorm:"column:not_select_url"`
|
|
AnimationURL sql.NullString `gorm:"column:animation_url"`
|
|
ExpireTime time.Time `gorm:"column:expire_time"`
|
|
UpdateTime time.Time `gorm:"column:update_time"`
|
|
}
|
|
|
|
// Current returns the current user's badge list split by long and short display type.
|
|
func (s *Service) Current(ctx context.Context, user AuthUser) (*CurrentBadgeListResponse, error) {
|
|
if s == nil || s.db == nil {
|
|
return nil, NewAppError(http.StatusServiceUnavailable, "database_unavailable", "database is unavailable")
|
|
}
|
|
sysOrigin, err := normalizeUser(user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.currentForUser(ctx, sysOrigin, user.UserID)
|
|
}
|
|
|
|
// Other returns another user's badge list in the current authenticated sysOrigin.
|
|
func (s *Service) Other(ctx context.Context, viewer AuthUser, targetUserID int64) (*CurrentBadgeListResponse, error) {
|
|
if s == nil || s.db == nil {
|
|
return nil, NewAppError(http.StatusServiceUnavailable, "database_unavailable", "database is unavailable")
|
|
}
|
|
sysOrigin, err := normalizeUser(viewer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if targetUserID <= 0 {
|
|
return nil, NewAppError(http.StatusBadRequest, "invalid_target_user_id", "target userId is required")
|
|
}
|
|
return s.currentForUser(ctx, sysOrigin, targetUserID)
|
|
}
|
|
|
|
func (s *Service) currentForUser(ctx context.Context, sysOrigin string, userID int64) (*CurrentBadgeListResponse, error) {
|
|
now := s.now()
|
|
vipLongBadges, vipShortBadges, vipBadgeIDs, err := s.loadVIPStateBadges(ctx, sysOrigin, userID, now)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
backpackLongBadges, backpackShortBadges, err := s.loadBackpackBadges(ctx, sysOrigin, userID, vipBadgeIDs, now)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
longBadges := append(vipLongBadges, backpackLongBadges...)
|
|
shortBadges := append(vipShortBadges, backpackShortBadges...)
|
|
|
|
badges := make([]CurrentBadgeItem, 0, len(longBadges)+len(shortBadges))
|
|
badges = append(badges, longBadges...)
|
|
badges = append(badges, shortBadges...)
|
|
|
|
return &CurrentBadgeListResponse{
|
|
UserID: userID,
|
|
SysOrigin: sysOrigin,
|
|
Badges: badges,
|
|
LongBadges: longBadges,
|
|
ShortBadges: shortBadges,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) loadVIPStateBadges(ctx context.Context, sysOrigin string, userID int64, now time.Time) ([]CurrentBadgeItem, []CurrentBadgeItem, map[int64]struct{}, error) {
|
|
var state model.UserVipState
|
|
err := s.db.WithContext(ctx).
|
|
Where("sys_origin = ? AND user_id = ?", sysOrigin, userID).
|
|
First(&state).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return []CurrentBadgeItem{}, []CurrentBadgeItem{}, nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if !isActiveVIPState(state, now) {
|
|
return []CurrentBadgeItem{}, []CurrentBadgeItem{}, nil, nil
|
|
}
|
|
|
|
type vipStateBadgeResource struct {
|
|
id int64
|
|
defaultType string
|
|
name string
|
|
url string
|
|
}
|
|
resources := []vipStateBadgeResource{
|
|
{id: state.BadgeResourceID, defaultType: badgeDisplayTypeLong, name: state.BadgeName, url: state.BadgeURL},
|
|
{id: state.ShortBadgeResourceID, defaultType: badgeDisplayTypeShort, name: state.ShortBadgeName, url: state.ShortBadgeURL},
|
|
}
|
|
|
|
longBadges := make([]CurrentBadgeItem, 0, 1)
|
|
shortBadges := make([]CurrentBadgeItem, 0, 1)
|
|
excludeIDs := make(map[int64]struct{}, len(resources))
|
|
updateAt := firstNonZeroTime(state.UpdateTime, state.StartAt, state.CreateTime)
|
|
for _, resource := range resources {
|
|
if resource.id <= 0 {
|
|
continue
|
|
}
|
|
if _, exists := excludeIDs[resource.id]; exists {
|
|
continue
|
|
}
|
|
excludeIDs[resource.id] = struct{}{}
|
|
row, err := s.loadBadgePicture(ctx, sysOrigin, resource.id)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
displayType := normalizeBadgeDisplayType(nullString(row.DisplayType), nullString(row.SourceType), resource.defaultType)
|
|
if displayType == "" {
|
|
continue
|
|
}
|
|
item := CurrentBadgeItem{
|
|
ID: formatID(resource.id),
|
|
BadgeID: formatID(resource.id),
|
|
Type: displayType,
|
|
SourceType: nullString(row.SourceType),
|
|
BadgeLevel: nullInt(row.BadgeLevel),
|
|
BadgeKey: nullString(row.BadgeKey),
|
|
Level: state.Level,
|
|
LevelCode: state.LevelCode,
|
|
DisplayName: state.DisplayName,
|
|
Use: true,
|
|
ExpireTime: formatMillis(state.ExpireAt),
|
|
ExpireAt: formatTime(state.ExpireAt),
|
|
UpdateTime: formatMillis(updateAt),
|
|
UpdatedAt: formatTime(updateAt),
|
|
}
|
|
item.BadgeName = firstNonEmpty(nullString(row.BadgeName), resource.name, state.DisplayName)
|
|
item.Name = item.BadgeName
|
|
item.SelectURL = firstNonEmpty(nullString(row.SelectURL), resource.url)
|
|
item.NotSelectURL = nullString(row.NotSelectURL)
|
|
item.AnimationURL = nullString(row.AnimationURL)
|
|
item.URL = firstNonEmpty(item.SelectURL, resource.url)
|
|
if row.Milestone.Valid && row.Milestone.Int64 > 0 {
|
|
item.Milestone = formatID(row.Milestone.Int64)
|
|
}
|
|
if item.Name == "" && item.URL == "" {
|
|
continue
|
|
}
|
|
if displayType == badgeDisplayTypeLong {
|
|
longBadges = append(longBadges, item)
|
|
} else {
|
|
shortBadges = append(shortBadges, item)
|
|
}
|
|
}
|
|
return longBadges, shortBadges, excludeIDs, nil
|
|
}
|
|
|
|
func (s *Service) loadBackpackBadges(ctx context.Context, sysOrigin string, userID int64, excludeBadgeIDs map[int64]struct{}, now time.Time) ([]CurrentBadgeItem, []CurrentBadgeItem, error) {
|
|
rows := make([]badgePictureRow, 0)
|
|
query := s.db.WithContext(ctx).
|
|
Table("user_badge_backpack AS backpack").
|
|
Select(`
|
|
backpack.badge_id AS badge_id,
|
|
backpack.expire_time AS expire_time,
|
|
backpack.update_time AS update_time,
|
|
config.type AS source_type,
|
|
config.display_type AS display_type,
|
|
config.badge_level AS badge_level,
|
|
config.milestone AS milestone,
|
|
config.badge_name AS badge_name,
|
|
config.badge_key AS badge_key,
|
|
picture.select_url AS select_url,
|
|
picture.not_select_url AS not_select_url,
|
|
picture.animation_url AS animation_url
|
|
`).
|
|
Joins("JOIN sys_badge_config AS config ON config.id = backpack.badge_id").
|
|
Joins("JOIN sys_badge_picture_config AS picture ON picture.badge_config_id = config.id AND picture.sys_origin = ?", sysOrigin).
|
|
Where("backpack.user_id = ?", userID).
|
|
Where("backpack.is_use_props = ?", true).
|
|
Where("config.is_del = ?", false).
|
|
Where("(backpack.expire_type = ? OR backpack.expire_time > ?)", expireTypePermanent, now).
|
|
Order("backpack.update_time DESC, backpack.id DESC")
|
|
if len(excludeBadgeIDs) > 0 {
|
|
ids := make([]int64, 0, len(excludeBadgeIDs))
|
|
for id := range excludeBadgeIDs {
|
|
ids = append(ids, id)
|
|
}
|
|
query = query.Where("backpack.badge_id NOT IN ?", ids)
|
|
}
|
|
if err := query.Scan(&rows).Error; err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
longBadges := make([]CurrentBadgeItem, 0)
|
|
shortBadges := make([]CurrentBadgeItem, 0, len(rows))
|
|
for _, row := range rows {
|
|
displayType := normalizeBadgeDisplayType(nullString(row.DisplayType), nullString(row.SourceType), "")
|
|
if displayType == "" {
|
|
continue
|
|
}
|
|
item := CurrentBadgeItem{
|
|
ID: formatID(row.BadgeID),
|
|
BadgeID: formatID(row.BadgeID),
|
|
Type: displayType,
|
|
SourceType: nullString(row.SourceType),
|
|
Name: nullString(row.BadgeName),
|
|
BadgeName: nullString(row.BadgeName),
|
|
BadgeKey: nullString(row.BadgeKey),
|
|
BadgeLevel: nullInt(row.BadgeLevel),
|
|
URL: nullString(row.SelectURL),
|
|
SelectURL: nullString(row.SelectURL),
|
|
NotSelectURL: nullString(row.NotSelectURL),
|
|
AnimationURL: nullString(row.AnimationURL),
|
|
Use: true,
|
|
ExpireTime: formatMillis(row.ExpireTime),
|
|
ExpireAt: formatTime(row.ExpireTime),
|
|
UpdateTime: formatMillis(row.UpdateTime),
|
|
UpdatedAt: formatTime(row.UpdateTime),
|
|
}
|
|
if row.Milestone.Valid && row.Milestone.Int64 > 0 {
|
|
item.Milestone = formatID(row.Milestone.Int64)
|
|
}
|
|
if displayType == badgeDisplayTypeLong {
|
|
longBadges = append(longBadges, item)
|
|
} else {
|
|
shortBadges = append(shortBadges, item)
|
|
}
|
|
}
|
|
return longBadges, shortBadges, nil
|
|
}
|
|
|
|
func (s *Service) loadBadgePicture(ctx context.Context, sysOrigin string, badgeID int64) (badgePictureRow, error) {
|
|
var row badgePictureRow
|
|
err := s.db.WithContext(ctx).
|
|
Table("sys_badge_config AS config").
|
|
Select(`
|
|
config.id AS badge_id,
|
|
config.type AS source_type,
|
|
config.display_type AS display_type,
|
|
config.badge_level AS badge_level,
|
|
config.milestone AS milestone,
|
|
config.badge_name AS badge_name,
|
|
config.badge_key AS badge_key,
|
|
picture.select_url AS select_url,
|
|
picture.not_select_url AS not_select_url,
|
|
picture.animation_url AS animation_url
|
|
`).
|
|
Joins("LEFT JOIN sys_badge_picture_config AS picture ON picture.badge_config_id = config.id AND picture.sys_origin = ?", sysOrigin).
|
|
Where("config.id = ? AND config.is_del = ?", badgeID, false).
|
|
Scan(&row).Error
|
|
return row, err
|
|
}
|
|
|
|
func isActiveVIPState(state model.UserVipState, now time.Time) bool {
|
|
return strings.EqualFold(strings.TrimSpace(state.Status), statusActive) && state.Level > 0 && state.ExpireAt.After(now)
|
|
}
|
|
|
|
func nullString(value sql.NullString) string {
|
|
if !value.Valid {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(value.String)
|
|
}
|
|
|
|
func nullInt(value sql.NullInt64) int {
|
|
if !value.Valid {
|
|
return 0
|
|
}
|
|
return int(value.Int64)
|
|
}
|