init: 1.0.0

This commit is contained in:
Chang lue Tsen
2025-04-25 12:08:29 +09:00
commit 8addcc584b
1031 changed files with 76472 additions and 0 deletions
@@ -0,0 +1,103 @@
package user
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/logic/common"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteAccountLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Delete Account
func NewDeleteAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteAccountLogic {
return &DeleteAccountLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteAccountLogic) DeleteAccount(req *types.DeleteAccountRequest) error {
userInfo, exists := l.ctx.Value(constant.CtxKeyUser).(user.User)
if !exists {
return nil
}
var account string
for _, authMethod := range userInfo.AuthMethods {
if authMethod.AuthType == req.Method {
account = authMethod.AuthIdentifier
break
}
}
if account == "" {
return nil
}
if req.Method == "email" {
emailConfig := l.svcCtx.Config.Email
if !emailConfig.Enable {
return errors.Wrapf(xerr.NewErrCode(xerr.EmailNotEnabled), "Email function is not enabled yet")
}
if emailConfig.EnableVerify {
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Security, account)
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
if err != nil {
l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey))
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
var payload common.CacheKeyPayload
err = json.Unmarshal([]byte(value), &payload)
if err != nil {
l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value))
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
if payload.Code != req.Code {
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
}
} else {
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeTelephoneCacheKey, constant.Security, account)
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
if err != nil {
l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey))
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
if value == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
var payload common.CacheKeyPayload
if err := json.Unmarshal([]byte(value), &payload); err != nil {
l.Errorw("[SendSmsCode]: Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value))
}
if payload.Code != req.Code {
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
}
err := l.svcCtx.UserModel.Delete(l.ctx, userInfo.Id)
if err != nil {
l.Errorw("update user password error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user password")
}
return nil
}
@@ -0,0 +1,115 @@
package user
import (
"context"
"sort"
"time"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
type GetUserOnlineTimeStatisticsLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get user online time total
func NewGetUserOnlineTimeStatisticsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserOnlineTimeStatisticsLogic {
return &GetUserOnlineTimeStatisticsLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserOnlineTimeStatisticsLogic) GetUserOnlineTimeStatistics() (resp *types.GetUserOnlineTimeStatisticsResponse, err error) {
u := l.ctx.Value(constant.CtxKeyUser).(*user.User)
//获取历史最长在线时间
var OnlineSeconds int64
if err := l.svcCtx.DB.Model(user.DeviceOnlineRecord{}).Where("user_id = ?", u.Id).Select("online_seconds").Order("online_seconds desc").Limit(1).Scan(&OnlineSeconds).Error; err != nil {
l.Logger.Error(err)
}
//获取历史连续最长在线天数
var DurationDays int64
if err := l.svcCtx.DB.Model(user.DeviceOnlineRecord{}).Where("user_id = ?", u.Id).Select("duration_days").Order("duration_days desc").Limit(1).Scan(&DurationDays).Error; err != nil {
l.Logger.Error(err)
}
//获取近七天在线情况
var userOnlineRecord []user.DeviceOnlineRecord
if err := l.svcCtx.DB.Model(&userOnlineRecord).Where("user_id = ? and created_at >= ?", u.Id, time.Now().AddDate(0, 0, -7).Format(time.DateTime)).Order("created_at desc").Find(&userOnlineRecord).Error; err != nil {
l.Logger.Error(err)
}
//获取当前连续在线天数
var currentContinuousDays int64
if len(userOnlineRecord) > 0 {
currentContinuousDays = userOnlineRecord[0].DurationDays
} else {
currentContinuousDays = 1
}
var dates []string
for i := 0; i < 7; i++ {
date := time.Now().AddDate(0, 0, -i).Format(time.DateOnly)
dates = append(dates, date)
}
onlineDays := make(map[string]types.WeeklyStat)
for _, record := range userOnlineRecord {
//获取近七天在线情况
onlineTime := record.OnlineTime.Format(time.DateOnly)
if weeklyStat, ok := onlineDays[onlineTime]; ok {
weeklyStat.Hours += float64(record.OnlineSeconds)
onlineDays[onlineTime] = weeklyStat
} else {
onlineDays[onlineTime] = types.WeeklyStat{
Hours: float64(record.OnlineSeconds),
//根据日期获取周几
DayName: record.OnlineTime.Weekday().String(),
}
}
}
//补全不存在的日期
for _, date := range dates {
if _, ok := onlineDays[date]; !ok {
onlineTime, _ := time.Parse(time.DateOnly, date)
onlineDays[date] = types.WeeklyStat{
DayName: onlineTime.Weekday().String(),
}
}
}
var keys []string
for key := range onlineDays {
keys = append(keys, key)
}
//排序
sort.Strings(keys)
var weeklyStats []types.WeeklyStat
for index, key := range keys {
weeklyStat := onlineDays[key]
weeklyStat.Day = index + 1
weeklyStat.Hours = weeklyStat.Hours / float64(3600)
weeklyStats = append(weeklyStats, weeklyStat)
}
resp = &types.GetUserOnlineTimeStatisticsResponse{
WeeklyStats: weeklyStats,
ConnectionRecords: types.ConnectionRecords{
CurrentContinuousDays: currentContinuousDays,
HistoryContinuousDays: DurationDays,
LongestSingleConnection: OnlineSeconds / 60,
},
}
return
}
@@ -0,0 +1,85 @@
package user
import (
"context"
"time"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
"github.com/perfect-panel/ppanel-server/internal/model/traffic"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
)
type GetUserSubscribeTrafficLogsLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get user subcribe traffic logs
func NewGetUserSubscribeTrafficLogsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserSubscribeTrafficLogsLogic {
return &GetUserSubscribeTrafficLogsLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserSubscribeTrafficLogsLogic) GetUserSubscribeTrafficLogs(req *types.GetUserSubscribeTrafficLogsRequest) (resp *types.GetUserSubscribeTrafficLogsResponse, err error) {
resp = &types.GetUserSubscribeTrafficLogsResponse{}
u := l.ctx.Value(constant.CtxKeyUser).(*user.User)
var traffics []traffic.TrafficLog
err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
return db.Model(traffic.TrafficLog{}).Where("user_id = ? and `timestamp` >= ? and `timestamp` < ?", u.Id, time.UnixMilli(req.StartTime), time.UnixMilli(req.EndTime)).Find(&traffics).Error
})
if err != nil {
l.Errorw("get user subscribe traffic logs failed", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error())
}
//合并多条记录为以天为单位
trafficMap := make(map[string]*traffic.TrafficLog)
for _, traf := range traffics {
key := traf.Timestamp.Format(time.DateOnly)
existTraf := trafficMap[key]
if existTraf == nil {
trafficMap[key] = &traf
} else {
existTraf.Upload = existTraf.Download + traf.Upload
existTraf.Download = existTraf.Download + traf.Download
trafficMap[key] = existTraf
}
}
startTime := time.UnixMilli(req.StartTime)
EndTime := time.UnixMilli(req.EndTime)
res := make(map[string]traffic.TrafficLog)
// 循环遍历每一天
for current := startTime; !current.After(EndTime); current = current.AddDate(0, 0, 1) {
dateStr := current.Format(time.DateOnly) // 格式化为日期字符串
if trafficMap[dateStr] == nil {
res[dateStr] = traffic.TrafficLog{
Timestamp: current,
}
} else {
res[dateStr] = *trafficMap[dateStr]
}
resp.List = append(resp.List, types.TrafficLog{
Id: res[dateStr].Id,
ServerId: res[dateStr].ServerId,
Upload: res[dateStr].Upload,
Download: res[dateStr].Download,
Timestamp: res[dateStr].Timestamp.UnixMilli(),
})
}
return
}
@@ -0,0 +1,62 @@
package user
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
type QueryUserAffiliateListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Query User Affiliate List
func NewQueryUserAffiliateListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserAffiliateListLogic {
return &QueryUserAffiliateListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryUserAffiliateListLogic) QueryUserAffiliateList(req *types.QueryUserAffiliateListRequest) (resp *types.QueryUserAffiliateListResponse, err error) {
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok {
logger.Error("current user is not found in context")
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
}
var data []*user.User
var total int64
err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
return db.Model(&user.User{}).Order("id desc").Where("referer_id = ?", u.Id).Count(&total).Limit(req.Size).Offset((req.Page - 1) * req.Size).Find(&data).Error
})
if err != nil {
l.Errorw("Query User Affiliate List failed: %v", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate List failed: %v", err.Error())
}
list := make([]types.UserAffiliate, 0)
for _, item := range data {
list = append(list, types.UserAffiliate{
//Email: tool.MaskEmail(item.Email),
Avatar: item.Avatar,
RegisteredAt: item.CreatedAt.UnixMilli(),
Enable: *item.Enable,
})
}
return &types.QueryUserAffiliateListResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,63 @@
package user
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
type QueryUserInfoLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// query user info
func NewQueryUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserInfoLogic {
return &QueryUserInfoLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryUserInfoLogic) QueryUserInfo() (resp *types.UserInfoResponse, err error) {
u := l.ctx.Value(constant.CtxKeyUser).(*user.User)
var devices []types.UserDevice
if len(u.UserDevices) != 0 {
for _, device := range u.UserDevices {
devices = append(devices, types.UserDevice{
Id: device.Id,
Identifier: device.Identifier,
Online: device.Online,
})
}
}
var authMeths []types.UserAuthMethod
authMethods, err := l.svcCtx.UserModel.FindUserAuthMethods(l.ctx, u.Id)
if err == nil && len(authMeths) != 0 {
for _, as := range authMethods {
authMeths = append(authMeths, types.UserAuthMethod{
AuthType: as.AuthType,
AuthIdentifier: as.AuthIdentifier,
})
}
}
resp = &types.UserInfoResponse{
Id: u.Id,
Balance: u.Balance,
Avatar: u.Avatar,
ReferCode: u.ReferCode,
RefererId: u.RefererId,
Devices: devices,
AuthMethods: authMeths,
}
return
}
@@ -0,0 +1,60 @@
package user
import (
"context"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
type QueryUserAffiliateLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Query User Affiliate Count
func NewQueryUserAffiliateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserAffiliateLogic {
return &QueryUserAffiliateLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryUserAffiliateLogic) QueryUserAffiliate() (resp *types.QueryUserAffiliateCountResponse, err error) {
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok {
logger.Error("current user is not found in context")
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
}
var sum int64
var total int64
err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
return db.Model(&user.User{}).Where("referer_id = ?", u.Id).Count(&total).Find(&user.User{}).Error
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate failed: %v", err)
}
err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
return db.Model(&user.CommissionLog{}).
Where("user_id = ?", u.Id).
Select("COALESCE(SUM(amount), 0)").
Scan(&sum).Error
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Affiliate failed: %v", err)
}
return &types.QueryUserAffiliateCountResponse{
Registers: total,
TotalCommission: sum,
}, nil
}
@@ -0,0 +1,46 @@
package user
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/internal/types"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/perfect-panel/ppanel-server/pkg/xerr"
"github.com/pkg/errors"
)
type UpdatePasswordLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Update Password
func NewUpdatePasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePasswordLogic {
return &UpdatePasswordLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdatePasswordLogic) UpdatePassword(req *types.UpdatePasswordRequeset) error {
userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User)
// Verify password
if !tool.VerifyPassWord(req.Password, userInfo.Password) {
return errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
}
userInfo.Password = tool.EncodePassWord(req.NewPassword)
err := l.svcCtx.UserModel.Update(l.ctx, userInfo)
if err != nil {
l.Errorw("update user password error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user password")
}
return err
}