Files
workspace/code/allapp/allapp-go-v3/internal/handle/auth.go
T
2026-04-24 17:28:11 +08:00

284 lines
5.9 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 handle
import (
"allapp-go/internal/errors"
"allapp-go/internal/httpx"
"allapp-go/internal/types"
"allapp-go/pkg/db"
"allapp-go/pkg/jwtx"
"allapp-go/pkg/requestx"
"allapp-go/pkg/s3store"
"allapp-go/pkg/uniqueid"
"allapp-go/pkg/wechat"
"fmt"
"net/url"
"path/filepath"
"time"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/log"
"github.com/google/uuid"
)
// LoginQq ======================== QQ 登录 ========================
func LoginQq(c fiber.Ctx) error {
var req types.LoginQqReq
vo := new(types.LoginResp)
if err := httpx.BindAndValidate(c, &req); err != nil {
return errors.WithStack(err)
}
return handleThirdLogin(c, req.Openid, req.Nickname, req.Avatar, 1, vo)
}
// LoginWechat ======================== 微信登录 ========================
func LoginWechat(c fiber.Ctx) error {
var req types.LoginWechatReq
vo := new(types.LoginResp)
if err := httpx.BindAndValidate(c, &req); err != nil {
return errors.WithStack(err)
}
openid, token, err := wechat.GetWechatAccess(req.Code)
if err != nil {
log.Errorw("微信获取Token失败", "code", req.Code, "error", err)
return httpx.Fail(c, "微信登录失败,请重试")
}
nickname, avatar, err := wechat.GetWechatUserInfo(token, openid)
if err != nil {
log.Errorw("微信获取用户信息系失败", "openid", openid, "error", err)
return httpx.Fail(c, "微信登录失败,请重试")
}
return handleThirdLogin(c, openid, nickname, avatar, 0, vo)
}
// ======================== 第三方登录主流程 ========================
func handleThirdLogin(
c fiber.Ctx,
openid string,
nickname string,
avatar string,
loginType int16,
vo *types.LoginResp,
) error {
dbClient := db.New()
user, err := getUserByOpenID(dbClient, c, openid, loginType)
if err != nil {
return errors.WithStack(err)
}
// 不存在 -> 注册
if user == nil {
return registerUser(dbClient, c, openid, nickname, avatar, loginType, vo)
}
// ========= 校验状态 =========
id := toInt64(user["id"])
// ========= 更新登录时间 =========
if err := dbClient.Update(
c.Context(),
"b_user",
"id",
map[string]any{
"id": id,
"last_login_time": "NOW()",
},
); err != nil {
return errors.WithStack(err)
}
// ========= token =========
token, err := jwtx.CreateToken(c.Context(), jwtx.TokenData{
UserID: id,
})
if err != nil {
return errors.WithStack(err)
}
familyMembers, err := dbClient.LoadData(
c.Context(),
"b_family_member",
"user_id = ?",
"",
[]string{"family_id"},
[]any{id},
)
if err != nil {
return errors.WithStack(err)
}
if len(familyMembers) == 0 {
vo.FamilyId = 0
} else {
familyMember := familyMembers[0]
familyId := toInt64(familyMember["family_id"])
vo.FamilyId = familyId
}
// ========= 返回 =========
vo.Token = token
vo.UserId = id
vo.Nickname, _ = user["nickname"].(string)
vo.Avatar, _ = user["avatar"].(string)
return httpx.OK(c, vo)
}
// ======================== 注册 ========================
func registerUser(
dbClient *db.Client,
c fiber.Ctx,
openid, nickname, avatar string,
loginType int16,
vo *types.LoginResp,
) error {
userId := uniqueid.NextId()
defaultAvatar := "https://default-avatar-url.com/default.png"
if nickname == "" {
nickname = fmt.Sprintf("用户_%d", userId)
}
if avatar == "" {
avatar = defaultAvatar
} else {
// 下载头像(流式)
stream, size, contentType, err := requestx.GetFileStream(c.Context(), avatar)
if err != nil {
avatar = defaultAvatar
} else {
defer func() {
_ = stream.Close()
}()
// ✅ 从 avatar URL 获取扩展名(简化版)
u, _ := url.Parse(avatar)
ext := filepath.Ext(u.Path)
if ext == "" {
ext = ".jpg" // 兜底
}
// 年月日 + uuid
now := time.Now()
key := fmt.Sprintf("%d/%02d/%02d/%s%s",
now.Year(),
now.Month(),
now.Day(),
uuid.NewString(),
ext,
)
// 上传到 S3
if err := s3store.UploadToRustFS(
c.Context(),
stream,
key,
size,
contentType,
); err != nil {
avatar = defaultAvatar
} else {
avatar = key
}
}
}
err := dbClient.WithTx(c.Context(), func(tx *db.Client) error {
if err := tx.Insert(c.Context(), "b_user", "id", map[string]any{
"id": userId,
"nickname": nickname,
"avatar": avatar,
"create_time": "NOW()",
"update_time": "NOW()",
"last_login_time": "NOW()",
}); err != nil {
return err
}
if err := tx.Insert(c.Context(), "b_user_oauth", "id", map[string]any{
"id": uniqueid.NextId(),
"user_id": userId,
"type": loginType,
"openid": openid,
"create_time": "NOW()",
"update_time": "NOW()",
}); err != nil {
return err
}
return nil
})
if err != nil {
return errors.WithStack(err)
}
token, err := jwtx.CreateToken(c.Context(), jwtx.TokenData{
UserID: userId,
})
if err != nil {
return errors.WithStack(err)
}
vo.Token = token
vo.UserId = userId
vo.Nickname = nickname
vo.Avatar = avatar
vo.FamilyId = 0
return httpx.OK(c, vo)
}
// ======================== DB 查询封装(去重复 SQL) ========================
func getUserByOpenID(dbClient *db.Client, c fiber.Ctx, openid string, loginType int16) (map[string]any, error) {
users, err := dbClient.LoadDataBySQL(
c.Context(),
`SELECT u.*
FROM b_user_oauth o
JOIN b_user u ON u.id = o.user_id
WHERE o.openid = $1 AND o.type = $2`,
[]any{openid, loginType},
)
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, nil
}
return users[0], nil
}
// ======================== 类型工具(极简版) ========================
func toInt64(v any) int64 {
switch val := v.(type) {
case int64:
return val
case int32:
return int64(val)
case int16:
return int64(val)
case int:
return int64(val)
case float64:
return int64(val)
default:
fmt.Printf("unknown type: %T, value=%v\n", v, v)
return 0
}
}