Files
backend/cloudprint-backend/internal/service/auth_service.go
2026-04-05 11:57:47 +08:00

251 lines
6.4 KiB
Go

package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"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
}
// CreateAdmin 创建管理员账号
func (s *AuthService) CreateAdmin(ctx context.Context, username, password, nickname string, role int8) (*model.Admin, error) {
// 检查用户名是否已存在
existingAdmin, err := s.adminRepo.GetByUsername(ctx, username)
if err != nil {
return nil, fmt.Errorf("failed to check existing admin: %w", err)
}
if existingAdmin != nil {
return nil, errors.New("username already exists")
}
// 对密码进行哈希处理
passwordHash, err := HashPassword(password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
// 创建管理员
admin := &model.Admin{
Username: username,
PasswordHash: passwordHash,
Nickname: nickname,
Role: role,
}
if err := s.adminRepo.Create(ctx, admin); err != nil {
return nil, fmt.Errorf("failed to create admin: %w", err)
}
return admin, nil
}
// UpdateAdmin 更新管理员信息
func (s *AuthService) UpdateAdmin(ctx context.Context, id int32, nickname string, role int8) (*model.Admin, error) {
admin, err := s.adminRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get admin: %w", err)
}
if admin == nil {
return nil, errors.New("admin not found")
}
admin.Nickname = nickname
admin.Role = role
if err := s.adminRepo.Update(ctx, admin); err != nil {
return nil, fmt.Errorf("failed to update admin: %w", err)
}
return admin, nil
}
// DeleteAdmin 删除管理员
func (s *AuthService) DeleteAdmin(ctx context.Context, id int32) error {
return s.adminRepo.Delete(ctx, id)
}
// GetAdminList 获取管理员列表
func (s *AuthService) GetAdminList(ctx context.Context, page, pageSize int) ([]model.Admin, int64, error) {
return s.adminRepo.List(ctx, page, pageSize)
}
// 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
}