Files
web_huangguo_web/app/service/authser/auth.go
T
fish03andClaude Opus 5.5 bef9f8b4ed Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 14:43:16 +07:00

262 lines
7.2 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 authser
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"videoweb/app/service/upstream"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
const (
registerPath = "/api/app/auth/register"
loginPath = "/api/app/auth/login"
resetPath = "/api/app/auth/resetPassword"
guestPath = "/api/app/auth/guest"
getInfoPath = "/api/app/auth/getInfo"
profilePath = "/api/app/auth/profile"
uploadPath = "/api/app/upload/image"
cookieMaxAge = 7 * 24 * 3600
)
// Register 调用上游注册接口。
func Register(c *gin.Context, req RegisterReq) (*APIEnvelope, *LoginResp, error) {
var env APIEnvelope
if _, err := upstream.PostJSON(c, registerPath, req, &env); err != nil {
return nil, nil, err
}
if env.Code != 200 {
return &env, nil, nil
}
user, err := decodeLoginData(env.Data)
if err != nil {
return &env, nil, err
}
return &env, user, nil
}
// ResetPassword 通过账号和邮箱校验后重置密码。
func ResetPassword(c *gin.Context, req ResetPasswordReq) (*APIEnvelope, error) {
var env APIEnvelope
if _, err := upstream.PostJSON(c, resetPath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
// Login 调用上游登录接口。
func Login(c *gin.Context, req LoginReq) (*APIEnvelope, *LoginResp, error) {
var env APIEnvelope
if _, err := upstream.PostJSON(c, loginPath, req, &env); err != nil {
return nil, nil, err
}
if env.Code != 200 {
return &env, nil, nil
}
user, err := decodeLoginData(env.Data)
if err != nil {
return &env, nil, err
}
return &env, user, nil
}
// GuestLogin 调用上游游客登录(按设备 ID 创建或找回游客账号)。
func GuestLogin(c *gin.Context, req GuestLoginReq) (*APIEnvelope, *LoginResp, error) {
var env APIEnvelope
if _, err := upstream.PostJSON(c, guestPath, req, &env); err != nil {
return nil, nil, err
}
if env.Code != 200 {
return &env, nil, nil
}
user, err := decodeLoginData(env.Data)
if err != nil {
return &env, nil, err
}
return &env, user, nil
}
// GetInfo 通过 token 拉取最新用户信息。
func GetInfo(c *gin.Context) (*APIEnvelope, *LoginResp, error) {
var env APIEnvelope
if err := upstream.GetJSON(c, getInfoPath, &env); err != nil {
return nil, nil, err
}
if env.Code != 200 {
return &env, nil, nil
}
user, err := decodeLoginData(env.Data)
if err != nil {
return &env, nil, err
}
return &env, user, nil
}
// UpdateProfile 修改资料(昵称 / 头像等)。
func UpdateProfile(c *gin.Context, req UpdateProfileReq) (*APIEnvelope, error) {
var env APIEnvelope
if _, err := upstream.PostJSON(c, profilePath, req, &env); err != nil {
return nil, err
}
return &env, nil
}
// UploadImage 上传图片;头像场景不加水印。
func UploadImage(c *gin.Context, fileName string, r io.Reader) (*ImageUploadResp, error) {
var resp ImageUploadResp
_, err := upstream.PostMultipart(c, uploadPath, map[string]string{
"watermark": "0",
}, &upstream.MultipartFile{
FieldName: "file",
FileName: fileName,
Reader: r,
}, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
func decodeLoginData(raw json.RawMessage) (*LoginResp, error) {
if len(raw) == 0 || string(raw) == "null" || string(raw) == `""` {
return nil, fmt.Errorf("empty login data")
}
var user LoginResp
if err := json.Unmarshal(raw, &user); err != nil {
return nil, err
}
return &user, nil
}
// SetAuthCookies 将登录/注册返回写入 cookie。
func SetAuthCookies(c *gin.Context, user *LoginResp) {
if c == nil || user == nil {
return
}
secure := c.Request != nil && c.Request.TLS != nil
if strings.TrimSpace(user.Token) != "" {
setCookie(c, "token", user.Token, secure)
}
setCookie(c, "userId", strconv.FormatInt(user.UserID, 10), secure)
setCookie(c, "accountNo", user.AccountNo, secure)
setCookie(c, "nickName", user.NickName, secure)
setCookie(c, "introduction", user.Introduction, secure)
setCookie(c, "avatar", user.Avatar, secure)
setCookie(c, "email", user.Email, secure)
setCookie(c, "inviteCode", user.InviteCode, secure)
setCookie(c, "recharge", strconv.FormatInt(user.Recharge, 10), secure)
setCookie(c, "vipType", strconv.Itoa(user.VipType), secure)
setCookie(c, "currentVipExpire", user.CurrentVipExpire, secure)
setCookie(c, "showCardId", strconv.FormatInt(user.ShowCardID, 10), secure)
setCookie(c, "showCardName", user.ShowCardName, secure)
setCookie(c, "userType", strconv.Itoa(user.Type), secure)
isReg := "0"
if user.IsRegister || user.Type == 1 {
isReg = "1"
}
setCookie(c, "isRegister", isReg, secure)
if sid := useragent.SIDFromRequest(c.Request); sid != "" {
setCookie(c, "sid", sid, secure)
}
}
func setCookie(c *gin.Context, name, value string, secure bool) {
// Cookie Value 仅允许 ASCII;中文昵称等需 URL 编码。
// 使用 PathEscape(空格→%20),与前端 encodeURIComponent 一致;
// 避免 QueryEscape 的空格→+,否则 JS decodeURIComponent 会留下字面量 "+"。
http.SetCookie(c.Writer, &http.Cookie{
Name: name,
Value: url.PathEscape(value),
Path: "/",
MaxAge: cookieMaxAge,
HttpOnly: false,
Secure: secure,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(7 * 24 * time.Hour),
})
}
func cookieValue(c *gin.Context, name string) string {
ck, err := c.Request.Cookie(name)
if err != nil || ck == nil {
return ""
}
raw := strings.TrimSpace(ck.Value)
if raw == "" {
return ""
}
// QueryUnescape 同时兼容历史 QueryEscape(+)与 PathEscape(%20)
if decoded, err := url.QueryUnescape(raw); err == nil {
return strings.TrimSpace(decoded)
}
return raw
}
// ClearAuthCookies 清除登录相关 cookie。
func ClearAuthCookies(c *gin.Context) {
for _, name := range authCookieNames() {
http.SetCookie(c.Writer, &http.Cookie{
Name: name,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: false,
SameSite: http.SameSiteLaxMode,
})
}
}
func authCookieNames() []string {
return []string{
"token", "_token_", "sid", "_s_token_",
"userId", "accountNo", "nickName", "introduction", "avatar",
"email", "inviteCode", "recharge", "vipType", "currentVipExpire", "showCardId", "showCardName",
"userType", "isRegister",
}
}
// AuthFromCookies 从 cookie 读取登录态,供 SSR 头部 / 个人中心展示。
func AuthFromCookies(c *gin.Context) (loggedIn bool, userName, avatar, email string) {
token := useragent.TokenFromRequest(c.Request)
if token == "" {
return false, "", "", ""
}
userName = cookieValue(c, "nickName")
if userName == "" {
userName = cookieValue(c, "accountNo")
}
avatar = usableAvatarURL(cookieValue(c, "avatar"))
email = cookieValue(c, "email")
return true, userName, avatar, email
}
// IsRegisteredFromCookies 是否正式注册账号(非游客)。
func IsRegisteredFromCookies(c *gin.Context) bool {
if useragent.TokenFromRequest(c.Request) == "" {
return false
}
if cookieValue(c, "isRegister") == "1" {
return true
}
return cookieValue(c, "userType") == "1"
}
func usableAvatarURL(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
if strings.HasPrefix(raw, "http://") || strings.HasPrefix(raw, "https://") || strings.HasPrefix(raw, "/static/") {
return raw
}
// 无域名的相对路径暂不可用(解密拉取需要完整 URL)
return ""
}