123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>handler: Go Coverage Report</title>
- <style>
- body {
- background: black;
- color: rgb(80, 80, 80);
- }
- body, pre, #legend span {
- font-family: Menlo, monospace;
- font-weight: bold;
- }
- #topbar {
- background: black;
- position: fixed;
- top: 0; left: 0; right: 0;
- height: 42px;
- border-bottom: 1px solid rgb(80, 80, 80);
- }
- #content {
- margin-top: 50px;
- }
- #nav, #legend {
- float: left;
- margin-left: 10px;
- }
- #legend {
- margin-top: 12px;
- }
- #nav {
- margin-top: 10px;
- }
- #legend span {
- margin: 0 5px;
- }
- .cov0 { color: rgb(192, 0, 0) }
- .cov1 { color: rgb(128, 128, 128) }
- .cov2 { color: rgb(116, 140, 131) }
- .cov3 { color: rgb(104, 152, 134) }
- .cov4 { color: rgb(92, 164, 137) }
- .cov5 { color: rgb(80, 176, 140) }
- .cov6 { color: rgb(68, 188, 143) }
- .cov7 { color: rgb(56, 200, 146) }
- .cov8 { color: rgb(44, 212, 149) }
- .cov9 { color: rgb(32, 224, 152) }
- .cov10 { color: rgb(20, 236, 155) }
- </style>
- </head>
- <body>
- <div id="topbar">
- <div id="nav">
- <select id="files">
-
- <option value="file0">github.com/go-nunu/nunu-layout-advanced/internal/handler/handler.go (80.0%)</option>
-
- <option value="file1">github.com/go-nunu/nunu-layout-advanced/internal/handler/user.go (55.6%)</option>
-
- <option value="file2">github.com/go-nunu/nunu-layout-advanced/internal/repository/repository.go (8.3%)</option>
-
- <option value="file3">github.com/go-nunu/nunu-layout-advanced/internal/repository/user.go (64.7%)</option>
-
- <option value="file4">github.com/go-nunu/nunu-layout-advanced/internal/service/service.go (100.0%)</option>
-
- <option value="file5">github.com/go-nunu/nunu-layout-advanced/internal/service/user.go (80.0%)</option>
-
- </select>
- </div>
- <div id="legend">
- <span>not tracked</span>
-
- <span class="cov0">not covered</span>
- <span class="cov8">covered</span>
-
- </div>
- </div>
- <div id="content">
-
- <pre class="file" id="file0" style="display: none">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 <span class="cov8" title="1">{
- return &Handler{
- logger: logger,
- }
- }</span>
- func GetUserIdFromCtx(ctx *gin.Context) string <span class="cov8" title="1">{
- v, exists := ctx.Get("claims")
- if !exists </span><span class="cov0" title="0">{
- return ""
- }</span>
- <span class="cov8" title="1">return v.(*middleware.MyCustomClaims).UserId</span>
- }
- </pre>
-
- <pre class="file" id="file1" style="display: none">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 <span class="cov8" title="1">{
- return &userHandler{
- Handler: handler,
- userService: userService,
- }
- }</span>
- func (h *userHandler) Register(ctx *gin.Context) <span class="cov8" title="1">{
- req := new(service.RegisterRequest)
- if err := ctx.ShouldBindJSON(req); err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">if err := h.userService.Register(ctx, req); err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">resp.HandleSuccess(ctx, nil)</span>
- }
- func (h *userHandler) Login(ctx *gin.Context) <span class="cov8" title="1">{
- var req service.LoginRequest
- if err := ctx.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">token, err := h.userService.Login(ctx, &req)
- if err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusUnauthorized, 1, err.Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">resp.HandleSuccess(ctx, gin.H{
- "accessToken": token,
- })</span>
- }
- func (h *userHandler) GetProfile(ctx *gin.Context) <span class="cov8" title="1">{
- userId := GetUserIdFromCtx(ctx)
- if userId == "" </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusUnauthorized, 1, "unauthorized", nil)
- return
- }</span>
- <span class="cov8" title="1">user, err := h.userService.GetProfile(ctx, userId)
- if err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusBadRequest, 1, err.Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">resp.HandleSuccess(ctx, user)</span>
- }
- func (h *userHandler) UpdateProfile(ctx *gin.Context) <span class="cov8" title="1">{
- userId := GetUserIdFromCtx(ctx)
- var req service.UpdateProfileRequest
- if err := ctx.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">if err := h.userService.UpdateProfile(ctx, userId, &req); err != nil </span><span class="cov0" title="0">{
- resp.HandleError(ctx, http.StatusBadRequest, 1, err.Error(), nil)
- return
- }</span>
- <span class="cov8" title="1">resp.HandleSuccess(ctx, nil)</span>
- }
- </pre>
-
- <pre class="file" id="file2" style="display: none">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 <span class="cov8" title="1">{
- return &Repository{
- db: db,
- rdb: rdb,
- logger: logger,
- }
- }</span>
- func NewDB(conf *viper.Viper) *gorm.DB <span class="cov0" title="0">{
- db, err := gorm.Open(mysql.Open(conf.GetString("data.mysql.user")), &gorm.Config{})
- if err != nil </span><span class="cov0" title="0">{
- panic(err)</span>
- }
- <span class="cov0" title="0">return db</span>
- }
- func NewRedis(conf *viper.Viper) *redis.Client <span class="cov0" title="0">{
- 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 </span><span class="cov0" title="0">{
- panic(fmt.Sprintf("redis error: %s", err.Error()))</span>
- }
- <span class="cov0" title="0">return rdb</span>
- }
- </pre>
-
- <pre class="file" id="file3" style="display: none">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 <span class="cov8" title="1">{
- return &userRepository{
- Repository: r,
- }
- }</span>
- func (r *userRepository) Create(ctx context.Context, user *model.User) error <span class="cov8" title="1">{
- if err := r.db.Create(user).Error; err != nil </span><span class="cov0" title="0">{
- return errors.Wrap(err, "failed to create user")
- }</span>
- <span class="cov8" title="1">return nil</span>
- }
- func (r *userRepository) Update(ctx context.Context, user *model.User) error <span class="cov8" title="1">{
- if err := r.db.Save(user).Error; err != nil </span><span class="cov0" title="0">{
- return errors.Wrap(err, "failed to update user")
- }</span>
- <span class="cov8" title="1">return nil</span>
- }
- func (r *userRepository) GetByID(ctx context.Context, userId string) (*model.User, error) <span class="cov8" title="1">{
- var user model.User
- if err := r.db.Where("user_id = ?", userId).First(&user).Error; err != nil </span><span class="cov0" title="0">{
- return nil, errors.Wrap(err, "failed to get user by ID")
- }</span>
- <span class="cov8" title="1">return &user, nil</span>
- }
- func (r *userRepository) GetByUsername(ctx context.Context, username string) (*model.User, error) <span class="cov8" title="1">{
- var user model.User
- if err := r.db.Where("username = ?", username).First(&user).Error; err != nil </span><span class="cov0" title="0">{
- if err == gorm.ErrRecordNotFound </span><span class="cov0" title="0">{
- return nil, nil
- }</span>
- <span class="cov0" title="0">return nil, errors.Wrap(err, "failed to get user by username")</span>
- }
- <span class="cov8" title="1">return &user, nil</span>
- }
- </pre>
-
- <pre class="file" id="file4" style="display: none">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 <span class="cov8" title="1">{
- return &Service{
- logger: logger,
- sid: sid,
- jwt: jwt,
- }
- }</span>
- </pre>
-
- <pre class="file" id="file5" style="display: none">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 <span class="cov8" title="1">{
- return &userService{
- userRepo: userRepo,
- Service: service,
- }
- }</span>
- func (s *userService) Register(ctx context.Context, req *RegisterRequest) error <span class="cov8" title="1">{
- // 检查用户名是否已存在
- if user, err := s.userRepo.GetByUsername(ctx, req.Username); err == nil && user != nil </span><span class="cov8" title="1">{
- return errors.New("username already exists")
- }</span>
- <span class="cov8" title="1">hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
- if err != nil </span><span class="cov0" title="0">{
- return errors.Wrap(err, "failed to hash password")
- }</span>
- // 生成用户ID
- <span class="cov8" title="1">userId, err := s.sid.GenString()
- if err != nil </span><span class="cov0" title="0">{
- return errors.Wrap(err, "failed to generate user ID")
- }</span>
- // 创建用户
- <span class="cov8" title="1">user := &model.User{
- UserId: userId,
- Username: req.Username,
- Password: string(hashedPassword),
- Email: req.Email,
- }
- if err = s.userRepo.Create(ctx, user); err != nil </span><span class="cov0" title="0">{
- return errors.Wrap(err, "failed to create user")
- }</span>
- <span class="cov8" title="1">return nil</span>
- }
- func (s *userService) Login(ctx context.Context, req *LoginRequest) (string, error) <span class="cov8" title="1">{
- user, err := s.userRepo.GetByUsername(ctx, req.Username)
- if err != nil || user == nil </span><span class="cov8" title="1">{
- return "", errors.Wrap(err, "failed to get user by username")
- }</span>
- <span class="cov8" title="1">err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
- if err != nil </span><span class="cov0" title="0">{
- return "", errors.Wrap(err, "failed to hash password")
- }</span>
- // 生成JWT token
- <span class="cov8" title="1">token, err := s.GenerateToken(ctx, user.UserId)
- if err != nil </span><span class="cov0" title="0">{
- return "", errors.Wrap(err, "failed to generate JWT token")
- }</span>
- <span class="cov8" title="1">return token, nil</span>
- }
- func (s *userService) GetProfile(ctx context.Context, userId string) (*model.User, error) <span class="cov8" title="1">{
- user, err := s.userRepo.GetByID(ctx, userId)
- if err != nil </span><span class="cov0" title="0">{
- return nil, errors.Wrap(err, "failed to get user by ID")
- }</span>
- <span class="cov8" title="1">return user, nil</span>
- }
- func (s *userService) UpdateProfile(ctx context.Context, userId string, req *UpdateProfileRequest) error <span class="cov8" title="1">{
- user, err := s.userRepo.GetByID(ctx, userId)
- if err != nil </span><span class="cov8" title="1">{
- return errors.Wrap(err, "failed to get user by ID")
- }</span>
- <span class="cov8" title="1">user.Email = req.Email
- user.Nickname = req.Nickname
- if err = s.userRepo.Update(ctx, user); err != nil </span><span class="cov0" title="0">{
- return errors.Wrap(err, "failed to update user")
- }</span>
- <span class="cov8" title="1">return nil</span>
- }
- func (s *userService) GenerateToken(ctx context.Context, userId string) (string, error) <span class="cov8" title="1">{
- // 生成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 </span><span class="cov0" title="0">{
- return "", errors.Wrap(err, "failed to generate JWT token")
- }</span>
- <span class="cov8" title="1">return token, nil</span>
- }
- </pre>
-
- </div>
- </body>
- <script>
- (function() {
- var files = document.getElementById('files');
- var visible;
- files.addEventListener('change', onChange, false);
- function select(part) {
- if (visible)
- visible.style.display = 'none';
- visible = document.getElementById(part);
- if (!visible)
- return;
- files.value = part;
- visible.style.display = 'block';
- location.hash = part;
- }
- function onChange() {
- select(files.value);
- window.scrollTo(0, 0);
- }
- if (location.hash != "") {
- select(location.hash.substr(1));
- }
- if (!visible) {
- select("file0");
- }
- })();
- </script>
- </html>
|