feat: 初始提交云印享付费打印服务后端
This commit is contained in:
189
cloudprint-backend/internal/service/auth_service.go
Normal file
189
cloudprint-backend/internal/service/auth_service.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cloudprint/cloudprint-backend/config"
|
||||
"github.com/cloudprint/cloudprint-backend/internal/model"
|
||||
"github.com/cloudprint/cloudprint-backend/internal/repository"
|
||||
"github.com/cloudprint/cloudprint-backend/pkg/jwt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type AuthService struct {
|
||||
userRepo *repository.UserRepository
|
||||
adminRepo *repository.AdminRepository
|
||||
jwtManager *jwt.JWTManager
|
||||
redis *RedisClient
|
||||
wxConfig *config.WeChatConfig
|
||||
}
|
||||
|
||||
type RedisClient struct {
|
||||
// In production, use actual Redis client
|
||||
}
|
||||
|
||||
func NewAuthService(
|
||||
userRepo *repository.UserRepository,
|
||||
adminRepo *repository.AdminRepository,
|
||||
jwtManager *jwt.JWTManager,
|
||||
redis *RedisClient,
|
||||
wxConfig *config.WeChatConfig,
|
||||
) *AuthService {
|
||||
return &AuthService{
|
||||
userRepo: userRepo,
|
||||
adminRepo: adminRepo,
|
||||
jwtManager: jwtManager,
|
||||
redis: redis,
|
||||
wxConfig: wxConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// WeChat code exchange response
|
||||
type WeChatCodeResponse struct {
|
||||
OpenID string `json:"openid"`
|
||||
SessionKey string `json:"session_key"`
|
||||
UnionID string `json:"unionid,omitempty"`
|
||||
ErrCode int `json:"errcode,omitempty"`
|
||||
ErrMsg string `json:"errmsg,omitempty"`
|
||||
}
|
||||
|
||||
func (s *AuthService) Login(ctx context.Context, code string) (*LoginResult, error) {
|
||||
// Exchange code for openid from WeChat
|
||||
openid, err := s.getWeChatOpenID(code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get wechat openid: %w", err)
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
user, err := s.userRepo.GetByOpenid(ctx, openid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
isNewUser := false
|
||||
if user == nil {
|
||||
isNewUser = true
|
||||
user = &model.User{
|
||||
Openid: openid,
|
||||
}
|
||||
if err := s.userRepo.Create(ctx, user); err != nil {
|
||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, "user", "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate access token: %w", err)
|
||||
}
|
||||
|
||||
refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, "user")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
|
||||
}
|
||||
|
||||
return &LoginResult{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
User: user,
|
||||
IsNewUser: isNewUser,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) getWeChatOpenID(code string) (string, error) {
|
||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||
s.wxConfig.AppID, s.wxConfig.AppSecret, code)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result WeChatCodeResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.ErrCode != 0 {
|
||||
return "", fmt.Errorf("wechat error: %s", result.ErrMsg)
|
||||
}
|
||||
|
||||
return result.OpenID, nil
|
||||
}
|
||||
|
||||
func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (string, error) {
|
||||
return s.jwtManager.RefreshAccessToken(refreshToken)
|
||||
}
|
||||
|
||||
func (s *AuthService) GetUserInfo(ctx context.Context, userID int32) (*model.User, error) {
|
||||
return s.userRepo.GetByID(ctx, userID)
|
||||
}
|
||||
|
||||
func (s *AuthService) UpdateUser(ctx context.Context, userID int32, nickname, avatar string) (*model.User, error) {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil || user == nil {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
user.Nickname = nickname
|
||||
if avatar != "" {
|
||||
user.Avatar = avatar
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
type LoginResult struct {
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
User *model.User
|
||||
IsNewUser bool
|
||||
}
|
||||
|
||||
// Admin login
|
||||
func (s *AuthService) AdminLogin(ctx context.Context, username, password string) (*AdminLoginResult, error) {
|
||||
admin, err := s.adminRepo.GetByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get admin: %w", err)
|
||||
}
|
||||
if admin == nil {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.PasswordHash), []byte(password)); err != nil {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Generate token
|
||||
accessToken, err := s.jwtManager.GenerateAccessToken(admin.ID, "admin", admin.Username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate token: %w", err)
|
||||
}
|
||||
|
||||
return &AdminLoginResult{
|
||||
AccessToken: accessToken,
|
||||
Admin: admin,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type AdminLoginResult struct {
|
||||
AccessToken string
|
||||
Admin *model.Admin
|
||||
}
|
||||
|
||||
// HashPassword creates a bcrypt hash of the password
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
Reference in New Issue
Block a user