chore: 清理敏感与开发文档,仅同步代码

- 永久忽略并从仓库移除 开发文档/
- 移除并忽略 .env 与小程序私有配置
- 同步小程序/管理端/API与脚本改动

Made-with: Cursor
This commit is contained in:
卡若
2026-03-17 17:50:12 +08:00
parent 868b0a10d9
commit 76965adb23
443 changed files with 24175 additions and 64154 deletions

View File

@@ -1,115 +1,128 @@
package handler
import (
"encoding/json"
"net/http"
"sync"
"soul-api/internal/database"
"soul-api/internal/model"
"soul-api/internal/wechat"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var paidStatuses = []string{"paid", "completed", "success"}
// AdminDashboardStats GET /api/admin/dashboard/stats
// 轻量聚合:总用户、付费订单数、付费用户数、总营收、转化率(无订单/用户明细)
func AdminDashboardStats(c *gin.Context) {
db := database.DB()
var (
totalUsers int64
paidOrderCount int64
totalRevenue float64
paidUserCount int64
)
var wg sync.WaitGroup
wg.Add(4)
go func() { defer wg.Done(); db.Model(&model.User{}).Count(&totalUsers) }()
go func() { defer wg.Done(); db.Model(&model.Order{}).Where("status IN ?", paidStatuses).Count(&paidOrderCount) }()
go func() {
defer wg.Done()
db.Model(&model.Order{}).Where("status IN ?", paidStatuses).
Select("COALESCE(SUM(amount), 0)").Scan(&totalRevenue)
}()
go func() {
defer wg.Done()
db.Table("orders").Where("status IN ?", paidStatuses).
Select("COUNT(DISTINCT user_id)").Scan(&paidUserCount)
}()
wg.Wait()
conversionRate := 0.0
if totalUsers > 0 && paidUserCount > 0 {
conversionRate = float64(paidUserCount) / float64(totalUsers) * 100
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"totalUsers": totalUsers,
"paidOrderCount": paidOrderCount,
"paidUserCount": paidUserCount,
"totalRevenue": totalRevenue,
"conversionRate": conversionRate,
})
}
// AdminDashboardRecentOrders GET /api/admin/dashboard/recent-orders
func AdminDashboardRecentOrders(c *gin.Context) {
db := database.DB()
var recentOrders []model.Order
db.Where("status IN ?", paidStatuses).Order("created_at DESC").Limit(10).Find(&recentOrders)
c.JSON(http.StatusOK, gin.H{"success": true, "recentOrders": buildRecentOrdersOut(db, recentOrders)})
}
// AdminDashboardNewUsers GET /api/admin/dashboard/new-users
func AdminDashboardNewUsers(c *gin.Context) {
db := database.DB()
var newUsers []model.User
db.Model(&model.User{}).Order("created_at DESC").Limit(10).Find(&newUsers)
c.JSON(http.StatusOK, gin.H{"success": true, "newUsers": buildNewUsersOut(newUsers)})
}
// AdminDashboardOverview GET /api/admin/dashboard/overview
// 数据概览:总用户、订单数(仅已付款)、转化率(有付款行为的用户数/总用户数)、总收入;最近订单与新注册用户均为实时查询。
// 数据概览:总用户、付费订单数、付费用户数、总营收、转化率、最近订单、新用户
// 优化6 组查询并行执行,减少总耗时
func AdminDashboardOverview(c *gin.Context) {
db := database.DB()
var totalUsers int64
db.Model(&model.User{}).Count(&totalUsers)
var (
totalUsers int64
paidOrderCount int64
totalRevenue float64
paidUserCount int64
recentOrders []model.Order
newUsers []model.User
)
var wg sync.WaitGroup
wg.Add(6)
var paidOrderCount int64
db.Model(&model.Order{}).Where("status IN ?", []string{"paid", "completed", "success"}).Count(&paidOrderCount)
var paidUserCount int64
db.Model(&model.Order{}).Where("status IN ?", []string{"paid", "completed", "success"}).Distinct("user_id").Count(&paidUserCount)
var totalRevenue float64
db.Model(&model.Order{}).Where("status IN ?", []string{"paid", "completed", "success"}).Select("COALESCE(SUM(amount),0)").Scan(&totalRevenue)
go func() {
defer wg.Done()
db.Model(&model.User{}).Count(&totalUsers)
}()
go func() {
defer wg.Done()
db.Model(&model.Order{}).Where("status IN ?", paidStatuses).Count(&paidOrderCount)
}()
go func() {
defer wg.Done()
db.Model(&model.Order{}).Where("status IN ?", paidStatuses).
Select("COALESCE(SUM(amount), 0)").Scan(&totalRevenue)
}()
go func() {
defer wg.Done()
db.Table("orders").Where("status IN ?", paidStatuses).
Select("COUNT(DISTINCT user_id)").Scan(&paidUserCount)
}()
go func() {
defer wg.Done()
db.Where("status IN ?", paidStatuses).
Order("created_at DESC").Limit(5).Find(&recentOrders)
}()
go func() {
defer wg.Done()
db.Model(&model.User{}).Order("created_at DESC").Limit(10).Find(&newUsers)
}()
wg.Wait()
conversionRate := 0.0
if totalUsers > 0 && paidUserCount > 0 {
conversionRate = float64(paidUserCount) / float64(totalUsers) * 100
}
var recentOrders []model.Order
db.Where("status IN ?", []string{"paid", "completed", "success"}).Order("created_at DESC").Limit(5).Find(&recentOrders)
userIDs := make(map[string]bool)
for _, o := range recentOrders {
userIDs[o.UserID] = true
if o.ReferrerID != nil && *o.ReferrerID != "" {
userIDs[*o.ReferrerID] = true
}
}
ids := make([]string, 0, len(userIDs))
for id := range userIDs {
ids = append(ids, id)
}
var orderUsers []model.User
if len(ids) > 0 {
db.Where("id IN ?", ids).Find(&orderUsers)
}
userMap := make(map[string]*model.User)
for i := range orderUsers {
userMap[orderUsers[i].ID] = &orderUsers[i]
}
recentOut := make([]gin.H, 0, len(recentOrders))
for _, o := range recentOrders {
u := userMap[o.UserID]
nickname := ""
phone := ""
avatar := ""
if u != nil {
if u.Nickname != nil {
nickname = *u.Nickname
}
if u.Phone != nil {
phone = *u.Phone
}
if u.Avatar != nil {
avatar = *u.Avatar
}
}
referrerCode := ""
if o.ReferralCode != nil {
referrerCode = *o.ReferralCode
} else if o.ReferrerID != nil && *o.ReferrerID != "" {
if ru := userMap[*o.ReferrerID]; ru != nil && ru.ReferralCode != nil {
referrerCode = *ru.ReferralCode
}
}
recentOut = append(recentOut, gin.H{
"id": o.ID, "orderSn": o.OrderSN, "userId": o.UserID, "userNickname": nickname, "userAvatar": avatar,
"userPhone": phone,
"amount": o.Amount, "status": ptrStr(o.Status), "productType": o.ProductType, "productId": o.ProductID, "description": o.Description,
"referrerId": o.ReferrerID, "referralCode": referrerCode, "createdAt": o.CreatedAt, "paymentMethod": "微信",
})
}
var newUsers []model.User
db.Order("created_at DESC").Limit(5).Find(&newUsers)
newUsersOut := make([]gin.H, 0, len(newUsers))
for _, u := range newUsers {
nickname := ""
if u.Nickname != nil {
nickname = *u.Nickname
}
phone := ""
if u.Phone != nil {
phone = *u.Phone
}
newUsersOut = append(newUsersOut, gin.H{
"id": u.ID, "nickname": nickname, "phone": phone, "referralCode": u.ReferralCode, "createdAt": u.CreatedAt,
})
}
// 匹配统计
var totalMatches int64
db.Raw("SELECT COUNT(*) FROM match_records").Scan(&totalMatches)
var matchRevenue float64
db.Model(&model.Order{}).Where("product_type = ? AND status IN ?", "match", []string{"paid", "completed", "success"}).
Select("COALESCE(SUM(amount),0)").Scan(&matchRevenue)
recentOut := buildRecentOrdersOut(db, recentOrders)
newOut := buildNewUsersOut(newUsers)
c.JSON(http.StatusOK, gin.H{
"success": true,
@@ -119,15 +132,84 @@ func AdminDashboardOverview(c *gin.Context) {
"totalRevenue": totalRevenue,
"conversionRate": conversionRate,
"recentOrders": recentOut,
"newUsers": newUsersOut,
"totalMatches": totalMatches,
"matchRevenue": matchRevenue,
"newUsers": newOut,
})
}
func ptrStr(s *string) string {
if s == nil {
func dashStr(s *string) string {
if s == nil || *s == "" {
return ""
}
return *s
}
func buildRecentOrdersOut(db *gorm.DB, recentOrders []model.Order) []gin.H {
if len(recentOrders) == 0 {
return nil
}
userIDs := make(map[string]bool)
for _, o := range recentOrders {
if o.UserID != "" {
userIDs[o.UserID] = true
}
}
ids := make([]string, 0, len(userIDs))
for id := range userIDs {
ids = append(ids, id)
}
var users []model.User
db.Where("id IN ?", ids).Find(&users)
userMap := make(map[string]*model.User)
for i := range users {
userMap[users[i].ID] = &users[i]
}
out := make([]gin.H, 0, len(recentOrders))
for _, o := range recentOrders {
b, _ := json.Marshal(o)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
if u := userMap[o.UserID]; u != nil {
m["userNickname"] = dashStr(u.Nickname)
m["userAvatar"] = dashStr(u.Avatar)
} else {
m["userNickname"] = ""
m["userAvatar"] = ""
}
out = append(out, m)
}
return out
}
// AdminDashboardMerchantBalance GET /api/admin/dashboard/merchant-balance
// 查询微信商户号实时余额(可用余额、待结算余额),用于看板展示
// 注意:普通商户可能需向微信申请开通权限,未开通时返回 error
func AdminDashboardMerchantBalance(c *gin.Context) {
bal, err := wechat.QueryMerchantBalance("BASIC")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"error": err.Error(),
"message": "查询商户余额失败,可能未开通权限(请联系微信支付运营申请)",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"availableAmount": bal.AvailableAmount, // 单位:分
"pendingAmount": bal.PendingAmount, // 单位:分
})
}
func buildNewUsersOut(newUsers []model.User) []gin.H {
out := make([]gin.H, 0, len(newUsers))
for _, u := range newUsers {
out = append(out, gin.H{
"id": u.ID,
"nickname": dashStr(u.Nickname),
"phone": dashStr(u.Phone),
"referralCode": dashStr(u.ReferralCode),
"createdAt": u.CreatedAt,
})
}
return out
}