package handler
import (
"github.com/gin-gonic/gin"
"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
"github.com/go-nunu/nunu-layout-advanced/pkg/log"
)
type Handler struct {
logger *log.Logger
}
func NewHandler(logger *log.Logger) *Handler {
return &Handler{
logger: logger,
}
}
func GetUserIdFromCtx(ctx *gin.Context) string {
v, exists := ctx.Get("claims")
if !exists {
return ""
}
return v.(*middleware.MyCustomClaims).UserId
}
package handler
import (
"github.com/gin-gonic/gin"
"github.com/go-nunu/nunu-layout-advanced/internal/service"
"github.com/go-nunu/nunu-layout-advanced/pkg/helper/resp"
"github.com/pkg/errors"
"net/http"
)
type UserHandler interface {
Register(ctx *gin.Context)
Login(ctx *gin.Context)
GetProfile(ctx *gin.Context)
UpdateProfile(ctx *gin.Context)
}
type userHandler struct {
*Handler
userService service.UserService
}
func NewUserHandler(handler *Handler, userService service.UserService) UserHandler {
return &userHandler{
Handler: handler,
userService: userService,
}
}
func (h *userHandler) Register(ctx *gin.Context) {
req := new(service.RegisterRequest)
if err := ctx.ShouldBindJSON(req); err != nil {
resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
return
}
if err := h.userService.Register(ctx, req); err != nil {
resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
return
}
resp.HandleSuccess(ctx, nil)
}
func (h *userHandler) Login(ctx *gin.Context) {
var req service.LoginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
return
}
token, err := h.userService.Login(ctx, &req)
if err != nil {
resp.HandleError(ctx, http.StatusUnauthorized, 1, err.Error(), nil)
return
}
resp.HandleSuccess(ctx, gin.H{
"accessToken": token,
})
}
func (h *userHandler) GetProfile(ctx *gin.Context) {
userId := GetUserIdFromCtx(ctx)
if userId == "" {
resp.HandleError(ctx, http.StatusUnauthorized, 1, "unauthorized", nil)
return
}
user, err := h.userService.GetProfile(ctx, userId)
if err != nil {
resp.HandleError(ctx, http.StatusBadRequest, 1, err.Error(), nil)
return
}
resp.HandleSuccess(ctx, user)
}
func (h *userHandler) UpdateProfile(ctx *gin.Context) {
userId := GetUserIdFromCtx(ctx)
var req service.UpdateProfileRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
return
}
if err := h.userService.UpdateProfile(ctx, userId, &req); err != nil {
resp.HandleError(ctx, http.StatusBadRequest, 1, err.Error(), nil)
return
}
resp.HandleSuccess(ctx, nil)
}
package repository
import (
"context"
"fmt"
"github.com/go-nunu/nunu-layout-advanced/pkg/log"
"github.com/redis/go-redis/v9"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type Repository struct {
db *gorm.DB
rdb *redis.Client
logger *log.Logger
}
func NewRepository(db *gorm.DB, rdb *redis.Client, logger *log.Logger) *Repository {
return &Repository{
db: db,
rdb: rdb,
logger: logger,
}
}
func NewDB(conf *viper.Viper) *gorm.DB {
db, err := gorm.Open(mysql.Open(conf.GetString("data.mysql.user")), &gorm.Config{})
if err != nil {
panic(err)
}
return db
}
func NewRedis(conf *viper.Viper) *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: conf.GetString("data.redis.addr"),
Password: conf.GetString("data.redis.password"),
DB: conf.GetInt("data.redis.db"),
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := rdb.Ping(ctx).Result()
if err != nil {
panic(fmt.Sprintf("redis error: %s", err.Error()))
}
return rdb
}
package repository
import (
"context"
"github.com/go-nunu/nunu-layout-advanced/internal/model"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type UserRepository interface {
Create(ctx context.Context, user *model.User) error
Update(ctx context.Context, user *model.User) error
GetByID(ctx context.Context, id string) (*model.User, error)
GetByUsername(ctx context.Context, username string) (*model.User, error)
}
type userRepository struct {
*Repository
}
func NewUserRepository(r *Repository) UserRepository {
return &userRepository{
Repository: r,
}
}
func (r *userRepository) Create(ctx context.Context, user *model.User) error {
if err := r.db.Create(user).Error; err != nil {
return errors.Wrap(err, "failed to create user")
}
return nil
}
func (r *userRepository) Update(ctx context.Context, user *model.User) error {
if err := r.db.Save(user).Error; err != nil {
return errors.Wrap(err, "failed to update user")
}
return nil
}
func (r *userRepository) GetByID(ctx context.Context, userId string) (*model.User, error) {
var user model.User
if err := r.db.Where("user_id = ?", userId).First(&user).Error; err != nil {
return nil, errors.Wrap(err, "failed to get user by ID")
}
return &user, nil
}
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*model.User, error) {
var user model.User
if err := r.db.Where("username = ?", username).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, errors.Wrap(err, "failed to get user by username")
}
return &user, nil
}
package service
import (
"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sid"
"github.com/go-nunu/nunu-layout-advanced/pkg/log"
)
type Service struct {
logger *log.Logger
sid *sid.Sid
jwt *middleware.JWT
}
func NewService(logger *log.Logger, sid *sid.Sid, jwt *middleware.JWT) *Service {
return &Service{
logger: logger,
sid: sid,
jwt: jwt,
}
}
package service
import (
"context"
"github.com/go-nunu/nunu-layout-advanced/internal/model"
"github.com/go-nunu/nunu-layout-advanced/internal/repository"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"time"
)
type RegisterRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
type UpdateProfileRequest struct {
Nickname string `json:"nickname"`
Email string `json:"email" binding:"required,email"`
Avatar string `json:"avatar"`
}
type ChangePasswordRequest struct {
OldPassword string `json:"oldPassword" binding:"required"`
NewPassword string `json:"newPassword" binding:"required"`
}
type UserService interface {
Register(ctx context.Context, req *RegisterRequest) error
Login(ctx context.Context, req *LoginRequest) (string, error)
GetProfile(ctx context.Context, userId string) (*model.User, error)
UpdateProfile(ctx context.Context, userId string, req *UpdateProfileRequest) error
GenerateToken(ctx context.Context, userId string) (string, error)
}
type userService struct {
userRepo repository.UserRepository
*Service
}
func NewUserService(service *Service, userRepo repository.UserRepository) UserService {
return &userService{
userRepo: userRepo,
Service: service,
}
}
func (s *userService) Register(ctx context.Context, req *RegisterRequest) error {
// 检查用户名是否已存在
if user, err := s.userRepo.GetByUsername(ctx, req.Username); err == nil && user != nil {
return errors.New("username already exists")
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return errors.Wrap(err, "failed to hash password")
}
// 生成用户ID
userId, err := s.sid.GenString()
if err != nil {
return errors.Wrap(err, "failed to generate user ID")
}
// 创建用户
user := &model.User{
UserId: userId,
Username: req.Username,
Password: string(hashedPassword),
Email: req.Email,
}
if err = s.userRepo.Create(ctx, user); err != nil {
return errors.Wrap(err, "failed to create user")
}
return nil
}
func (s *userService) Login(ctx context.Context, req *LoginRequest) (string, error) {
user, err := s.userRepo.GetByUsername(ctx, req.Username)
if err != nil || user == nil {
return "", errors.Wrap(err, "failed to get user by username")
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
if err != nil {
return "", errors.Wrap(err, "failed to hash password")
}
// 生成JWT token
token, err := s.GenerateToken(ctx, user.UserId)
if err != nil {
return "", errors.Wrap(err, "failed to generate JWT token")
}
return token, nil
}
func (s *userService) GetProfile(ctx context.Context, userId string) (*model.User, error) {
user, err := s.userRepo.GetByID(ctx, userId)
if err != nil {
return nil, errors.Wrap(err, "failed to get user by ID")
}
return user, nil
}
func (s *userService) UpdateProfile(ctx context.Context, userId string, req *UpdateProfileRequest) error {
user, err := s.userRepo.GetByID(ctx, userId)
if err != nil {
return errors.Wrap(err, "failed to get user by ID")
}
user.Email = req.Email
user.Nickname = req.Nickname
if err = s.userRepo.Update(ctx, user); err != nil {
return errors.Wrap(err, "failed to update user")
}
return nil
}
func (s *userService) GenerateToken(ctx context.Context, userId string) (string, error) {
// 生成JWT token
s.jwt.GenToken(userId, time.Now().Add(time.Hour*24*90))
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"userId": userId,
"exp": time.Now().Add(time.Hour * 24).Unix(),
}).SignedString([]byte("secret"))
if err != nil {
return "", errors.Wrap(err, "failed to generate JWT token")
}
return token, nil
}