基本功能已初步完善
Some checks failed
build / build (api, amd64, linux) (push) Has been cancelled
build / build (api, arm64, linux) (push) Has been cancelled
build / build (api.exe, amd64, windows) (push) Has been cancelled

This commit is contained in:
CN-JS-HuiBai
2026-04-17 20:41:47 +08:00
parent 25fd919477
commit b3435e5ef8
34 changed files with 3495 additions and 429 deletions

View File

@@ -23,6 +23,7 @@ func AdminDashboardSummary(c *gin.Context) {
var totalOrders int64
var pendingOrders int64
var pendingTickets int64
var commissionPendingTotal int64
var onlineUsers int64
var onlineNodes int64
var onlineDevices int64
@@ -30,6 +31,10 @@ func AdminDashboardSummary(c *gin.Context) {
database.DB.Model(&model.User{}).Count(&totalUsers)
database.DB.Model(&model.Order{}).Count(&totalOrders)
database.DB.Model(&model.Order{}).Where("status = ?", 0).Count(&pendingOrders)
database.DB.Model(&model.Order{}).
Where("status = ? AND commission_status = ?", 3, 0).
Select("COALESCE(SUM(commission_balance), 0)").
Scan(&commissionPendingTotal)
database.DB.Model(&model.Ticket{}).Where("status = ?", 0).Count(&pendingTickets)
database.DB.Model(&model.Server{}).Where("show = ?", true).Count(&onlineNodes) // Simplified online check
@@ -75,25 +80,28 @@ func AdminDashboardSummary(c *gin.Context) {
userGrowth := calculateGrowth(currentMonthNewUsers, lastMonthNewUsers)
Success(c, gin.H{
"server_time": now,
"todayIncome": todayIncome,
"dayIncomeGrowth": dayIncomeGrowth,
"currentMonthIncome": currentMonthIncome,
"lastMonthIncome": lastMonthIncome,
"monthIncomeGrowth": monthIncomeGrowth,
"currentMonthNewUsers": currentMonthNewUsers,
"totalUsers": totalUsers,
"activeUsers": totalUsers, // Placeholder for valid subscription count
"userGrowth": userGrowth,
"onlineUsers": onlineUsers,
"onlineDevices": onlineDevices,
"ticketPendingTotal": pendingTickets,
"onlineNodes": onlineNodes,
"todayTraffic": todayTraffic,
"monthTraffic": monthTraffic,
"totalTraffic": totalTraffic,
"secure_path": service.GetAdminSecurePath(),
"app_name": service.MustGetString("app_name", "XBoard"),
"server_time": now,
"todayIncome": todayIncome,
"dayIncomeGrowth": dayIncomeGrowth,
"currentMonthIncome": currentMonthIncome,
"lastMonthIncome": lastMonthIncome,
"monthIncomeGrowth": monthIncomeGrowth,
"totalOrders": totalOrders,
"pendingOrders": pendingOrders,
"currentMonthNewUsers": currentMonthNewUsers,
"totalUsers": totalUsers,
"activeUsers": totalUsers, // Placeholder for valid subscription count
"userGrowth": userGrowth,
"commissionPendingTotal": commissionPendingTotal,
"onlineUsers": onlineUsers,
"onlineDevices": onlineDevices,
"ticketPendingTotal": pendingTickets,
"onlineNodes": onlineNodes,
"todayTraffic": todayTraffic,
"monthTraffic": monthTraffic,
"totalTraffic": totalTraffic,
"secure_path": service.GetAdminSecurePath(),
"app_name": service.MustGetString("app_name", "XBoard"),
})
}
@@ -107,7 +115,6 @@ func calculateGrowth(current, previous int64) float64 {
return float64(current-previous) / float64(previous) * 100.0
}
func AdminPlansFetch(c *gin.Context) {
var plans []model.Plan
if err := database.DB.Order("sort ASC, id DESC").Find(&plans).Error; err != nil {
@@ -248,11 +255,10 @@ func AdminPlanSort(c *gin.Context) {
Success(c, true)
}
func AdminOrdersFetch(c *gin.Context) {
params := getFetchParams(c)
page := parsePositiveInt(params["page"], 1)
perPage := parsePositiveInt(params["per_page"], 50)
page := parsePositiveInt(firstString(params["page"], params["current"]), 1)
perPage := parsePositiveInt(firstString(params["per_page"], params["pageSize"]), 50)
keyword := strings.TrimSpace(params["keyword"])
statusFilter := strings.TrimSpace(params["status"])
@@ -277,15 +283,28 @@ func AdminOrdersFetch(c *gin.Context) {
}
userEmails := loadUserEmailMap(extractOrderUserIDs(orders))
inviteEmails := loadUserEmailMap(extractOrderInviteUserIDs(orders))
items := make([]gin.H, 0, len(orders))
for _, order := range orders {
item := normalizeOrder(order)
item["user_email"] = userEmails[order.UserID]
item["user"] = gin.H{
"id": order.UserID,
"email": userEmails[order.UserID],
}
if order.InviteUserID != nil {
item["invite_user"] = gin.H{
"id": *order.InviteUserID,
"email": inviteEmails[*order.InviteUserID],
}
}
items = append(items, item)
}
Success(c, gin.H{
"list": items,
"list": items,
"data": items,
"total": total,
"filters": gin.H{
"keyword": keyword,
"status": statusFilter,
@@ -388,7 +407,6 @@ func AdminCouponDrop(c *gin.Context) {
Success(c, true)
}
func AdminOrderPaid(c *gin.Context) {
var payload struct {
TradeNo string `json:"trade_no"`
@@ -421,23 +439,17 @@ func AdminOrderPaid(c *gin.Context) {
return
}
// Update user
var user model.User
if err := tx.Where("id = ?", order.ID).First(&user).Error; err == nil {
// Calculate expiration and traffic
// Simplified logic: set plan and transfer_enable
updates := map[string]any{
"plan_id": order.PlanID,
"updated_at": now,
}
if order.Plan != nil {
updates["transfer_enable"] = order.Plan.TransferEnable
}
if err := tx.Model(&model.User{}).Where("id = ?", order.UserID).Updates(updates).Error; err != nil {
tx.Rollback()
Fail(c, http.StatusInternalServerError, "failed to update user")
return
}
updates := map[string]any{
"plan_id": order.PlanID,
"updated_at": now,
}
if order.Plan != nil {
updates["transfer_enable"] = order.Plan.TransferEnable
}
if err := tx.Model(&model.User{}).Where("id = ?", order.UserID).Updates(updates).Error; err != nil {
tx.Rollback()
Fail(c, http.StatusInternalServerError, "failed to update user")
return
}
tx.Commit()
@@ -527,19 +539,19 @@ func AdminUserUpdate(c *gin.Context) {
now := time.Now().Unix()
values := map[string]any{
"email": payload["email"],
"password": payload["password"],
"balance": payload["balance"],
"commission_type": payload["commission_type"],
"commission_rate": payload["commission_rate"],
"email": payload["email"],
"password": payload["password"],
"balance": payload["balance"],
"commission_type": payload["commission_type"],
"commission_rate": payload["commission_rate"],
"commission_balance": payload["commission_balance"],
"group_id": payload["group_id"],
"plan_id": payload["plan_id"],
"speed_limit": payload["speed_limit"],
"device_limit": payload["device_limit"],
"expired_at": payload["expired_at"],
"remarks": payload["remarks"],
"updated_at": now,
"group_id": payload["group_id"],
"plan_id": payload["plan_id"],
"speed_limit": payload["speed_limit"],
"device_limit": payload["device_limit"],
"expired_at": payload["expired_at"],
"remarks": payload["remarks"],
"updated_at": now,
}
// Remove nil values to avoid overwriting with defaults if not provided
@@ -564,8 +576,8 @@ func AdminUserUpdate(c *gin.Context) {
func AdminUsersFetch(c *gin.Context) {
params := getFetchParams(c)
page := parsePositiveInt(params["page"], 1)
perPage := parsePositiveInt(params["per_page"], 50)
page := parsePositiveInt(firstString(params["page"], params["current"]), 1)
perPage := parsePositiveInt(firstString(params["per_page"], params["pageSize"]), 50)
keyword := strings.TrimSpace(params["keyword"])
query := database.DB.Model(&model.User{}).Preload("Plan").Order("id DESC")
@@ -591,6 +603,28 @@ func AdminUsersFetch(c *gin.Context) {
}
deviceMap := service.GetUsersDevices(userIDs)
realnameStatusByUserID := make(map[int]string, len(userIDs))
if len(userIDs) > 0 {
var records []model.RealNameAuth
if err := database.DB.Select("user_id", "status").Where("user_id IN ?", userIDs).Find(&records).Error; err == nil {
for _, record := range records {
realnameStatusByUserID[int(record.UserID)] = record.Status
}
}
}
shadowByParentID := make(map[int]model.User, len(userIDs))
if len(userIDs) > 0 {
var shadowUsers []model.User
if err := database.DB.Select("id", "parent_id", "email").Where("parent_id IN ?", userIDs).Find(&shadowUsers).Error; err == nil {
for _, shadow := range shadowUsers {
if shadow.ParentID != nil {
shadowByParentID[*shadow.ParentID] = shadow
}
}
}
}
groupNames := loadServerGroupNameMap()
items := make([]gin.H, 0, len(users))
for _, user := range users {
@@ -599,37 +633,70 @@ func AdminUsersFetch(c *gin.Context) {
onlineIP = strings.Join(ips, ", ")
}
realnameStatus := realnameStatusByUserID[user.ID]
if realnameStatus == "" {
realnameStatus = "unverified"
}
ipv6Shadow, hasIPv6Shadow := shadowByParentID[user.ID]
ipv6ShadowID := 0
if hasIPv6Shadow {
ipv6ShadowID = ipv6Shadow.ID
}
items = append(items, gin.H{
"id": user.ID,
"email": user.Email,
"balance": user.Balance,
"group_id": intValue(user.GroupID),
"group_name": groupNames[intFromPointer(user.GroupID)],
"plan_id": intValue(user.PlanID),
"plan_name": planName(user.Plan),
"transfer_enable": user.TransferEnable,
"u": user.U,
"d": user.D,
"banned": user.Banned,
"is_admin": user.IsAdmin,
"is_staff": user.IsStaff,
"device_limit": intValue(user.DeviceLimit),
"online_count": intValue(user.OnlineCount),
"expired_at": int64Value(user.ExpiredAt),
"last_login_at": int64Value(user.LastLoginAt),
"last_login_ip": user.LastLoginIP,
"online_ip": onlineIP,
"created_at": user.CreatedAt,
"updated_at": user.UpdatedAt,
"remarks": stringValue(user.Remarks),
"commission_type": user.CommissionType,
"commission_rate": intValue(user.CommissionRate),
"id": user.ID,
"email": user.Email,
"parent_id": intValue(user.ParentID),
"is_shadow_user": user.ParentID != nil,
"balance": user.Balance,
"uuid": user.UUID,
"token": user.Token,
"group_id": intValue(user.GroupID),
"group_name": groupNames[intFromPointer(user.GroupID)],
"group": gin.H{
"id": intValue(user.GroupID),
"name": groupNames[intFromPointer(user.GroupID)],
},
"plan_id": intValue(user.PlanID),
"plan_name": planName(user.Plan),
"plan": gin.H{
"id": intValue(user.PlanID),
"name": planName(user.Plan),
},
"transfer_enable": user.TransferEnable,
"u": user.U,
"d": user.D,
"total_used": user.U + user.D,
"banned": user.Banned,
"is_admin": user.IsAdmin,
"is_staff": user.IsStaff,
"device_limit": intValue(user.DeviceLimit),
"online_count": intValue(user.OnlineCount),
"expired_at": int64Value(user.ExpiredAt),
"next_reset_at": int64Value(user.NextResetAt),
"last_login_at": int64Value(user.LastLoginAt),
"last_login_ip": user.LastLoginIP,
"online_ip": onlineIP,
"online_ip_count": len(deviceMap[user.ID]),
"realname_status": realnameStatus,
"realname_label": realNameStatusLabel(realnameStatus),
"ipv6_shadow_id": ipv6ShadowID,
"ipv6_shadow_email": firstString(ipv6Shadow.Email, service.IPv6ShadowEmail(user.Email)),
"ipv6_enabled": hasIPv6Shadow,
"created_at": user.CreatedAt,
"updated_at": user.UpdatedAt,
"remarks": stringValue(user.Remarks),
"commission_type": user.CommissionType,
"commission_rate": intValue(user.CommissionRate),
"commission_balance": user.CommissionBalance,
})
}
Success(c, gin.H{
"list": items,
c.JSON(http.StatusOK, gin.H{
"list": items,
"data": items,
"total": total,
"filters": gin.H{
"keyword": keyword,
},
@@ -689,7 +756,9 @@ func AdminTicketsFetch(c *gin.Context) {
}
Success(c, gin.H{
"list": items,
"list": items,
"data": items,
"total": total,
"filters": gin.H{
"keyword": keyword,
},
@@ -746,6 +815,16 @@ func extractOrderUserIDs(orders []model.Order) []int {
return ids
}
func extractOrderInviteUserIDs(orders []model.Order) []int {
ids := make([]int, 0, len(orders))
for _, order := range orders {
if order.InviteUserID != nil {
ids = append(ids, *order.InviteUserID)
}
}
return ids
}
func extractTicketUserIDs(tickets []model.Ticket) []int {
ids := make([]int, 0, len(tickets))
for _, ticket := range tickets {