Files
soul-yongping/soul-api/internal/handler/miniprogram.go

832 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"soul-api/internal/database"
"soul-api/internal/model"
"soul-api/internal/wechat"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var (
orderPollLogger *log.Logger
orderPollLoggerOnce sync.Once
)
// orderPollLogf 将订单轮询检测日志写入 log/order-poll.log不输出到控制台
func orderPollLogf(format string, args ...interface{}) {
orderPollLoggerOnce.Do(func() {
_ = os.MkdirAll("log", 0755)
f, err := os.OpenFile(filepath.Join("log", "order-poll.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
orderPollLogger = log.New(io.Discard, "", 0)
return
}
orderPollLogger = log.New(f, "[OrderPoll] ", log.Ldate|log.Ltime)
})
if orderPollLogger != nil {
orderPollLogger.Printf(format, args...)
}
}
// MiniprogramLogin POST /api/miniprogram/login
func MiniprogramLogin(c *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少登录code"})
return
}
// 调用微信接口获取 openid 和 session_key
openID, sessionKey, _, err := wechat.Code2Session(req.Code)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": fmt.Sprintf("微信登录失败: %v", err)})
return
}
db := database.DB()
// 查询用户是否存在
var user model.User
result := db.Where("open_id = ?", openID).First(&user)
isNewUser := result.Error != nil
if isNewUser {
// 创建新用户
userID := openID // 直接使用 openid 作为用户 ID
referralCode := "SOUL" + strings.ToUpper(openID[len(openID)-6:])
nickname := "微信用户" + openID[len(openID)-4:]
avatar := ""
hasFullBook := false
earnings := 0.0
pendingEarnings := 0.0
referralCount := 0
purchasedSections := "[]"
user = model.User{
ID: userID,
OpenID: &openID,
SessionKey: &sessionKey,
Nickname: &nickname,
Avatar: &avatar,
ReferralCode: &referralCode,
HasFullBook: &hasFullBook,
PurchasedSections: &purchasedSections,
Earnings: &earnings,
PendingEarnings: &pendingEarnings,
ReferralCount: &referralCount,
}
if err := db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "创建用户失败"})
return
}
} else {
// 更新 session_key
db.Model(&user).Update("session_key", sessionKey)
}
// 从 orders 表查询真实购买记录
var purchasedSections []string
var orderRows []struct {
ProductID string `gorm:"column:product_id"`
}
db.Raw(`
SELECT DISTINCT product_id
FROM orders
WHERE user_id = ?
AND status = 'paid'
AND product_type = 'section'
`, user.ID).Scan(&orderRows)
for _, row := range orderRows {
if row.ProductID != "" {
purchasedSections = append(purchasedSections, row.ProductID)
}
}
if purchasedSections == nil {
purchasedSections = []string{}
}
// 构建返回的用户对象
responseUser := map[string]interface{}{
"id": user.ID,
"openId": getStringValue(user.OpenID),
"nickname": getStringValue(user.Nickname),
"avatar": getStringValue(user.Avatar),
"phone": getStringValue(user.Phone),
"wechatId": getStringValue(user.WechatID),
"referralCode": getStringValue(user.ReferralCode),
"hasFullBook": getBoolValue(user.HasFullBook),
"purchasedSections": purchasedSections,
"earnings": getFloatValue(user.Earnings),
"pendingEarnings": getFloatValue(user.PendingEarnings),
"referralCount": getIntValue(user.ReferralCount),
"createdAt": user.CreatedAt,
}
// 生成 token
token := fmt.Sprintf("tk_%s_%d", openID[len(openID)-8:], time.Now().Unix())
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": map[string]interface{}{
"openId": openID,
"user": responseUser,
"token": token,
},
"isNewUser": isNewUser,
})
}
// 辅助函数
func getStringValue(ptr *string) string {
if ptr == nil {
return ""
}
return *ptr
}
func getBoolValue(ptr *bool) bool {
if ptr == nil {
return false
}
return *ptr
}
func getFloatValue(ptr *float64) float64 {
if ptr == nil {
return 0.0
}
return *ptr
}
func getIntValue(ptr *int) int {
if ptr == nil {
return 0
}
return *ptr
}
// MiniprogramPay GET/POST /api/miniprogram/pay
func MiniprogramPay(c *gin.Context) {
if c.Request.Method == "POST" {
miniprogramPayPost(c)
} else {
miniprogramPayGet(c)
}
}
// POST - 创建小程序支付订单
func miniprogramPayPost(c *gin.Context) {
var req struct {
OpenID string `json:"openId" binding:"required"`
ProductType string `json:"productType" binding:"required"`
ProductID string `json:"productId"`
Amount float64 `json:"amount" binding:"required"`
Description string `json:"description"`
UserID string `json:"userId"`
ReferralCode string `json:"referralCode"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少openId参数请先登录"})
return
}
if req.Amount <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "支付金额无效"})
return
}
db := database.DB()
// 查询用户的有效推荐人(先查 binding再查 referralCode
var referrerID *string
if req.UserID != "" {
var binding struct {
ReferrerID string `gorm:"column:referrer_id"`
}
err := db.Raw(`
SELECT referrer_id
FROM referral_bindings
WHERE referee_id = ? AND status = 'active' AND expiry_date > NOW()
ORDER BY binding_date DESC
LIMIT 1
`, req.UserID).Scan(&binding).Error
if err == nil && binding.ReferrerID != "" {
referrerID = &binding.ReferrerID
}
}
if referrerID == nil && req.ReferralCode != "" {
var refUser model.User
if err := db.Where("referral_code = ?", req.ReferralCode).First(&refUser).Error; err == nil {
referrerID = &refUser.ID
}
}
// 有推荐人时应用好友优惠(无论是 binding 还是 referralCode
finalAmount := req.Amount
if referrerID != nil {
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "referral_config").First(&cfg).Error; err == nil {
var config map[string]interface{}
if err := json.Unmarshal(cfg.ConfigValue, &config); err == nil {
if userDiscount, ok := config["userDiscount"].(float64); ok && userDiscount > 0 {
discountRate := userDiscount / 100
finalAmount = req.Amount * (1 - discountRate)
if finalAmount < 0.01 {
finalAmount = 0.01
}
}
}
}
}
// 生成订单号
orderSn := wechat.GenerateOrderSn()
totalFee := int(finalAmount * 100) // 转为分
description := req.Description
if description == "" {
if req.ProductType == "fullbook" {
description = "《一场Soul的创业实验》全书"
} else if req.ProductType == "vip" {
description = "卡若创业派对VIP年度会员365天"
} else if req.ProductType == "match" {
description = "购买匹配次数"
} else {
description = fmt.Sprintf("章节购买-%s", req.ProductID)
}
}
// 获取客户端 IP
clientIP := c.ClientIP()
if clientIP == "" {
clientIP = "127.0.0.1"
}
// 插入订单到数据库
userID := req.UserID
if userID == "" {
userID = req.OpenID
}
productID := req.ProductID
if productID == "" {
switch req.ProductType {
case "vip":
productID = "vip_annual"
case "match":
productID = "match"
default:
productID = "fullbook"
}
}
status := "created"
order := model.Order{
ID: orderSn,
OrderSN: orderSn,
UserID: userID,
OpenID: req.OpenID,
ProductType: req.ProductType,
ProductID: &productID,
Amount: finalAmount,
Description: &description,
Status: &status,
ReferrerID: referrerID,
ReferralCode: &req.ReferralCode,
}
if err := db.Create(&order).Error; err != nil {
// 订单创建失败,但不中断支付流程
fmt.Printf("[MiniprogramPay] 插入订单失败: %v\n", err)
}
attach := fmt.Sprintf(`{"productType":"%s","productId":"%s","userId":"%s"}`, req.ProductType, req.ProductID, userID)
ctx := c.Request.Context()
prepayID, err := wechat.PayJSAPIOrder(ctx, req.OpenID, orderSn, totalFee, description, attach)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": fmt.Sprintf("微信支付请求失败: %v", err)})
return
}
payParams, err := wechat.GetJSAPIPayParams(prepayID)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": fmt.Sprintf("生成支付参数失败: %v", err)})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": map[string]interface{}{
"orderSn": orderSn,
"prepayId": prepayID,
"payParams": payParams,
},
})
}
// GET - 查询订单状态(并主动同步:若微信已支付但本地未标记,则更新本地订单,便于配额即时生效)
func miniprogramPayGet(c *gin.Context) {
orderSn := c.Query("orderSn")
if orderSn == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少订单号"})
return
}
ctx := c.Request.Context()
tradeState, transactionID, totalFee, err := wechat.QueryOrderByOutTradeNo(ctx, orderSn)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": map[string]interface{}{
"status": "unknown",
"orderSn": orderSn,
},
})
return
}
status := "paying"
switch tradeState {
case "SUCCESS":
status = "paid"
// 若微信已支付,主动同步到本地 orders不等 PayNotify便于购买次数即时生效
db := database.DB()
var order model.Order
if err := db.Where("order_sn = ?", orderSn).First(&order).Error; err == nil && order.Status != nil && *order.Status != "paid" {
now := time.Now()
db.Model(&order).Updates(map[string]interface{}{
"status": "paid",
"transaction_id": transactionID,
"pay_time": now,
})
orderPollLogf("主动同步订单已支付: %s", orderSn)
}
case "CLOSED", "REVOKED", "PAYERROR":
status = "failed"
case "REFUND":
status = "refunded"
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": map[string]interface{}{
"status": status,
"orderSn": orderSn,
"transactionId": transactionID,
"totalFee": totalFee,
},
})
}
// MiniprogramPayNotify POST /api/miniprogram/pay/notifyv3 支付回调PowerWeChat 验签解密)
func MiniprogramPayNotify(c *gin.Context) {
resp, err := wechat.HandlePayNotify(c.Request, func(orderSn, transactionID string, totalFee int, attachStr, openID string) error {
totalAmount := float64(totalFee) / 100
fmt.Printf("[PayNotify] 支付成功: orderSn=%s, transactionId=%s, amount=%.2f\n", orderSn, transactionID, totalAmount)
var attach struct {
ProductType string `json:"productType"`
ProductID string `json:"productId"`
UserID string `json:"userId"`
}
if attachStr != "" {
_ = json.Unmarshal([]byte(attachStr), &attach)
}
db := database.DB()
buyerUserID := attach.UserID
if openID != "" {
var user model.User
if err := db.Where("open_id = ?", openID).First(&user).Error; err == nil {
if attach.UserID != "" && user.ID != attach.UserID {
fmt.Printf("[PayNotify] 买家身份校验: attach.userId 与 openId 解析不一致,以 openId 为准\n")
}
buyerUserID = user.ID
}
}
if buyerUserID == "" && attach.UserID != "" {
buyerUserID = attach.UserID
}
var order model.Order
result := db.Where("order_sn = ?", orderSn).First(&order)
if result.Error != nil {
fmt.Printf("[PayNotify] 订单不存在,补记订单: %s\n", orderSn)
productID := attach.ProductID
if productID == "" {
productID = "fullbook"
}
productType := attach.ProductType
if productType == "" {
productType = "unknown"
}
desc := "支付回调补记订单"
status := "paid"
now := time.Now()
order = model.Order{
ID: orderSn,
OrderSN: orderSn,
UserID: buyerUserID,
OpenID: openID,
ProductType: productType,
ProductID: &productID,
Amount: totalAmount,
Description: &desc,
Status: &status,
TransactionID: &transactionID,
PayTime: &now,
}
if err := db.Create(&order).Error; err != nil {
fmt.Printf("[PayNotify] 补记订单失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("create order: %w", err)
}
} else if *order.Status != "paid" {
status := "paid"
now := time.Now()
if err := db.Model(&order).Updates(map[string]interface{}{
"status": status,
"transaction_id": transactionID,
"pay_time": now,
}).Error; err != nil {
fmt.Printf("[PayNotify] 更新订单状态失败: %s, err=%v\n", orderSn, err)
return fmt.Errorf("update order: %w", err)
}
fmt.Printf("[PayNotify] 订单状态已更新为已支付: %s\n", orderSn)
} else {
fmt.Printf("[PayNotify] 订单已支付,跳过更新: %s\n", orderSn)
}
if buyerUserID != "" && attach.ProductType != "" {
if attach.ProductType == "fullbook" {
db.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true)
fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID)
} else if attach.ProductType == "vip" {
// VIP 支付成功:更新 users.is_vip、vip_expire_date、vip_activated_at排序后付款在前
expireDate := time.Now().AddDate(0, 0, 365)
vipActivatedAt := time.Now()
if order.PayTime != nil {
vipActivatedAt = *order.PayTime
}
db.Model(&model.User{}).Where("id = ?", buyerUserID).Updates(map[string]interface{}{
"is_vip": true,
"vip_expire_date": expireDate,
"vip_activated_at": vipActivatedAt,
})
fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", buyerUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05"))
} else if attach.ProductType == "match" {
fmt.Printf("[PayNotify] 用户购买匹配次数: %s订单 %s\n", buyerUserID, orderSn)
} else if attach.ProductType == "section" && attach.ProductID != "" {
var count int64
db.Model(&model.Order{}).Where(
"user_id = ? AND product_type = 'section' AND product_id = ? AND status = 'paid' AND order_sn != ?",
buyerUserID, attach.ProductID, orderSn,
).Count(&count)
if count == 0 {
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", buyerUserID, attach.ProductID)
} else {
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", buyerUserID, attach.ProductID)
}
}
productID := attach.ProductID
if productID == "" {
productID = "fullbook"
}
db.Where(
"user_id = ? AND product_type = ? AND product_id = ? AND status = 'created' AND order_sn != ?",
buyerUserID, attach.ProductType, productID, orderSn,
).Delete(&model.Order{})
processReferralCommission(db, buyerUserID, totalAmount, orderSn, &order)
}
return nil
})
if err != nil {
fmt.Printf("[PayNotify] 处理回调失败: %v\n", err)
c.String(http.StatusOK, failResponse())
return
}
defer resp.Body.Close()
for k, v := range resp.Header {
if len(v) > 0 {
c.Header(k, v[0])
}
}
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
}
// 处理分销佣金(会员订单 20%/10%,内容订单 90%
func processReferralCommission(db *gorm.DB, buyerUserID string, amount float64, orderSn string, order *model.Order) {
type Binding struct {
ID int `gorm:"column:id"`
ReferrerID string `gorm:"column:referrer_id"`
ExpiryDate time.Time `gorm:"column:expiry_date"`
PurchaseCount int `gorm:"column:purchase_count"`
TotalCommission float64 `gorm:"column:total_commission"`
}
var binding Binding
err := db.Raw(`
SELECT id, referrer_id, expiry_date, purchase_count, total_commission
FROM referral_bindings
WHERE referee_id = ? AND status = 'active'
ORDER BY binding_date DESC
LIMIT 1
`, buyerUserID).Scan(&binding).Error
if err != nil {
fmt.Printf("[PayNotify] 用户无有效推广绑定,跳过分佣: %s\n", buyerUserID)
return
}
if time.Now().After(binding.ExpiryDate) {
fmt.Printf("[PayNotify] 绑定已过期,跳过分佣: %s\n", buyerUserID)
return
}
// 确保 order 有 referrer_id补记订单可能缺失
if order != nil && (order.ReferrerID == nil || *order.ReferrerID == "") {
order.ReferrerID = &binding.ReferrerID
db.Model(order).Update("referrer_id", binding.ReferrerID)
}
// 构建用于计算的 order若为 nil 则用 binding 信息)
calcOrder := order
if calcOrder == nil {
calcOrder = &model.Order{Amount: amount, ProductType: "unknown", ReferrerID: &binding.ReferrerID}
}
commission := computeOrderCommission(db, calcOrder, nil)
if commission <= 0 {
fmt.Printf("[PayNotify] 佣金为 0跳过分佣: orderSn=%s\n", orderSn)
return
}
newPurchaseCount := binding.PurchaseCount + 1
newTotalCommission := binding.TotalCommission + commission
fmt.Printf("[PayNotify] 处理分佣: referrerId=%s, amount=%.2f, commission=%.2f\n",
binding.ReferrerID, amount, commission)
db.Model(&model.User{}).Where("id = ?", binding.ReferrerID).
Update("pending_earnings", db.Raw("pending_earnings + ?", commission))
db.Exec(`
UPDATE referral_bindings
SET last_purchase_date = NOW(),
purchase_count = COALESCE(purchase_count, 0) + 1,
total_commission = COALESCE(total_commission, 0) + ?
WHERE id = ?
`, commission, binding.ID)
fmt.Printf("[PayNotify] 分佣完成: 推广者 %s 获得 %.2f 元(第 %d 次购买,累计 %.2f 元)\n",
binding.ReferrerID, commission, newPurchaseCount, newTotalCommission)
}
// 微信支付回调响应
func successResponse() string {
return `<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`
}
func failResponse() string {
return `<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>`
}
// MiniprogramPhone POST /api/miniprogram/phone
func MiniprogramPhone(c *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
UserID string `json:"userId"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "缺少code参数"})
return
}
// 获取手机号
phoneNumber, countryCode, err := wechat.GetPhoneNumber(req.Code)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "获取手机号失败",
"error": err.Error(),
})
return
}
// 如果提供了 userId更新到数据库
if req.UserID != "" {
db := database.DB()
db.Model(&model.User{}).Where("id = ?", req.UserID).Update("phone", phoneNumber)
fmt.Printf("[MiniprogramPhone] 手机号已绑定到用户: %s\n", req.UserID)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"phoneNumber": phoneNumber,
"countryCode": countryCode,
})
}
// MiniprogramQrcode POST /api/miniprogram/qrcode
func MiniprogramQrcode(c *gin.Context) {
var req struct {
Scene string `json:"scene"`
Page string `json:"page"`
Width int `json:"width"`
ChapterID string `json:"chapterId"`
UserID string `json:"userId"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "参数错误"})
return
}
// 构建 scene 参数
scene := req.Scene
if scene == "" {
var parts []string
if req.UserID != "" {
userId := req.UserID
if len(userId) > 15 {
userId = userId[:15]
}
parts = append(parts, fmt.Sprintf("ref=%s", userId))
}
if req.ChapterID != "" {
parts = append(parts, fmt.Sprintf("ch=%s", req.ChapterID))
}
if len(parts) == 0 {
scene = "soul"
} else {
scene = strings.Join(parts, "&")
}
}
page := req.Page
if page == "" {
page = "pages/index/index"
}
width := req.Width
if width == 0 {
width = 280
}
fmt.Printf("[MiniprogramQrcode] 生成小程序码, scene=%s\n", scene)
// 生成小程序码
imageData, err := wechat.GenerateMiniProgramCode(scene, page, width)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"error": fmt.Sprintf("生成小程序码失败: %v", err),
})
return
}
// 转换为 base64
base64Image := fmt.Sprintf("data:image/png;base64,%s", base64Encode(imageData))
c.JSON(http.StatusOK, gin.H{
"success": true,
"image": base64Image,
"scene": scene,
})
}
// MiniprogramQrcodeImage GET /api/miniprogram/qrcode/image?scene=xxx&page=xxx&width=280
// 直接返回 image/png供小程序 wx.downloadFile 使用,便于开发工具与真机统一用 tempFilePath 绘制
func MiniprogramQrcodeImage(c *gin.Context) {
scene := c.Query("scene")
if scene == "" {
scene = "soul"
}
page := c.DefaultQuery("page", "pages/read/read")
width, _ := strconv.Atoi(c.DefaultQuery("width", "280"))
if width <= 0 {
width = 280
}
imageData, err := wechat.GenerateMiniProgramCode(scene, page, width)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": fmt.Sprintf("生成小程序码失败: %v", err),
})
return
}
c.Header("Content-Type", "image/png")
c.Data(http.StatusOK, "image/png", imageData)
}
// base64 编码
func base64Encode(data []byte) string {
const base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var result strings.Builder
for i := 0; i < len(data); i += 3 {
b1, b2, b3 := data[i], byte(0), byte(0)
if i+1 < len(data) {
b2 = data[i+1]
}
if i+2 < len(data) {
b3 = data[i+2]
}
result.WriteByte(base64Table[b1>>2])
result.WriteByte(base64Table[((b1&0x03)<<4)|(b2>>4)])
if i+1 < len(data) {
result.WriteByte(base64Table[((b2&0x0F)<<2)|(b3>>6)])
} else {
result.WriteByte('=')
}
if i+2 < len(data) {
result.WriteByte(base64Table[b3&0x3F])
} else {
result.WriteByte('=')
}
}
return result.String()
}
// MiniprogramUsers GET /api/miniprogram/users 小程序-用户列表/单个(首页超级个体补充、会员详情回退)
// 支持 ?limit=20 返回列表;?id=xxx 返回单个。返回 { success, data } 格式
func MiniprogramUsers(c *gin.Context) {
id := c.Query("id")
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
if limit < 1 || limit > 50 {
limit = 20
}
db := database.DB()
if id != "" {
var user model.User
if err := db.Where("id = ?", id).First(&user).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "data": nil})
return
}
var cnt int64
db.Model(&model.Order{}).Where("user_id = ? AND status = ? AND (product_type = ? OR product_type = ?)",
id, "paid", "fullbook", "vip").Count(&cnt)
// 用户信息与会员资料vip*、P3 资料扩展,供会员详情页完整展示
item := gin.H{
"id": user.ID,
"nickname": getStringValue(user.Nickname),
"avatar": getStringValue(user.Avatar),
"phone": getStringValue(user.Phone),
"wechatId": getStringValue(user.WechatID),
"vipName": getStringValue(user.VipName),
"vipAvatar": getStringValue(user.VipAvatar),
"vipContact": getStringValue(user.VipContact),
"vipProject": getStringValue(user.VipProject),
"vipBio": getStringValue(user.VipBio),
"mbti": getStringValue(user.Mbti),
"region": getStringValue(user.Region),
"industry": getStringValue(user.Industry),
"position": getStringValue(user.Position),
"businessScale": getStringValue(user.BusinessScale),
"skills": getStringValue(user.Skills),
"storyBestMonth": getStringValue(user.StoryBestMonth),
"storyAchievement": getStringValue(user.StoryAchievement),
"storyTurning": getStringValue(user.StoryTurning),
"helpOffer": getStringValue(user.HelpOffer),
"helpNeed": getStringValue(user.HelpNeed),
"projectIntro": getStringValue(user.ProjectIntro),
"is_vip": cnt > 0,
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": item})
return
}
var users []model.User
db.Order("created_at DESC").Limit(limit).Find(&users)
list := make([]gin.H, 0, len(users))
for i := range users {
u := &users[i]
var cnt int64
db.Model(&model.Order{}).Where("user_id = ? AND status = ? AND (product_type = ? OR product_type = ?)",
u.ID, "paid", "fullbook", "vip").Count(&cnt)
list = append(list, gin.H{
"id": u.ID,
"nickname": getStringValue(u.Nickname),
"avatar": getStringValue(u.Avatar),
"is_vip": cnt > 0,
})
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
}