同步历史版本代码
This commit is contained in:
@@ -10,7 +10,7 @@ import (
|
||||
type Auth struct {
|
||||
Id int64 `gorm:"primaryKey"`
|
||||
Method string `gorm:"unique;type:varchar(255);not null;default:'';comment:platform"`
|
||||
Config string `gorm:"type:text;not null;comment:Auth Configuration"`
|
||||
Config string `gorm:"type:mediumtext;not null;comment:Auth Configuration"`
|
||||
Enabled *bool `gorm:"type:tinyint(1);not null;default:false;comment:Is Enabled"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||
@@ -129,7 +129,7 @@ func (l *EmailAuthConfig) Marshal() string {
|
||||
if l.ExpirationEmailTemplate == "" {
|
||||
l.ExpirationEmailTemplate = email.DefaultExpirationEmailTemplate
|
||||
}
|
||||
if l.ExpirationEmailTemplate == "" {
|
||||
if l.MaintenanceEmailTemplate == "" {
|
||||
l.MaintenanceEmailTemplate = email.DefaultMaintenanceEmailTemplate
|
||||
}
|
||||
if l.TrafficExceedEmailTemplate == "" {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package apple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/cache"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Model interface {
|
||||
Insert(ctx context.Context, data *Transaction, tx ...*gorm.DB) error
|
||||
FindByOriginalId(ctx context.Context, originalId string) (*Transaction, error)
|
||||
FindByUserAndProduct(ctx context.Context, userId int64, productId string) (*Transaction, error)
|
||||
}
|
||||
|
||||
type defaultModel struct {
|
||||
cache.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
type customModel struct {
|
||||
*defaultModel
|
||||
}
|
||||
|
||||
func NewModel(db *gorm.DB, c *redis.Client) Model {
|
||||
return &customModel{
|
||||
defaultModel: &defaultModel{
|
||||
CachedConn: cache.NewConn(db, c),
|
||||
table: "`apple_iap_transactions`",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultModel) jwsKey(jws string) string {
|
||||
sum := sha256.Sum256([]byte(jws))
|
||||
return fmt.Sprintf("cache:iap:jws:%s", hex.EncodeToString(sum[:]))
|
||||
}
|
||||
|
||||
func (m *customModel) Insert(ctx context.Context, data *Transaction, tx ...*gorm.DB) error {
|
||||
return m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||
if len(tx) > 0 {
|
||||
conn = tx[0]
|
||||
}
|
||||
return conn.Model(&Transaction{}).Create(data).Error
|
||||
}, m.jwsKey(data.JWSHash))
|
||||
}
|
||||
|
||||
func (m *customModel) FindByOriginalId(ctx context.Context, originalId string) (*Transaction, error) {
|
||||
var data Transaction
|
||||
key := fmt.Sprintf("cache:iap:original:%s", originalId)
|
||||
err := m.QueryCtx(ctx, &data, key, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Model(&Transaction{}).Where("original_transaction_id = ?", originalId).First(&data).Error
|
||||
})
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func (m *customModel) FindByUserAndProduct(ctx context.Context, userId int64, productId string) (*Transaction, error) {
|
||||
var data Transaction
|
||||
err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Model(&Transaction{}).Where("user_id = ? AND product_id = ?", userId, productId).Order("purchase_at DESC").First(&data).Error
|
||||
})
|
||||
return &data, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package apple
|
||||
|
||||
import "time"
|
||||
|
||||
type Transaction struct {
|
||||
Id int64 `gorm:"primaryKey"`
|
||||
UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"`
|
||||
OriginalTransactionId string `gorm:"type:varchar(255);uniqueIndex:uni_original;not null;comment:Original Transaction ID"`
|
||||
TransactionId string `gorm:"type:varchar(255);not null;comment:Transaction ID"`
|
||||
ProductId string `gorm:"type:varchar(255);not null;comment:Product ID"`
|
||||
PurchaseAt time.Time `gorm:"not null;comment:Purchase Time"`
|
||||
RevocationAt *time.Time `gorm:"comment:Revocation Time"`
|
||||
JWSHash string `gorm:"type:varchar(255);not null;comment:JWS Hash"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||
}
|
||||
|
||||
func (Transaction) TableName() string {
|
||||
return "apple_iap_transactions"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package logmessage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type (
|
||||
Model interface {
|
||||
logMessageModel
|
||||
customLogMessageModel
|
||||
}
|
||||
logMessageModel interface {
|
||||
Insert(ctx context.Context, data *LogMessage) error
|
||||
FindOne(ctx context.Context, id int64) (*LogMessage, error)
|
||||
Update(ctx context.Context, data *LogMessage) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
customModel struct{
|
||||
*defaultModel
|
||||
}
|
||||
defaultModel struct{
|
||||
*gorm.DB
|
||||
}
|
||||
)
|
||||
|
||||
func newDefaultModel(db *gorm.DB) *defaultModel {
|
||||
return &defaultModel{DB: db}
|
||||
}
|
||||
|
||||
func (m *defaultModel) Insert(ctx context.Context, data *LogMessage) error {
|
||||
return m.WithContext(ctx).Create(data).Error
|
||||
}
|
||||
|
||||
func (m *defaultModel) FindOne(ctx context.Context, id int64) (*LogMessage, error) {
|
||||
var v LogMessage
|
||||
err := m.WithContext(ctx).Where("id = ?", id).First(&v).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (m *defaultModel) Update(ctx context.Context, data *LogMessage) error {
|
||||
return m.WithContext(ctx).Where("`id` = ?", data.Id).Save(data).Error
|
||||
}
|
||||
|
||||
func (m *defaultModel) Delete(ctx context.Context, id int64) error {
|
||||
return m.WithContext(ctx).Where("`id` = ?", id).Delete(&LogMessage{}).Error
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package logmessage
|
||||
|
||||
import "time"
|
||||
|
||||
type LogMessage struct {
|
||||
Id int64 `gorm:"primaryKey;AUTO_INCREMENT"`
|
||||
Platform string `gorm:"type:varchar(32);not null"`
|
||||
AppVersion string `gorm:"type:varchar(32);default:null"`
|
||||
OsName string `gorm:"type:varchar(32);default:null"`
|
||||
OsVersion string `gorm:"type:varchar(32);default:null"`
|
||||
DeviceId string `gorm:"type:varchar(64);default:null"`
|
||||
UserId *int64 `gorm:"type:bigint;default:null"`
|
||||
SessionId string `gorm:"type:varchar(64);default:null"`
|
||||
Level uint8 `gorm:"type:tinyint(1);not null;default:3"`
|
||||
ErrorCode string `gorm:"type:varchar(64);default:null"`
|
||||
Message string `gorm:"type:text;not null"`
|
||||
Stack string `gorm:"type:mediumtext;default:null"`
|
||||
Context string `gorm:"type:json;default:null"`
|
||||
ClientIP string `gorm:"type:varchar(45);default:null"`
|
||||
UserAgent string `gorm:"type:varchar(255);default:null"`
|
||||
Locale string `gorm:"type:varchar(16);default:null"`
|
||||
Digest string `gorm:"type:varchar(64);uniqueIndex:uniq_digest;default:null"`
|
||||
OccurredAt *time.Time `gorm:"type:datetime;default:null"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||
}
|
||||
|
||||
func (LogMessage) TableName() string { return "log_message" }
|
||||
@@ -0,0 +1,52 @@
|
||||
package logmessage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func NewModel(db *gorm.DB) Model {
|
||||
return &customModel{ defaultModel: newDefaultModel(db) }
|
||||
}
|
||||
|
||||
type FilterParams struct {
|
||||
Page int
|
||||
Size int
|
||||
Platform string
|
||||
Level uint8
|
||||
UserID int64
|
||||
DeviceID string
|
||||
ErrorCode string
|
||||
Keyword string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
|
||||
type customLogMessageModel interface {
|
||||
Filter(ctx context.Context, filter *FilterParams) ([]*LogMessage, int64, error)
|
||||
}
|
||||
|
||||
func (m *customModel) Filter(ctx context.Context, filter *FilterParams) ([]*LogMessage, int64, error) {
|
||||
tx := m.WithContext(ctx).Model(&LogMessage{}).Order("id DESC")
|
||||
if filter == nil {
|
||||
filter = &FilterParams{ Page: 1, Size: 10 }
|
||||
}
|
||||
if filter.Page < 1 { filter.Page = 1 }
|
||||
if filter.Size < 1 { filter.Size = 10 }
|
||||
if filter.Platform != "" { tx = tx.Where("`platform` = ?", filter.Platform) }
|
||||
if filter.Level != 0 { tx = tx.Where("`level` = ?", filter.Level) }
|
||||
if filter.UserID != 0 { tx = tx.Where("`user_id` = ?", filter.UserID) }
|
||||
if filter.DeviceID != "" { tx = tx.Where("`device_id` = ?", filter.DeviceID) }
|
||||
if filter.ErrorCode != "" { tx = tx.Where("`error_code` = ?", filter.ErrorCode) }
|
||||
if !filter.Start.IsZero() { tx = tx.Where("`created_at` >= ?", filter.Start) }
|
||||
if !filter.End.IsZero() { tx = tx.Where("`created_at` <= ?", filter.End) }
|
||||
if filter.Keyword != "" {
|
||||
like := "%" + filter.Keyword + "%"
|
||||
tx = tx.Where("`message` LIKE ? OR `stack` LIKE ?", like, like)
|
||||
}
|
||||
var total int64
|
||||
var rows []*LogMessage
|
||||
err := tx.Count(&total).Limit(filter.Size).Offset((filter.Page-1)*filter.Size).Find(&rows).Error
|
||||
return rows, total, err
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type customServerLogicModel interface {
|
||||
FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error)
|
||||
ClearNodeCache(ctx context.Context, params *FilterNodeParams) error
|
||||
ClearServerAllCache(ctx context.Context) error
|
||||
CountNodesByIdsAndTags(ctx context.Context, nodeIds []int64, tags []string) (int64, error)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -129,7 +130,7 @@ func (m *customServerModel) ClearNodeCache(ctx context.Context, params *FilterNo
|
||||
return err
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
cacheKeys = append(keys, keys...)
|
||||
cacheKeys = append(cacheKeys, keys...)
|
||||
}
|
||||
cursor = newCursor
|
||||
if cursor == 0 {
|
||||
@@ -152,7 +153,7 @@ func (m *customServerModel) ClearServerCache(ctx context.Context, serverId int64
|
||||
cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, serverId))
|
||||
var cursor uint64
|
||||
for {
|
||||
keys, newCursor, err := m.Cache.Scan(ctx, 0, fmt.Sprintf("%s%d*", ServerConfigCacheKey, serverId), 100).Result()
|
||||
keys, newCursor, err := m.Cache.Scan(ctx, cursor, fmt.Sprintf("%s%d*", ServerConfigCacheKey, serverId), 100).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -175,18 +176,21 @@ func (m *customServerModel) ClearServerCache(ctx context.Context, serverId int64
|
||||
func (m *customServerModel) ClearServerAllCache(ctx context.Context) error {
|
||||
var cursor uint64
|
||||
var keys []string
|
||||
prefix := ServerUserListCacheKey + "*"
|
||||
for {
|
||||
scanKeys, newCursor, err := m.Cache.Scan(ctx, cursor, prefix, 999).Result()
|
||||
if err != nil {
|
||||
m.Logger.Error(ctx, fmt.Sprintf("ClearServerAllCache err:%v", err))
|
||||
break
|
||||
}
|
||||
m.Logger.Info(ctx, fmt.Sprintf("ClearServerAllCache query keys:%v", scanKeys))
|
||||
keys = append(keys, scanKeys...)
|
||||
cursor = newCursor
|
||||
if cursor == 0 {
|
||||
break
|
||||
prefixes := []string{ServerConfigCacheKey + "*", ServerUserListCacheKey + "*"}
|
||||
for _, prefix := range prefixes {
|
||||
cursor = 0
|
||||
for {
|
||||
scanKeys, newCursor, err := m.Cache.Scan(ctx, cursor, prefix, 999).Result()
|
||||
if err != nil {
|
||||
m.Logger.Error(ctx, fmt.Sprintf("ClearServerAllCache err:%v", err))
|
||||
break
|
||||
}
|
||||
m.Logger.Info(ctx, fmt.Sprintf("ClearServerAllCache query keys:%v", scanKeys))
|
||||
keys = append(keys, scanKeys...)
|
||||
cursor = newCursor
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
@@ -196,6 +200,29 @@ func (m *customServerModel) ClearServerAllCache(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountNodesByIdsAndTags 根据节点ID和标签计算启用的节点数量
|
||||
func (m *customServerModel) CountNodesByIdsAndTags(ctx context.Context, nodeIds []int64, tags []string) (int64, error) {
|
||||
var count int64
|
||||
query := m.WithContext(ctx).Model(&Node{}).Where("enabled = ?", true)
|
||||
|
||||
if len(nodeIds) > 0 || len(tags) > 0 {
|
||||
subQuery := m.WithContext(ctx).Model(&Node{}).Where("enabled = ?", true)
|
||||
|
||||
if len(nodeIds) > 0 && len(tags) > 0 {
|
||||
subQuery = subQuery.Where("id IN ? OR ?", nodeIds, InSet("tags", tags))
|
||||
} else if len(nodeIds) > 0 {
|
||||
subQuery = subQuery.Where("id IN ?", nodeIds)
|
||||
} else {
|
||||
subQuery = subQuery.Scopes(InSet("tags", tags))
|
||||
}
|
||||
|
||||
query = subQuery
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// InSet 支持多值 OR 查询
|
||||
func InSet(field string, values []string) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
|
||||
@@ -12,6 +12,7 @@ type customPaymentLogicModel interface {
|
||||
FindAll(ctx context.Context) ([]*Payment, error)
|
||||
FindListByPage(ctx context.Context, page, size int, req *Filter) (int64, []*Payment, error)
|
||||
FindAvailableMethods(ctx context.Context) ([]*Payment, error)
|
||||
FindListByPlatform(ctx context.Context, platform string) ([]*Payment, error)
|
||||
}
|
||||
|
||||
// NewModel returns a model for the database table.
|
||||
@@ -67,3 +68,11 @@ func (m *customPaymentModel) FindListByPage(ctx context.Context, page, size int,
|
||||
})
|
||||
return total, resp, err
|
||||
}
|
||||
|
||||
func (m *customPaymentModel) FindListByPlatform(ctx context.Context, platform string) ([]*Payment, error) {
|
||||
var resp []*Payment
|
||||
err := m.QueryNoCacheCtx(ctx, &resp, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Model(&Payment{}).Where("mark = ?", platform).Find(v).Error
|
||||
})
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -127,3 +127,26 @@ func (l *CryptoSaaSConfig) Unmarshal(data []byte) error {
|
||||
aux := (*Alias)(l)
|
||||
return json.Unmarshal(data, &aux)
|
||||
}
|
||||
|
||||
type AppleIAPConfig struct {
|
||||
ProductIds []string `json:"product_ids"`
|
||||
KeyID string `json:"key_id"`
|
||||
IssuerID string `json:"issuer_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
Sandbox bool `json:"sandbox"`
|
||||
}
|
||||
|
||||
func (l *AppleIAPConfig) Marshal() ([]byte, error) {
|
||||
type Alias AppleIAPConfig
|
||||
return json.Marshal(&struct {
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(l),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *AppleIAPConfig) Unmarshal(data []byte) error {
|
||||
type Alias AppleIAPConfig
|
||||
aux := (*Alias)(l)
|
||||
return json.Unmarshal(data, &aux)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,18 @@ func (m *customUserModel) QueryDeviceList(ctx context.Context, userId int64) ([]
|
||||
return list, total, err
|
||||
}
|
||||
|
||||
func (m *customUserModel) QueryDeviceListByUserIds(ctx context.Context, userIds []int64) ([]*Device, int64, error) {
|
||||
var list []*Device
|
||||
var total int64
|
||||
if len(userIds) == 0 {
|
||||
return list, total, nil
|
||||
}
|
||||
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Model(&Device{}).Where("`user_id` IN ?", userIds).Count(&total).Find(&list).Error
|
||||
})
|
||||
return list, total, err
|
||||
}
|
||||
|
||||
func (m *customUserModel) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
||||
old, err := m.FindOneDevice(ctx, data.Id)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
FamilyStatusActive uint8 = 1
|
||||
FamilyRoleOwner uint8 = 1
|
||||
FamilyRoleMember uint8 = 2
|
||||
FamilyMemberActive uint8 = 1
|
||||
FamilyMemberLeft uint8 = 2
|
||||
FamilyMemberRemoved uint8 = 3
|
||||
DefaultFamilyMaxSize int64 = 5
|
||||
)
|
||||
|
||||
type UserFamily struct {
|
||||
Id int64 `gorm:"primaryKey"`
|
||||
OwnerUserId int64 `gorm:"uniqueIndex:uniq_owner_user_id;not null;comment:Owner User ID"`
|
||||
MaxMembers int64 `gorm:"not null;default:5;comment:Max members in family"`
|
||||
Status uint8 `gorm:"type:tinyint(1);not null;default:1;comment:Status: 1=active, 0=disabled"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index;comment:Deletion Time"`
|
||||
}
|
||||
|
||||
func (*UserFamily) TableName() string {
|
||||
return "user_family"
|
||||
}
|
||||
|
||||
type UserFamilyMember struct {
|
||||
Id int64 `gorm:"primaryKey"`
|
||||
FamilyId int64 `gorm:"index:idx_family_status,priority:1;not null;comment:Family ID"`
|
||||
UserId int64 `gorm:"uniqueIndex:uniq_user_id;not null;comment:Member User ID"`
|
||||
Role uint8 `gorm:"type:tinyint(1);not null;default:2;comment:Role: 1=owner, 2=member"`
|
||||
Status uint8 `gorm:"index:idx_family_status,priority:2;type:tinyint(1);not null;default:1;comment:Status: 1=active, 2=left, 3=removed"`
|
||||
JoinSource string `gorm:"type:varchar(32);not null;default:'';comment:Join source"`
|
||||
JoinedAt time.Time `gorm:"not null;comment:Joined time"`
|
||||
LeftAt *time.Time `gorm:"default:NULL;comment:Left time"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index;comment:Deletion Time"`
|
||||
}
|
||||
|
||||
func (*UserFamilyMember) TableName() string {
|
||||
return "user_family_member"
|
||||
}
|
||||
@@ -99,6 +99,7 @@ type customUserLogicModel interface {
|
||||
FindOneByEmail(ctx context.Context, email string) (*User, error)
|
||||
FindOneDevice(ctx context.Context, id int64) (*Device, error)
|
||||
QueryDeviceList(ctx context.Context, userid int64) ([]*Device, int64, error)
|
||||
QueryDeviceListByUserIds(ctx context.Context, userIds []int64) ([]*Device, int64, error)
|
||||
QueryDevicePageList(ctx context.Context, userid, subscribeId int64, page, size int) ([]*Device, int64, error)
|
||||
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
||||
@@ -110,6 +111,13 @@ type customUserLogicModel interface {
|
||||
|
||||
QueryDailyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error)
|
||||
QueryMonthlyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error)
|
||||
FindActiveSubscribe(ctx context.Context, userId int64) (*Subscribe, error)
|
||||
FindActiveSubscribesByUserIds(ctx context.Context, userIds []int64) (map[int64]*UserStatusInfo, error)
|
||||
}
|
||||
|
||||
type UserStatusInfo struct {
|
||||
MemberStatus string
|
||||
LastTrafficAt *time.Time
|
||||
}
|
||||
|
||||
type UserStatisticsWithDate struct {
|
||||
@@ -334,3 +342,17 @@ func (m *customUserModel) QueryMonthlyUserStatisticsList(ctx context.Context, da
|
||||
|
||||
return results, err
|
||||
}
|
||||
|
||||
// FindActiveSubscribe finds the active subscription for a user
|
||||
func (m *customUserModel) FindActiveSubscribe(ctx context.Context, userId int64) (*Subscribe, error) {
|
||||
var subscribe Subscribe
|
||||
err := m.QueryNoCacheCtx(ctx, &subscribe, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Where("user_id = ? AND status IN (0, 1) AND expire_time > ?", userId, time.Now()).
|
||||
Order("expire_time DESC").
|
||||
First(v).Error
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &subscribe, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FindActiveSubscribesByUserIds Find active subscriptions for multiple users
|
||||
func (m *customUserModel) FindActiveSubscribesByUserIds(ctx context.Context, userIds []int64) (map[int64]*UserStatusInfo, error) {
|
||||
if len(userIds) == 0 {
|
||||
return map[int64]*UserStatusInfo{}, nil
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
UserId int64
|
||||
Name string
|
||||
UpdatedAt *time.Time
|
||||
}
|
||||
var results []Result
|
||||
|
||||
// Query latest active subscription for each user
|
||||
err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error {
|
||||
return conn.Table("user_subscribe").
|
||||
Select("user_subscribe.user_id, subscribe.name, user_subscribe.updated_at").
|
||||
Joins("LEFT JOIN subscribe ON user_subscribe.subscribe_id = subscribe.id").
|
||||
Where("user_subscribe.user_id IN ? AND user_subscribe.status IN (0, 1) AND user_subscribe.expire_time > ?", userIds, time.Now()).
|
||||
Order("user_subscribe.created_at ASC"). // Ascending so we can overwrite in map to get the latest
|
||||
Scan(v).Error
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userMap := make(map[int64]*UserStatusInfo)
|
||||
for _, r := range results {
|
||||
userMap[r.UserId] = &UserStatusInfo{
|
||||
MemberStatus: r.Name,
|
||||
LastTrafficAt: r.UpdatedAt,
|
||||
}
|
||||
}
|
||||
return userMap, nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -74,18 +75,29 @@ func (m *defaultUserModel) FindUsersSubscribeBySubscribeId(ctx context.Context,
|
||||
// QueryUserSubscribe returns a list of records that meet the conditions.
|
||||
func (m *defaultUserModel) QueryUserSubscribe(ctx context.Context, userId int64, status ...int64) ([]*SubscribeDetails, error) {
|
||||
var list []*SubscribeDetails
|
||||
key := fmt.Sprintf("%s%d", cacheUserSubscribeUserPrefix, userId)
|
||||
|
||||
// Check if includeExpired is set in context
|
||||
includeExpired, _ := ctx.Value(constant.CtxKeyIncludeExpired).(string)
|
||||
cacheKeySuffix := ""
|
||||
if includeExpired == "all" {
|
||||
cacheKeySuffix = ":all"
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s%d%s", cacheUserSubscribeUserPrefix, userId, cacheKeySuffix)
|
||||
err := m.QueryCtx(ctx, &list, key, func(conn *gorm.DB, v interface{}) error {
|
||||
// 获取当前时间
|
||||
now := time.Now()
|
||||
// 获取当前时间向前推 7 天
|
||||
sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour)
|
||||
// 基础条件查询
|
||||
conn = conn.Model(&Subscribe{}).Where("`user_id` = ?", userId)
|
||||
if len(status) > 0 {
|
||||
conn = conn.Where("`status` IN ?", status)
|
||||
}
|
||||
// 订阅过期时间大于当前时间或者订阅结束时间大于当前时间
|
||||
|
||||
if includeExpired == "all" {
|
||||
// 查询所有订阅(包括已过期的)
|
||||
return conn.Preload("Subscribe").Find(&list).Error
|
||||
}
|
||||
|
||||
// 默认只查询有效订阅
|
||||
now := time.Now()
|
||||
sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour)
|
||||
return conn.Where("`expire_time` > ? OR `finished_at` >= ? OR `expire_time` = ?", now, sevenDaysAgo, time.UnixMilli(0)).
|
||||
Preload("Subscribe").
|
||||
Find(&list).Error
|
||||
|
||||
@@ -28,6 +28,9 @@ type User struct {
|
||||
AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"`
|
||||
UserDevices []Device `gorm:"foreignKey:UserId;references:Id"`
|
||||
Rules string `gorm:"type:TEXT;comment:User Rules"`
|
||||
LastLoginTime *time.Time `gorm:"default:NULL;comment:Last Login Time"`
|
||||
MemberStatus string `gorm:"type:varchar(20);default:'';comment:Member Status"`
|
||||
Remark string `gorm:"type:varchar(255);default:'';comment:Remark"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index;comment:Deletion Time"`
|
||||
|
||||
Reference in New Issue
Block a user