Files
soul-yongping/soul-api/internal/handler/wechat.go
Alex-larget 28ad08da84 同步
2026-03-24 15:44:08 +08:00

170 lines
5.0 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 (
"fmt"
"net/http"
"strings"
"time"
"soul-api/internal/database"
"soul-api/internal/model"
"soul-api/internal/wechat"
"github.com/gin-gonic/gin"
)
// WechatLogin POST /api/wechat/login
func WechatLogin(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
}
// WechatPhoneLoginReq 手机号登录请求code 为 wx.login() 的 codephoneCode 为 getPhoneNumber 返回的 code
type WechatPhoneLoginReq struct {
Code string `json:"code"` // wx.login() 得到,用于 code2session 拿 openId
PhoneCode string `json:"phoneCode"` // getPhoneNumber 得到,用于换手机号
}
// WechatPhoneLogin POST /api/wechat/phone-login
// 请求体code必填+ phoneCode必填。先 code2session 得到 openId再 getPhoneNumber 得到手机号,创建/更新用户并返回与 /api/miniprogram/login 一致的数据结构。
func WechatPhoneLogin(c *gin.Context) {
var req WechatPhoneLoginReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 code 或 phoneCode"})
return
}
if req.Code == "" || req.PhoneCode == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "请提供 code 与 phoneCode"})
return
}
openID, sessionKey, _, err := wechat.Code2Session(req.Code)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": fmt.Sprintf("微信登录失败: %v", err)})
return
}
phoneNumber, countryCode, err := wechat.GetPhoneNumber(req.PhoneCode)
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 {
// 软删除后再次登录:旧记录 id=openid 仍存在,需用新 id 避免主键冲突
userID := "user_" + randomSuffix()
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 := "[]"
phone := phoneNumber
if countryCode != "" && countryCode != "86" {
phone = "+" + countryCode + " " + phoneNumber
}
user = model.User{
ID: userID,
OpenID: &openID,
SessionKey: &sessionKey,
Nickname: &nickname,
Avatar: &avatar,
Phone: &phone,
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 {
phone := phoneNumber
if countryCode != "" && countryCode != "86" {
phone = "+" + countryCode + " " + phoneNumber
}
db.Model(&user).Updates(map[string]interface{}{"session_key": sessionKey, "phone": phone})
user.Phone = &phone
}
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)
purchasedSections := []string{}
for _, row := range orderRows {
if row.ProductID != "" {
purchasedSections = append(purchasedSections, row.ProductID)
}
}
responseUser := map[string]interface{}{
"id": user.ID,
"openId": strVal(user.OpenID),
"nickname": strVal(user.Nickname),
"avatar": resolveAvatarURL(strVal(user.Avatar)),
"phone": strVal(user.Phone),
"wechatId": strVal(user.WechatID),
"referralCode": strVal(user.ReferralCode),
"hasFullBook": boolVal(user.HasFullBook),
"purchasedSections": purchasedSections,
"earnings": floatVal(user.Earnings),
"pendingEarnings": floatVal(user.PendingEarnings),
"referralCount": intVal(user.ReferralCount),
"createdAt": user.CreatedAt,
}
// 与 /api/miniprogram/login 一致,避免手机号登录后 VIP 引导、权益展示滞后
if user.IsVip != nil {
responseUser["isVip"] = *user.IsVip
}
if user.VipExpireDate != nil {
responseUser["vipExpireDate"] = user.VipExpireDate.Format("2006-01-02")
}
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 strVal(p *string) string {
if p == nil {
return ""
}
return *p
}
func boolVal(p *bool) bool {
if p == nil {
return false
}
return *p
}
func floatVal(p *float64) float64 {
if p == nil {
return 0
}
return *p
}
func intVal(p *int) int {
if p == nil {
return 0
}
return *p
}