Files
doc_ai_backed/internal/service/user_service.go
liuyuanchuang 45dcef5702 feat: add proxy
2026-03-06 11:03:41 +08:00

290 lines
8.8 KiB
Go

package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"net/url"
"gitea.com/texpixel/document_ai/config"
model "gitea.com/texpixel/document_ai/internal/model/user"
"gitea.com/texpixel/document_ai/internal/storage/cache"
"gitea.com/texpixel/document_ai/internal/storage/dao"
"gitea.com/texpixel/document_ai/pkg/common"
"gitea.com/texpixel/document_ai/pkg/log"
"gitea.com/texpixel/document_ai/pkg/sms"
"golang.org/x/crypto/bcrypt"
)
type UserService struct {
userDao *dao.UserDao
}
func NewUserService() *UserService {
return &UserService{
userDao: dao.NewUserDao(),
}
}
func (svc *UserService) GetSmsCode(ctx context.Context, phone string) (string, error) {
limit, err := cache.GetUserSendSmsLimit(ctx, phone)
if err != nil {
log.Error(ctx, "func", "GetSmsCode", "msg", "get user send sms limit error", "error", err)
return "", err
}
if limit >= cache.UserSendSmsLimitCount {
return "", errors.New("sms code send limit reached")
}
user, err := svc.userDao.GetByPhone(dao.DB.WithContext(ctx), phone)
if err != nil {
log.Error(ctx, "func", "GetSmsCode", "msg", "get user error", "error", err)
return "", err
}
if user == nil {
user = &dao.User{Phone: phone}
err = svc.userDao.Create(dao.DB.WithContext(ctx), user)
if err != nil {
log.Error(ctx, "func", "GetSmsCode", "msg", "create user error", "error", err)
return "", err
}
}
code := fmt.Sprintf("%06d", rand.Intn(1000000))
err = sms.SendMessage(&sms.SendSmsRequest{PhoneNumbers: phone, SignName: sms.Signature, TemplateCode: sms.TemplateCode, TemplateParam: fmt.Sprintf(sms.TemplateParam, code)})
if err != nil {
log.Error(ctx, "func", "GetSmsCode", "msg", "send message error", "error", err)
return "", err
}
cacheErr := cache.SetUserSmsCode(ctx, phone, code)
if cacheErr != nil {
log.Error(ctx, "func", "GetSmsCode", "msg", "set user sms code error", "error", cacheErr)
}
cacheErr = cache.SetUserSendSmsLimit(ctx, phone)
if cacheErr != nil {
log.Error(ctx, "func", "GetSmsCode", "msg", "set user send sms limit error", "error", cacheErr)
}
return code, nil
}
func (svc *UserService) VerifySmsCode(ctx context.Context, phone, code string) (uid int64, err error) {
user, err := svc.userDao.GetByPhone(dao.DB.WithContext(ctx), phone)
if err != nil {
log.Error(ctx, "func", "VerifySmsCode", "msg", "get user error", "error", err, "phone", phone)
return 0, err
}
if user == nil {
log.Error(ctx, "func", "VerifySmsCode", "msg", "user not found", "phone", phone)
return 0, errors.New("user not found")
}
storedCode, err := cache.GetUserSmsCode(ctx, phone)
if err != nil {
log.Error(ctx, "func", "VerifySmsCode", "msg", "get user sms code error", "error", err)
return 0, err
}
if storedCode != code {
log.Error(ctx, "func", "VerifySmsCode", "msg", "invalid sms code", "phone", phone, "code", code, "storedCode", storedCode)
return 0, errors.New("invalid sms code")
}
cacheErr := cache.DeleteUserSmsCode(ctx, phone)
if cacheErr != nil {
log.Error(ctx, "func", "VerifySmsCode", "msg", "delete user sms code error", "error", cacheErr)
}
return user.ID, nil
}
func (svc *UserService) GetUserInfo(ctx context.Context, uid int64) (*dao.User, error) {
user, err := svc.userDao.GetByID(dao.DB.WithContext(ctx), uid)
if err != nil {
log.Error(ctx, "func", "GetUserInfo", "msg", "get user error", "error", err)
return nil, err
}
if user == nil {
log.Warn(ctx, "func", "GetUserInfo", "msg", "user not found", "uid", uid)
return &dao.User{}, nil
}
return user, nil
}
func (svc *UserService) RegisterByEmail(ctx context.Context, email, password string) (uid int64, err error) {
existingUser, err := svc.userDao.GetByEmail(dao.DB.WithContext(ctx), email)
if err != nil {
log.Error(ctx, "func", "RegisterByEmail", "msg", "get user by email error", "error", err)
return 0, err
}
if existingUser != nil {
log.Warn(ctx, "func", "RegisterByEmail", "msg", "email already registered", "email", email)
return 0, common.ErrEmailExists
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
log.Error(ctx, "func", "RegisterByEmail", "msg", "hash password error", "error", err)
return 0, err
}
user := &dao.User{
Email: email,
Password: string(hashedPassword),
}
err = svc.userDao.Create(dao.DB.WithContext(ctx), user)
if err != nil {
log.Error(ctx, "func", "RegisterByEmail", "msg", "create user error", "error", err)
return 0, err
}
return user.ID, nil
}
func (svc *UserService) LoginByEmail(ctx context.Context, email, password string) (uid int64, err error) {
user, err := svc.userDao.GetByEmail(dao.DB.WithContext(ctx), email)
if err != nil {
log.Error(ctx, "func", "LoginByEmail", "msg", "get user by email error", "error", err)
return 0, err
}
if user == nil {
log.Warn(ctx, "func", "LoginByEmail", "msg", "user not found", "email", email)
return 0, common.ErrEmailNotFound
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
log.Warn(ctx, "func", "LoginByEmail", "msg", "password mismatch", "email", email)
return 0, common.ErrPasswordMismatch
}
return user.ID, nil
}
type googleTokenResponse struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
}
func (svc *UserService) googleHTTPClient() *http.Client {
if config.GlobalConfig.Google.Proxy == "" {
return &http.Client{}
}
proxyURL, err := url.Parse(config.GlobalConfig.Google.Proxy)
if err != nil {
return &http.Client{}
}
return &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
}
func (svc *UserService) ExchangeGoogleCodeAndGetUserInfo(ctx context.Context, clientID, clientSecret, code, redirectURI string) (*model.GoogleUserInfo, error) {
tokenURL := "https://oauth2.googleapis.com/token"
formData := url.Values{
"client_id": {clientID},
"client_secret": {clientSecret},
"code": {code},
"grant_type": {"authorization_code"},
"redirect_uri": {redirectURI},
}
client := svc.googleHTTPClient()
resp, err := client.PostForm(tokenURL, formData)
if err != nil {
log.Error(ctx, "func", "ExchangeGoogleCodeAndGetUserInfo", "msg", "exchange code failed", "error", err)
return nil, err
}
defer resp.Body.Close()
var tokenResp googleTokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
log.Error(ctx, "func", "ExchangeGoogleCodeAndGetUserInfo", "msg", "decode token response failed", "error", err)
return nil, err
}
if tokenResp.AccessToken == "" {
log.Error(ctx, "func", "ExchangeGoogleCodeAndGetUserInfo", "msg", "no access token in response")
return nil, errors.New("no access token in response")
}
userInfo, err := svc.getGoogleUserInfo(ctx, tokenResp.AccessToken)
if err != nil {
log.Error(ctx, "func", "ExchangeGoogleCodeAndGetUserInfo", "msg", "get user info failed", "error", err)
return nil, err
}
return &model.GoogleUserInfo{
ID: userInfo.ID,
Email: userInfo.Email,
Name: userInfo.Name,
}, nil
}
func (svc *UserService) getGoogleUserInfo(ctx context.Context, accessToken string) (*model.GoogleUserInfo, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/oauth2/v2/userinfo", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
client := svc.googleHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var userInfo model.GoogleUserInfo
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
return nil, err
}
return &userInfo, nil
}
func (svc *UserService) FindOrCreateGoogleUser(ctx context.Context, userInfo *model.GoogleUserInfo) (uid int64, err error) {
existingUser, err := svc.userDao.GetByGoogleID(dao.DB.WithContext(ctx), userInfo.ID)
if err != nil {
log.Error(ctx, "func", "FindOrCreateGoogleUser", "msg", "get user by google id error", "error", err)
return 0, err
}
if existingUser != nil {
return existingUser.ID, nil
}
existingUser, err = svc.userDao.GetByEmail(dao.DB.WithContext(ctx), userInfo.Email)
if err != nil {
log.Error(ctx, "func", "FindOrCreateGoogleUser", "msg", "get user by email error", "error", err)
return 0, err
}
if existingUser != nil {
existingUser.GoogleID = userInfo.ID
err = svc.userDao.Update(dao.DB.WithContext(ctx), existingUser)
if err != nil {
log.Error(ctx, "func", "FindOrCreateGoogleUser", "msg", "update user google id error", "error", err)
return 0, err
}
return existingUser.ID, nil
}
user := &dao.User{
Email: userInfo.Email,
GoogleID: userInfo.ID,
Username: userInfo.Name,
}
err = svc.userDao.Create(dao.DB.WithContext(ctx), user)
if err != nil {
log.Error(ctx, "func", "FindOrCreateGoogleUser", "msg", "create user error", "error", err)
return 0, err
}
return user.ID, nil
}