Browse Source

feat: Adding Unit Test Cases

chris 2 years ago
parent
commit
b2f5d2ad0c

+ 15 - 0
Makefile

@@ -0,0 +1,15 @@
+.PHONY: init
+init:
+	go install github.com/google/wire/cmd/wire@latest
+	go install github.com/golang/mock/mockgen@latest
+
+.PHONY: mock
+mock:
+	mockgen -source=internal/service/user.go -destination mocks/service/user.go
+	mockgen -source=internal/repository/user.go -destination mocks/repository/user.go
+
+.PHONY: test
+test:
+	go test -coverpkg=./internal/handler,./internal/service,./internal/repository -coverprofile=./.nunu/coverage.out ./test/server/...
+	go tool cover -html=./.nunu/coverage.out -o coverage.html
+

+ 84 - 94
README.md

@@ -1,20 +1,17 @@
-# Nunu — A CLI tool for building go aplication.
+# nunu-layout-basic - Basic Layout
 
-Nunu is an application scaffold based on Golang, named after a game character in League of Legends, a little boy riding on the shoulder of a snow monster. Like Nunu, this project also stands on the shoulders of giants, integrating various popular libraries in the Golang ecosystem. Their combination can help you quickly build an efficient and reliable application.
-
-[中文介绍](https://github.com/go-nunu/nunu/blob/main/README_zh.md)
+Nunu is an application scaffold based on Golang. Its name comes from a game character in League of Legends, a little boy riding on the shoulder of a yeti. Like Nunu, this project also stands on the shoulders of giants. It is a composition of various popular libraries from the Golang ecosystem, which can help you quickly build efficient and reliable applications.
 
+[English Introduction](https://github.com/go-nunu/nunu/blob/main/README.md)
 
 ![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/banner.png)
 
-
-
-
-
-
+## Documentation
+* [User Guide](https://github.com/go-nunu/nunu/blob/main/docs/en/guide.md)
+* [Architecture](https://github.com/go-nunu/nunu/blob/main/docs/en/architecture.md)
+* [Getting Started Tutorial](https://github.com/go-nunu/nunu/blob/main/docs/en/tutorial.md)
 
 ## Features
-
 - **Gin**: https://github.com/gin-gonic/gin
 - **Gorm**: https://github.com/go-gorm/gorm
 - **Wire**: https://github.com/google/wire
@@ -27,103 +24,92 @@ Nunu is an application scaffold based on Golang, named after a game character in
 - **gocron**:  https://github.com/go-co-op/gocron
 - More...
 
-## Features
-* **Low learning cost and customization**: Nunu encapsulates some popular libraries that Gopher is most familiar with. You can easily customize your application to meet specific needs.
-* **High performance and scalability**: Nunu aims to have high performance and scalability. It uses the latest technology and best practices to ensure that your application can handle high traffic and large amounts of data.
-* **Secure and reliable**: Nunu uses stable and reliable third-party libraries to ensure the security and reliability of your application.
-* **Modular and extensible**: Nunu is designed to be modular and extensible. You can easily add new features and functionality by using third-party libraries or writing your own modules.
-* **Complete documentation and testing**: Nunu has complete documentation and testing. It provides comprehensive documentation and examples to help you get started quickly. It also includes a set of test suites to ensure that your application works as expected.
+## Highlights
+* **Low Learning Curve and Customization**: Nunu encapsulates popular libraries that Gophers are familiar with. You can easily customize the application to meet specific requirements.
+* **High Performance and Scalability**: Nunu aims to be high-performance and scalable. It utilizes the latest technologies and best practices to ensure your application can handle high traffic and large data volumes.
+* **Security and Reliability**: Nunu uses stable and reliable third-party libraries to ensure the security and reliability of your application.
+* **Modularity and Extensibility**: Nunu is designed to be modular and extensible. You can easily add new features and functionalities by using third-party libraries or writing your own modules.
+* **Comprehensive Documentation and Test Coverage**: Nunu has comprehensive documentation and test coverage. It provides detailed documentation and examples to help you get started quickly. It also includes a test suite to ensure your application works as expected.
 
 ## Nunu CLI
 
 ![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/screenshot.jpg)
 
-
-## Documentation
-* [Guide](https://github.com/go-nunu/nunu/blob/main/docs/en/guide.md)
-* [Architecture](https://github.com/go-nunu/nunu/blob/main/docs/en/architecture.md)
-* [Tutorial](https://github.com/go-nunu/nunu/blob/main/docs/en/tutorial.md)
-
-
 ## Directory Structure
 ```
 .
 ├── cmd
-│   └── server
-│       ├── wire
-│       │   ├── wire.go
-│       │   └── wire_gen.go
-│       └── main.go
+│   └── server
+│       ├── main.go
+│       ├── wire.go
+│       └── wire_gen.go
 ├── config
-│   ├── local.yml
-│   └── prod.yml
+│   ├── local.yml
+│   └── prod.yml
 ├── internal
-│   ├── dao
-│   │   ├── dao.go
-│   │   └── user.go
-│   ├── handler
-│   │   ├── handler.go
-│   │   └── user.go
-│   ├── middleware
-│   │   └── cors.go
-│   ├── model
-│   │   └── user.go
-│   ├── provider
-│   │   └── provider.go
-│   ├── server
-│   │   └── http.go
-│   └── service
-│       ├── service.go
-│       └── user.go
+│   ├── handler
+│   │   ├── handler.go
+│   │   └── user.go
+│   ├── middleware
+│   │   └── cors.go
+│   ├── model
+│   │   └── user.go
+│   ├── repository
+│   │   ├── repository.go
+│   │   └── user.go
+│   ├── server
+│   │   └── http.go
+│   └── service
+│       ├── service.go
+│       └── user.go
 ├── pkg
-│   ├── config
-│   │   └── config.go
-│   ├── helper
-│   │   ├── md5
-│   │   │   └── md5.go
-│   │   ├── resp
-│   │   │   └── resp.go
-│   │   ├── sonyflake
-│   │   │   └── sonyflake.go
-│   │   └── uuid
-│   │       └── uuid.go
-│   ├── http
-│   │   └── http.go
-│   └── log
-│       └── log.go
 ├── LICENSE
 ├── README.md
 ├── README_zh.md
 ├── go.mod
 └── go.sum
-```
 
+```
 
-This is the directory structure of a classic Golang project, which includes the following directories:
-
-- `cmd`: Contains the code for command-line applications, such as `main.go`.
-- `config`: Contains configuration files, such as `config.yaml`.
-- `internal`: Contains internal code that is not exposed externally.
-    - `dao`: Contains the code for Data Access Objects (DAOs).
-    - `handler`: Contains the code for HTTP request handlers.
-    - `middleware`: Contains the code for HTTP middleware.
-    - `model`: Contains the code for data models.
-    - `provider`: Contains the code for dependency injection.
-    - `server`: Contains the code for HTTP servers.
-    - `service`: Contains the code for business logic.
-- `pkg`: Contains reusable code that is exposed externally.
-    - `config`: Contains the code for reading configuration files.
-    - `helper`: Contains the code for helper functions.
-    - `http`: Contains HTTP-related code.
-    - `log`: Contains code related to logging.
+This is a classic directory structure for a Golang project, which includes the following directories:
+
+- cmd: Contains the entry points of the application, including the main function and dependency injection code.
+  - server: The main entry point of the application, including the main function and dependency injection code.
+    - main.go: The main function used to start the application.
+    - wire.go: The dependency injection code generated using Wire.
+    - wire_gen.go: The dependency injection code generated using Wire.
+
+- config: Contains the configuration files of the application.
+  - local.yml: The configuration file for the local environment.
+  - prod.yml: The configuration file for the production environment.
+
+- internal: Contains the internal code of the application.
+  - handler: Contains the handlers for handling HTTP requests.
+    - handler.go: The common handler for handling HTTP requests.
+    - user.go: The handler for handling user-related HTTP requests.
+  - middleware: Contains the middleware code.
+    - cors.go: The CORS (Cross-Origin Resource Sharing) middleware.
+  - model: Contains the data model code.
+    - user.go: The user data model.
+  - repository: Contains the data access code.
+    - repository.go: The common interface for data access.
+    - user.go: The implementation of the user data access interface.
+  - server: Contains the server code.
+    - http.go: The implementation of the HTTP server.
+  - service: Contains the business logic code.
+    - service.go: The common interface for business logic.
+    - user.go: The implementation of the user business logic.
+
+- pkg: Contains the public packages of the application.
+- storage: Contains the storage files of the application.
+- go.mod: The Go module file.
+- go.sum: The dependency versions file for the Go module.
 
 ## Requirements
-To use Nunu, you need to install the following software on your system:
+To use Nunu, you need to have the following software installed on your system:
 
 * Golang 1.16 or higher
 * Git
-* MySQL 5.7 or higher (optional)
-* Redis (optional)
 
 ### Installation
 
@@ -133,29 +119,33 @@ You can install Nunu using the following command:
 go install github.com/go-nunu/nunu@latest
 ```
 
-
 ### Creating a New Project
 
 You can create a new Golang project using the following command:
 
 ```bash
 nunu new projectName
+```
 
-// or
+By default, it will pull from the GitHub repository, but you can also use a mirror repository for faster access:
 
-nunu new projectName -r https://github.com/go-nunu/nunu-layout-advanced.git
+```
+// Use the basic template
+nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-basic.git
+// Use the advanced template
+nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-advanced.git
 ```
 
 This command will create a directory named `projectName` and generate an elegant Golang project structure within it.
 
 ### Creating Components
 
-You can create handlers, services, and daos for your project using the following commands:
+You can create handlers, services, repositories, and models for your project using the following commands:
 
 ```bash
 nunu create handler user
 nunu create service user
-nunu create dao user
+nunu create repository user
 nunu create model user
 ```
 or
@@ -163,7 +153,7 @@ or
 nunu create all user
 ```
 
-These commands will create components named `UserHandler`, `UserService`, `UserDao` and `UserModel`, respectively, and place them in the correct directories.
+These commands will create components named `UserHandler`, `UserService`, `UserDao`, and `UserModel` respectively and place them in the correct directories.
 
 ### Starting the Project
 
@@ -173,11 +163,11 @@ You can quickly start your project using the following command:
 nunu run
 ```
 
-This command will start your Golang project and support file update hot reload.
+This command will start your Golang project and support hot-reloading of files.
 
 ### Compiling wire.go
 
-You can quickly compile your `wire.go` file using the following command:
+You can quickly compile `wire.go` using the following command:
 
 ```bash
 nunu wire
@@ -185,10 +175,10 @@ nunu wire
 
 This command will compile your `wire.go` file and generate the required dependencies.
 
-## Contributing
+## Contribution
 
-If you find any issues or have any improvement suggestions, please feel free to raise an issue or submit a pull request. We welcome your contributions!
+If you find any issues or have any improvement suggestions, please feel free to raise an issue or submit a pull request. Your contributions are highly appreciated!
 
 ## License
 
-Nunu is released under the MIT license. See [LICENSE](LICENSE) for more information.
+Nunu is released under the MIT License. See the [LICENSE](LICENSE) file for more information.

+ 45 - 47
README_zh.md

@@ -1,4 +1,4 @@
-# Nunu — A CLI tool for building go aplication.
+# nunu-layout-basic — 基础布局
 
 
 Nunu是一个基于Golang的应用脚手架,它的名字来自于英雄联盟中的游戏角色,一个骑在雪怪肩膀上的小男孩。和努努一样,该项目也是站在巨人的肩膀上,它是由Golang生态中各种非常流行的库整合而成的,它们的组合可以帮助你快速构建一个高效、可靠的应用程序。
@@ -8,6 +8,11 @@ Nunu是一个基于Golang的应用脚手架,它的名字来自于英雄联盟
 ![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/banner.png)
 
 
+## 文档
+* [使用指南](https://github.com/go-nunu/nunu/blob/main/docs/zh/guide.md)
+* [分层架构](https://github.com/go-nunu/nunu/blob/main/docs/zh/architecture.md)
+* [上手教程](https://github.com/go-nunu/nunu/blob/main/docs/zh/tutorial.md)
+
 ## 功能
 - **Gin**: https://github.com/gin-gonic/gin
 - **Gorm**: https://github.com/go-gorm/gorm
@@ -32,26 +37,18 @@ Nunu是一个基于Golang的应用脚手架,它的名字来自于英雄联盟
 ![Nunu](https://github.com/go-nunu/nunu/blob/main/.github/assets/screenshot.jpg)
 
 
-## 文档
-* [使用指南](https://github.com/go-nunu/nunu/blob/main/docs/zh/guide.md)
-* [分层架构](https://github.com/go-nunu/nunu/blob/main/docs/zh/architecture.md)
-* [上手教程](https://github.com/go-nunu/nunu/blob/main/docs/zh/tutorial.md)
 ## 目录结构
 ```
 .
 ├── cmd
 │   └── server
-│       ├── wire
-│       │   ├── wire.go
-│       │   └── wire_gen.go
-│       └── main.go
+│       ├── main.go
+│       ├── wire.go
+│       └── wire_gen.go
 ├── config
 │   ├── local.yml
 │   └── prod.yml
 ├── internal
-│   ├── dao
-│   │   ├── dao.go
-│   │   └── user.go
 │   ├── handler
 │   │   ├── handler.go
 │   │   └── user.go
@@ -59,61 +56,62 @@ Nunu是一个基于Golang的应用脚手架,它的名字来自于英雄联盟
 │   │   └── cors.go
 │   ├── model
 │   │   └── user.go
-│   ├── provider
-│   │   └── provider.go
+│   ├── repository
+│   │   ├── repository.go
+│   │   └── user.go
 │   ├── server
 │   │   └── http.go
 │   └── service
 │       ├── service.go
 │       └── user.go
 ├── pkg
-│   ├── config
-│   │   └── config.go
-│   ├── helper
-│   │   ├── md5
-│   │   │   └── md5.go
-│   │   ├── resp
-│   │   │   └── resp.go
-│   │   ├── sonyflake
-│   │   │   └── sonyflake.go
-│   │   └── uuid
-│   │       └── uuid.go
-│   ├── http
-│   │   └── http.go
-│   └── log
-│       └── log.go
 ├── LICENSE
 ├── README.md
 ├── README_zh.md
 ├── go.mod
 └── go.sum
+
 ```
 
 这是一个经典的Golang 项目的目录结构,包含以下目录:
 
-- `cmd`:存放命令行应用的代码,例如 `main.go`。
-- `config`:存放配置文件,例如 `config.yaml`。
-- `internal`:存放项目内部的代码,不对外暴露。
-  - `dao`:存放数据访问对象(Data Access Object)的代码。
-  - `handler`:存放 HTTP 请求处理器的代码。
-  - `middleware`:存放 HTTP 中间件的代码。
-  - `model`:存放数据模型的代码。
-  - `provider`:存放依赖注入的代码。
-  - `server`:存放 HTTP 服务器的代码。
-  - `service`:存放业务逻辑的代码。
-- `pkg`:存放可重用的代码,对外暴露。
-  - `config`:存放读取配置文件的代码。
-  - `helper`:存放辅助函数的代码。
-  - `http`:存放 HTTP 相关的代码。
-  - `log`:存放日志相关的代码。
+- cmd: 存放应用程序的入口点,包括主函数和依赖注入的代码。
+  - server: 应用程序的主要入口点,包含主函数和依赖注入的代码。
+    - main.go: 主函数,用于启动应用程序。
+    - wire.go: 使用Wire库生成的依赖注入代码。
+    - wire_gen.go: 使用Wire库生成的依赖注入代码。
+
+- config: 存放应用程序的配置文件。
+  - local.yml: 本地环境的配置文件。
+  - prod.yml: 生产环境的配置文件。
+
+- internal: 存放应用程序的内部代码。
+  - handler: 处理HTTP请求的处理程序。
+    - handler.go: 处理HTTP请求的通用处理程序。
+    - user.go: 处理用户相关的HTTP请求的处理程序。
+  - middleware: 存放中间件代码。
+    - cors.go: 跨域资源共享中间件。
+  - model: 存放数据模型代码。
+    - user.go: 用户数据模型。
+  - repository: 存放数据访问代码。
+    - repository.go: 数据访问的通用接口。
+    - user.go: 用户数据访问接口的实现。
+  - server: 存放服务器代码。
+    - http.go: HTTP服务器的实现。
+  - service: 存放业务逻辑代码。
+    - service.go: 业务逻辑的通用接口。
+    - user.go: 用户业务逻辑的实现。
+
+- pkg: 存放应用程序的公共包。
+- storage: 存放应用程序的存储文件。
+- go.mod: Go模块文件。
+- go.sum: Go模块的依赖版本文件。
 
 ## 要求
 要使用Nunu,您需要在系统上安装以下软件:
 
 * Golang 1.16或更高版本
 * Git
-* MySQL5.7或更高版本(可选)
-* Redis(可选)
 
 
 
@@ -150,7 +148,7 @@ nunu new projectName -r https://gitee.com/go-nunu/nunu-layout-advanced.git
 ```bash
 nunu create handler user
 nunu create service user
-nunu create dao user
+nunu create repository user
 nunu create model user
 ```

+ 1 - 2
cmd/job/main.go

@@ -1,7 +1,6 @@
 package main
 
 import (
-	"github.com/go-nunu/nunu-layout-advanced/cmd/job/wire"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/config"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
 )
@@ -11,7 +10,7 @@ func main() {
 	logger := log.NewLog(conf)
 	logger.Info("start")
 
-	app, cleanup, err := wire.NewApp(conf, logger)
+	app, cleanup, err := newApp(conf, logger)
 	if err != nil {
 		panic(err)
 	}

+ 5 - 5
cmd/job/wire/wire.go → cmd/job/wire.go

@@ -1,19 +1,19 @@
 //go:build wireinject
 // +build wireinject
 
-package wire
+package main
 
 import (
 	"github.com/go-nunu/nunu-layout-advanced/internal/job"
-	"github.com/go-nunu/nunu-layout-advanced/internal/provider"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
 	"github.com/google/wire"
 	"github.com/spf13/viper"
 )
 
-// wire.go 初始化模块
-func NewApp(*viper.Viper, *log.Logger) (*job.Job, func(), error) {
+var JobSet = wire.NewSet(job.NewJob)
+
+func newApp(*viper.Viper, *log.Logger) (*job.Job, func(), error) {
 	panic(wire.Build(
-		provider.JobSet,
+		JobSet,
 	))
 }

+ 2 - 3
cmd/job/wire/wire_gen.go → cmd/job/wire_gen.go

@@ -4,7 +4,7 @@
 //go:build !wireinject
 // +build !wireinject
 
-package wire
+package main
 
 import (
 	"github.com/go-nunu/nunu-layout-advanced/internal/job"
@@ -14,8 +14,7 @@ import (
 
 // Injectors from wire.go:
 
-// wire.go 初始化模块
-func NewApp(viperViper *viper.Viper, logger *log.Logger) (*job.Job, func(), error) {
+func newApp(viperViper *viper.Viper, logger *log.Logger) (*job.Job, func(), error) {
 	jobJob := job.NewJob(logger)
 	return jobJob, func() {
 	}, nil

+ 1 - 2
cmd/migration/main.go

@@ -1,7 +1,6 @@
 package main
 
 import (
-	"github.com/go-nunu/nunu-layout-advanced/cmd/migration/wire"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/config"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
 )
@@ -10,7 +9,7 @@ func main() {
 	conf := config.NewConfig()
 	logger := log.NewLog(conf)
 
-	app, cleanup, err := wire.NewApp(conf, logger)
+	app, cleanup, err := newApp(conf, logger)
 	if err != nil {
 		panic(err)
 	}

+ 27 - 0
cmd/migration/wire.go

@@ -0,0 +1,27 @@
+//go:build wireinject
+// +build wireinject
+
+package main
+
+import (
+	"github.com/go-nunu/nunu-layout-advanced/internal/migration"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/google/wire"
+	"github.com/spf13/viper"
+)
+
+var RepositorySet = wire.NewSet(
+	repository.NewDB,
+	repository.NewRedis,
+	repository.NewRepository,
+	repository.NewUserRepository,
+)
+var MigrateSet = wire.NewSet(migration.NewMigrate)
+
+func newApp(*viper.Viper, *log.Logger) (*migration.Migrate, func(), error) {
+	panic(wire.Build(
+		RepositorySet,
+		MigrateSet,
+	))
+}

+ 0 - 21
cmd/migration/wire/wire.go

@@ -1,21 +0,0 @@
-//go:build wireinject
-// +build wireinject
-
-package wire
-
-import (
-	"github.com/go-nunu/nunu-layout-advanced/internal/migration"
-	"github.com/go-nunu/nunu-layout-advanced/internal/provider"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
-	"github.com/google/wire"
-	"github.com/spf13/viper"
-)
-
-// wire.go 初始化模块
-func NewApp(*viper.Viper, *log.Logger) (*migration.Migrate, func(), error) {
-	//log.Info("NewApp")
-	panic(wire.Build(
-		provider.DaoSet,
-		provider.MigrateSet,
-	))
-}

+ 4 - 5
cmd/migration/wire/wire_gen.go → cmd/migration/wire_gen.go

@@ -4,20 +4,19 @@
 //go:build !wireinject
 // +build !wireinject
 
-package wire
+package main
 
 import (
-	"github.com/go-nunu/nunu-layout-advanced/internal/dao"
 	"github.com/go-nunu/nunu-layout-advanced/internal/migration"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
 	"github.com/spf13/viper"
 )
 
 // Injectors from wire.go:
 
-// wire.go 初始化模块
-func NewApp(viperViper *viper.Viper, logger *log.Logger) (*migration.Migrate, func(), error) {
-	db := dao.NewDB(viperViper)
+func newApp(viperViper *viper.Viper, logger *log.Logger) (*migration.Migrate, func(), error) {
+	db := repository.NewDB(viperViper)
 	migrate := migration.NewMigrate(db, logger)
 	return migrate, func() {
 	}, nil

+ 1 - 2
cmd/server/main.go

@@ -2,7 +2,6 @@ package main
 
 import (
 	"fmt"
-	"github.com/go-nunu/nunu-layout-advanced/cmd/server/wire"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/config"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/http"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
@@ -13,7 +12,7 @@ func main() {
 	conf := config.NewConfig()
 	logger := log.NewLog(conf)
 
-	app, cleanup, err := wire.NewApp(conf, logger)
+	app, cleanup, err := newApp(conf, logger)
 	if err != nil {
 		panic(err)
 	}

+ 51 - 0
cmd/server/wire.go

@@ -0,0 +1,51 @@
+//go:build wireinject
+// +build wireinject
+
+package main
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-nunu/nunu-layout-advanced/internal/handler"
+	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"github.com/go-nunu/nunu-layout-advanced/internal/server"
+	"github.com/go-nunu/nunu-layout-advanced/internal/service"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sid"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/google/wire"
+	"github.com/spf13/viper"
+)
+
+var ServerSet = wire.NewSet(server.NewServerHTTP)
+
+var SidSet = wire.NewSet(sid.NewSid)
+
+var JwtSet = wire.NewSet(middleware.NewJwt)
+
+var HandlerSet = wire.NewSet(
+	handler.NewHandler,
+	handler.NewUserHandler,
+)
+
+var ServiceSet = wire.NewSet(
+	service.NewService,
+	service.NewUserService,
+)
+
+var RepositorySet = wire.NewSet(
+	repository.NewDB,
+	repository.NewRedis,
+	repository.NewRepository,
+	repository.NewUserRepository,
+)
+
+func newApp(*viper.Viper, *log.Logger) (*gin.Engine, func(), error) {
+	panic(wire.Build(
+		ServerSet,
+		RepositorySet,
+		ServiceSet,
+		HandlerSet,
+		SidSet,
+		JwtSet,
+	))
+}

+ 0 - 24
cmd/server/wire/wire.go

@@ -1,24 +0,0 @@
-//go:build wireinject
-// +build wireinject
-
-package wire
-
-import (
-	"github.com/gin-gonic/gin"
-	"github.com/go-nunu/nunu-layout-advanced/internal/provider"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
-	"github.com/google/wire"
-	"github.com/spf13/viper"
-)
-
-// wire.go 初始化模块
-func NewApp(*viper.Viper, *log.Logger) (*gin.Engine, func(), error) {
-	panic(wire.Build(
-		provider.ServerSet,
-		provider.DaoSet,
-		provider.ServiceSet,
-		provider.HandlerSet,
-		provider.SonyflakeSet,
-		provider.JwtSet,
-	))
-}

+ 0 - 38
cmd/server/wire/wire_gen.go

@@ -1,38 +0,0 @@
-// Code generated by Wire. DO NOT EDIT.
-
-//go:generate go run github.com/google/wire/cmd/wire
-//go:build !wireinject
-// +build !wireinject
-
-package wire
-
-import (
-	"github.com/gin-gonic/gin"
-	"github.com/go-nunu/nunu-layout-advanced/internal/dao"
-	"github.com/go-nunu/nunu-layout-advanced/internal/handler"
-	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
-	"github.com/go-nunu/nunu-layout-advanced/internal/server"
-	"github.com/go-nunu/nunu-layout-advanced/internal/service"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sonyflake"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
-	"github.com/spf13/viper"
-)
-
-// Injectors from wire.go:
-
-// wire.go 初始化模块
-func NewApp(viperViper *viper.Viper, logger *log.Logger) (*gin.Engine, func(), error) {
-	jwt := middleware.NewJwt(viperViper)
-	sonyflakeSonyflake := sonyflake.NewSonyflake()
-	handlerHandler := handler.NewHandler(logger, sonyflakeSonyflake)
-	serviceService := service.NewService(logger, sonyflakeSonyflake, jwt)
-	db := dao.NewDB(viperViper)
-	client := dao.NewRedis(viperViper)
-	daoDao := dao.NewDao(db, client, logger)
-	userDao := dao.NewUserDao(daoDao)
-	userService := service.NewUserService(serviceService, userDao)
-	userHandler := handler.NewUserHandler(handlerHandler, userService)
-	engine := server.NewServerHTTP(logger, jwt, userHandler)
-	return engine, func() {
-	}, nil
-}

+ 52 - 0
cmd/server/wire_gen.go

@@ -0,0 +1,52 @@
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate go run github.com/google/wire/cmd/wire
+//go:build !wireinject
+// +build !wireinject
+
+package main
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-nunu/nunu-layout-advanced/internal/handler"
+	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"github.com/go-nunu/nunu-layout-advanced/internal/server"
+	"github.com/go-nunu/nunu-layout-advanced/internal/service"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sid"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/google/wire"
+	"github.com/spf13/viper"
+)
+
+// Injectors from wire.go:
+
+func newApp(viperViper *viper.Viper, logger *log.Logger) (*gin.Engine, func(), error) {
+	jwt := middleware.NewJwt(viperViper)
+	handlerHandler := handler.NewHandler(logger)
+	sidSid := sid.NewSid()
+	serviceService := service.NewService(logger, sidSid, jwt)
+	db := repository.NewDB(viperViper)
+	client := repository.NewRedis(viperViper)
+	repositoryRepository := repository.NewRepository(db, client, logger)
+	userRepository := repository.NewUserRepository(repositoryRepository)
+	userService := service.NewUserService(serviceService, userRepository)
+	userHandler := handler.NewUserHandler(handlerHandler, userService)
+	engine := server.NewServerHTTP(logger, jwt, userHandler)
+	return engine, func() {
+	}, nil
+}
+
+// wire.go:
+
+var ServerSet = wire.NewSet(server.NewServerHTTP)
+
+var SidSet = wire.NewSet(sid.NewSid)
+
+var JwtSet = wire.NewSet(middleware.NewJwt)
+
+var HandlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler)
+
+var ServiceSet = wire.NewSet(service.NewService, service.NewUserService)
+
+var RepositorySet = wire.NewSet(repository.NewDB, repository.NewRedis, repository.NewRepository, repository.NewUserRepository)

+ 514 - 0
coverage.html

@@ -0,0 +1,514 @@
+
+<!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 &amp;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 &amp;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(&amp;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, &amp;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(&amp;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, &amp;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 &amp;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")), &amp;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(&amp;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 &amp;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(&amp;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 &amp;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(&amp;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 &amp;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 &amp;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 &amp;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 &amp;&amp; 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 := &amp;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>

+ 3 - 0
go.mod

@@ -3,9 +3,12 @@ module github.com/go-nunu/nunu-layout-advanced
 go 1.16
 
 require (
+	github.com/DATA-DOG/go-sqlmock v1.5.0
 	github.com/gin-gonic/gin v1.9.1
 	github.com/go-co-op/gocron v1.28.2
+	github.com/go-redis/redismock/v9 v9.0.3 // indirect
 	github.com/golang-jwt/jwt/v5 v5.0.0
+	github.com/golang/mock v1.6.0 // indirect
 	github.com/google/wire v0.5.0
 	github.com/pkg/errors v0.9.1
 	github.com/redis/go-redis/v9 v9.0.5

+ 54 - 0
go.sum

@@ -596,6 +596,8 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum
 git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
+github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -695,6 +697,8 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/
 github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
 github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
@@ -722,6 +726,7 @@ github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpx
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
 github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -732,9 +737,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
 github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-redis/redismock/v9 v9.0.3 h1:mtHQi2l51lCmXIbTRTqb1EiHYe9tL5Yk5oorlSJJqR0=
+github.com/go-redis/redismock/v9 v9.0.3/go.mod h1:F6tJRfnU8R/NZ0E+Gjvoluk14MqMC5ueSZX6vVQypc0=
 github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -759,6 +767,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -817,6 +826,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
@@ -881,6 +891,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
 github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
 github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -967,6 +978,29 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
+github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
+github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
+github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
+github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
+github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
+github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
+github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
+github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
+github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
+github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
+github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
@@ -1005,6 +1039,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
 github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
 github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -1180,12 +1215,15 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1209,6 +1247,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@@ -1223,6 +1262,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1242,6 +1282,7 @@ golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfS
 golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -1298,6 +1339,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1309,11 +1351,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1338,6 +1383,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1371,8 +1417,10 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1478,6 +1526,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -1488,8 +1537,11 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
 golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
+golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1773,10 +1825,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
 gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 0 - 56
internal/dao/user.go

@@ -1,56 +0,0 @@
-package dao
-
-import (
-	"github.com/go-nunu/nunu-layout-advanced/internal/model"
-	"github.com/pkg/errors"
-	"gorm.io/gorm"
-)
-
-type UserDao struct {
-	*Dao
-}
-
-func NewUserDao(dao *Dao) *UserDao {
-	return &UserDao{
-		dao,
-	}
-}
-func (d *UserDao) CreateUser(user *model.User) error {
-	if err := d.db.Create(user).Error; err != nil {
-		return errors.Wrap(err, "failed to create user")
-	}
-
-	return nil
-}
-
-func (d *UserDao) GetUserById(userId string) (*model.User, error) {
-	var user model.User
-	if err := d.db.Where("user_id = ?", userId).First(&user).Error; err != nil {
-		if err == gorm.ErrRecordNotFound {
-			return nil, nil
-		}
-		return nil, errors.Wrap(err, "failed to get user by ID")
-	}
-
-	return &user, nil
-}
-
-func (d *UserDao) GetUserByUsername(username string) (*model.User, error) {
-	var user model.User
-	if err := d.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
-}
-
-func (d *UserDao) UpdateUser(user *model.User) error {
-	if err := d.db.Save(user).Error; err != nil {
-		return errors.Wrap(err, "failed to update user")
-	}
-
-	return nil
-}

+ 1 - 2
internal/handler/handler.go

@@ -4,14 +4,13 @@ import (
 	"github.com/gin-gonic/gin"
 	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
-	"github.com/sony/sonyflake"
 )
 
 type Handler struct {
 	logger *log.Logger
 }
 
-func NewHandler(logger *log.Logger, sf *sonyflake.Sonyflake) *Handler {
+func NewHandler(logger *log.Logger) *Handler {
 	return &Handler{
 		logger: logger,
 	}

+ 19 - 12
internal/handler/user.go

@@ -8,26 +8,33 @@ import (
 	"net/http"
 )
 
-type UserHandler struct {
+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
+	userService service.UserService
 }
 
-func NewUserHandler(handler *Handler, userService *service.UserService) *UserHandler {
-	return &UserHandler{
+func NewUserHandler(handler *Handler, userService service.UserService) UserHandler {
+	return &userHandler{
 		Handler:     handler,
 		userService: userService,
 	}
 }
 
-func (h *UserHandler) Register(ctx *gin.Context) {
+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(req); err != nil {
+	if err := h.userService.Register(ctx, req); err != nil {
 		resp.HandleError(ctx, http.StatusBadRequest, 1, errors.Wrap(err, "invalid request").Error(), nil)
 		return
 	}
@@ -35,14 +42,14 @@ func (h *UserHandler) Register(ctx *gin.Context) {
 	resp.HandleSuccess(ctx, nil)
 }
 
-func (h *UserHandler) Login(ctx *gin.Context) {
+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(&req)
+	token, err := h.userService.Login(ctx, &req)
 	if err != nil {
 		resp.HandleError(ctx, http.StatusUnauthorized, 1, err.Error(), nil)
 		return
@@ -53,14 +60,14 @@ func (h *UserHandler) Login(ctx *gin.Context) {
 	})
 }
 
-func (h *UserHandler) GetProfile(ctx *gin.Context) {
+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(userId)
+	user, err := h.userService.GetProfile(ctx, userId)
 	if err != nil {
 		resp.HandleError(ctx, http.StatusBadRequest, 1, err.Error(), nil)
 		return
@@ -69,7 +76,7 @@ func (h *UserHandler) GetProfile(ctx *gin.Context) {
 	resp.HandleSuccess(ctx, user)
 }
 
-func (h *UserHandler) UpdateProfile(ctx *gin.Context) {
+func (h *userHandler) UpdateProfile(ctx *gin.Context) {
 	userId := GetUserIdFromCtx(ctx)
 
 	var req service.UpdateProfileRequest
@@ -78,7 +85,7 @@ func (h *UserHandler) UpdateProfile(ctx *gin.Context) {
 		return
 	}
 
-	if err := h.userService.UpdateProfile(userId, &req); err != nil {
+	if err := h.userService.UpdateProfile(ctx, userId, &req); err != nil {
 		resp.HandleError(ctx, http.StatusBadRequest, 1, err.Error(), nil)
 		return
 	}

+ 4 - 3
internal/middleware/cors.go

@@ -8,12 +8,13 @@ import (
 func CORSMiddleware() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		method := c.Request.Method
-		c.Header("Access-Control-Allow-Origin", "*")
-		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
-		c.Header("Access-Control-Allow-Headers", "sec-fetch-dest,sec-fetch-mode,sec-fetch-site,Access-Control-Allow-Origin,X-Mode,Authorization, Content-Length, X-CSRF-Token, Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT,  Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,timestamp,nonce,sign,x-vs-language,Debug,App-Version,language,X-Forwarded-For,X-Real-IP")
+		c.Header("Access-Control-Allow-Origin", c.GetHeader("Origin"))
 		c.Header("Access-Control-Allow-Credentials", "true")
 
 		if method == "OPTIONS" {
+			c.Header("Access-Control-Allow-Methods", c.GetHeader("Access-Control-Request-Method"))
+			c.Header("Access-Control-Allow-Headers", c.GetHeader("Access-Control-Request-Headers"))
+			c.Header("Access-Control-Max-Age", "7200")
 			c.AbortWithStatus(http.StatusNoContent)
 			return
 		}

+ 11 - 14
internal/middleware/jwt.go

@@ -1,30 +1,32 @@
 package middleware
 
 import (
+	"net/http"
+	"regexp"
+	"time"
+
 	"github.com/gin-gonic/gin"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/resp"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
 	"github.com/golang-jwt/jwt/v5"
 	"github.com/spf13/viper"
 	"go.uber.org/zap"
-	"net/http"
-	"regexp"
-	"time"
 )
 
 type JWT struct {
 	key []byte
 }
+
 type MyCustomClaims struct {
 	UserId string
 	jwt.RegisteredClaims
 }
 
-// NewJwt https://pkg.go.dev/github.com/golang-jwt/jwt/v5
 func NewJwt(conf *viper.Viper) *JWT {
 	return &JWT{key: []byte(conf.GetString("security.jwt.key"))}
 }
-func (j *JWT) GenToken(userId string, expiresAt time.Time) string {
+
+func (j *JWT) GenToken(userId string, expiresAt time.Time) (string, error) {
 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, MyCustomClaims{
 		UserId: userId,
 		RegisteredClaims: jwt.RegisteredClaims{
@@ -41,13 +43,13 @@ func (j *JWT) GenToken(userId string, expiresAt time.Time) string {
 	// Sign and get the complete encoded token as a string using the key
 	tokenString, err := token.SignedString(j.key)
 	if err != nil {
-		return ""
+		return "", err
 	}
-	return tokenString
-
+	return tokenString, nil
 }
+
 func (j *JWT) ParseToken(tokenString string) (*MyCustomClaims, error) {
-	re, _ := regexp.Compile(`(?i)Bearer `)
+	re := regexp.MustCompile(`(?i)Bearer `)
 	tokenString = re.ReplaceAllString(tokenString, "")
 	token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
 		return j.key, nil
@@ -60,7 +62,6 @@ func (j *JWT) ParseToken(tokenString string) (*MyCustomClaims, error) {
 	}
 }
 
-// StrictAuth 严格权限
 func StrictAuth(j *JWT, logger *log.Logger) gin.HandlerFunc {
 	return func(ctx *gin.Context) {
 		tokenString := ctx.Request.Header.Get("Authorization")
@@ -74,7 +75,6 @@ func StrictAuth(j *JWT, logger *log.Logger) gin.HandlerFunc {
 			return
 		}
 
-		// parseToken 解析token包含的信息
 		claims, err := j.ParseToken(tokenString)
 		if err != nil {
 			logger.WithContext(ctx).Error("token error", zap.Any("data", map[string]interface{}{
@@ -86,7 +86,6 @@ func StrictAuth(j *JWT, logger *log.Logger) gin.HandlerFunc {
 			return
 		}
 
-		// 继续交由下一个路由处理,并将解析出的信息传递下去
 		ctx.Set("claims", claims)
 		recoveryLoggerFunc(ctx, logger)
 		ctx.Next()
@@ -107,14 +106,12 @@ func NoStrictAuth(j *JWT, logger *log.Logger) gin.HandlerFunc {
 			return
 		}
 
-		// parseToken 解析token包含的信息
 		claims, err := j.ParseToken(tokenString)
 		if err != nil {
 			ctx.Next()
 			return
 		}
 
-		// 继续交由下一个路由处理,并将解析出的信息传递下去
 		ctx.Set("claims", claims)
 		recoveryLoggerFunc(ctx, logger)
 		ctx.Next()

+ 18 - 31
internal/middleware/sign.go

@@ -13,50 +13,37 @@ import (
 
 func SignMiddleware(logger *log.Logger, conf *viper.Viper) gin.HandlerFunc {
 	return func(ctx *gin.Context) {
-		timestamp, ok := ctx.Request.Header["Timestamp"]
-		if !ok || len(timestamp) == 0 {
-			resp.HandleError(ctx, http.StatusBadRequest, 1, "sign error.", nil)
-			ctx.Abort()
-			return
-		}
-		nonce, ok := ctx.Request.Header["Nonce"]
-		if !ok || len(nonce) == 0 {
-			resp.HandleError(ctx, http.StatusBadRequest, 1, "sign error.", nil)
-			ctx.Abort()
-			return
-		}
-		sign, ok := ctx.Request.Header["Sign"]
-		if !ok || len(sign) == 0 {
-			resp.HandleError(ctx, http.StatusBadRequest, 1, "sign error.", nil)
-			ctx.Abort()
-			return
-		}
-		appVersion, ok := ctx.Request.Header["App-Version"]
-		if !ok || len(appVersion) == 0 {
-			resp.HandleError(ctx, http.StatusBadRequest, 1, "sign error.", nil)
-			ctx.Abort()
-			return
+		requiredHeaders := []string{"Timestamp", "Nonce", "Sign", "App-Version"}
+
+		for _, header := range requiredHeaders {
+			value, ok := ctx.Request.Header[header]
+			if !ok || len(value) == 0 {
+				resp.HandleError(ctx, http.StatusBadRequest, 1, "sign error.", nil)
+				ctx.Abort()
+				return
+			}
 		}
 
-		data := map[string]string{}
-		data["AppKey"] = conf.GetString("security.api_sign.app_key")
-		data["Timestamp"] = timestamp[0]
-		data["Nonce"] = nonce[0]
-		data["AppVersion"] = appVersion[0]
+		data := map[string]string{
+			"AppKey":     conf.GetString("security.api_sign.app_key"),
+			"Timestamp":  ctx.Request.Header.Get("Timestamp"),
+			"Nonce":      ctx.Request.Header.Get("Nonce"),
+			"AppVersion": ctx.Request.Header.Get("App-Version"),
+		}
 
 		var keys []string
 		for k := range data {
 			keys = append(keys, k)
 		}
 		sort.Slice(keys, func(i, j int) bool { return strings.ToLower(keys[i]) < strings.ToLower(keys[j]) })
-		//拼接
-		str := ""
+
+		var str string
 		for _, k := range keys {
 			str += k + data[k]
 		}
 		str += conf.GetString("security.api_sign.app_security")
 
-		if sign[0] != strings.ToUpper(md5.Md5(str)) {
+		if ctx.Request.Header.Get("Sign") != strings.ToUpper(md5.Md5(str)) {
 			resp.HandleError(ctx, http.StatusBadRequest, 1, "sign error.", nil)
 			ctx.Abort()
 			return

+ 0 - 40
internal/provider/provider.go

@@ -1,40 +0,0 @@
-package provider
-
-import (
-	"github.com/go-nunu/nunu-layout-advanced/internal/dao"
-	"github.com/go-nunu/nunu-layout-advanced/internal/handler"
-	"github.com/go-nunu/nunu-layout-advanced/internal/job"
-	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
-	"github.com/go-nunu/nunu-layout-advanced/internal/migration"
-	"github.com/go-nunu/nunu-layout-advanced/internal/server"
-	"github.com/go-nunu/nunu-layout-advanced/internal/service"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sonyflake"
-	"github.com/google/wire"
-)
-
-var ServerSet = wire.NewSet(server.NewServerHTTP)
-
-var SonyflakeSet = wire.NewSet(sonyflake.NewSonyflake)
-
-var MigrateSet = wire.NewSet(migration.NewMigrate)
-
-var JobSet = wire.NewSet(job.NewJob)
-
-var JwtSet = wire.NewSet(middleware.NewJwt)
-
-var DaoSet = wire.NewSet(
-	dao.NewDB,
-	dao.NewRedis,
-	dao.NewDao,
-	dao.NewUserDao,
-)
-
-var ServiceSet = wire.NewSet(
-	service.NewService,
-	service.NewUserService,
-)
-
-var HandlerSet = wire.NewSet(
-	handler.NewHandler,
-	handler.NewUserHandler,
-)

+ 4 - 4
internal/dao/dao.go → internal/repository/repository.go

@@ -1,4 +1,4 @@
-package dao
+package repository
 
 import (
 	"context"
@@ -11,14 +11,14 @@ import (
 	"time"
 )
 
-type Dao struct {
+type Repository struct {
 	db     *gorm.DB
 	rdb    *redis.Client
 	logger *log.Logger
 }
 
-func NewDao(db *gorm.DB, rdb *redis.Client, logger *log.Logger) *Dao {
-	return &Dao{
+func NewRepository(db *gorm.DB, rdb *redis.Client, logger *log.Logger) *Repository {
+	return &Repository{
 		db:     db,
 		rdb:    rdb,
 		logger: logger,

+ 61 - 0
internal/repository/user.go

@@ -0,0 +1,61 @@
+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
+}

+ 3 - 3
internal/server/http.go

@@ -11,7 +11,7 @@ import (
 func NewServerHTTP(
 	logger *log.Logger,
 	jwt *middleware.JWT,
-	userHandler *handler.UserHandler,
+	userHandler handler.UserHandler,
 ) *gin.Engine {
 	gin.SetMode(gin.ReleaseMode)
 	r := gin.Default()
@@ -33,8 +33,8 @@ func NewServerHTTP(
 			})
 		})
 
-		noAuthRouter.POST("/user/register", userHandler.Register)
-		noAuthRouter.POST("/user/login", userHandler.Login)
+		noAuthRouter.POST("/register", userHandler.Register)
+		noAuthRouter.POST("/login", userHandler.Login)
 	}
 	// 非严格权限路由
 	noStrictAuthRouter := r.Group("/").Use(middleware.NoStrictAuth(jwt, logger), middleware.RequestLogMiddleware(logger))

+ 8 - 8
internal/service/service.go

@@ -2,20 +2,20 @@ 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"
-	"github.com/sony/sonyflake"
 )
 
 type Service struct {
-	logger    *log.Logger
-	sonyflake *sonyflake.Sonyflake
-	jwt       *middleware.JWT
+	logger *log.Logger
+	sid    *sid.Sid
+	jwt    *middleware.JWT
 }
 
-func NewService(logger *log.Logger, sonyflake *sonyflake.Sonyflake, jwt *middleware.JWT) *Service {
+func NewService(logger *log.Logger, sid *sid.Sid, jwt *middleware.JWT) *Service {
 	return &Service{
-		logger:    logger,
-		sonyflake: sonyflake,
-		jwt:       jwt,
+		logger: logger,
+		sid:    sid,
+		jwt:    jwt,
 	}
 }

+ 33 - 38
internal/service/user.go

@@ -1,9 +1,9 @@
 package service
 
 import (
-	"github.com/go-nunu/nunu-layout-advanced/internal/dao"
+	"context"
 	"github.com/go-nunu/nunu-layout-advanced/internal/model"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/convert"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
 	"github.com/golang-jwt/jwt/v5"
 	"github.com/pkg/errors"
 	"golang.org/x/crypto/bcrypt"
@@ -32,27 +32,29 @@ type ChangePasswordRequest struct {
 	NewPassword string `json:"newPassword" binding:"required"`
 }
 
-type UserService struct {
-	userDao *dao.UserDao
-	*Service
+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)
 }
 
-func NewUserService(service *Service, userDao *dao.UserDao) *UserService {
-	return &UserService{
-		userDao: userDao,
-		Service: service,
-	}
+type userService struct {
+	userRepo repository.UserRepository
+	*Service
 }
 
-func (s *UserService) Register(req *RegisterRequest) error {
-	// 生成用户ID
-	userId, err := s.generateUserId()
-	if err != nil {
-		return errors.Wrap(err, "failed to generate user ID")
+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.userDao.GetUserByUsername(req.Username); err == nil && user != nil {
+	if user, err := s.userRepo.GetByUsername(ctx, req.Username); err == nil && user != nil {
 		return errors.New("username already exists")
 	}
 
@@ -60,7 +62,11 @@ func (s *UserService) Register(req *RegisterRequest) error {
 	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,
@@ -68,15 +74,15 @@ func (s *UserService) Register(req *RegisterRequest) error {
 		Password: string(hashedPassword),
 		Email:    req.Email,
 	}
-	if err = s.userDao.CreateUser(user); err != nil {
+	if err = s.userRepo.Create(ctx, user); err != nil {
 		return errors.Wrap(err, "failed to create user")
 	}
 
 	return nil
 }
 
-func (s *UserService) Login(req *LoginRequest) (string, error) {
-	user, err := s.userDao.GetUserByUsername(req.Username)
+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")
 	}
@@ -86,7 +92,7 @@ func (s *UserService) Login(req *LoginRequest) (string, error) {
 		return "", errors.Wrap(err, "failed to hash password")
 	}
 	// 生成JWT token
-	token, err := s.generateToken(user.UserId)
+	token, err := s.GenerateToken(ctx, user.UserId)
 	if err != nil {
 		return "", errors.Wrap(err, "failed to generate JWT token")
 	}
@@ -94,8 +100,8 @@ func (s *UserService) Login(req *LoginRequest) (string, error) {
 	return token, nil
 }
 
-func (s *UserService) GetProfile(userId string) (*model.User, error) {
-	user, err := s.userDao.GetUserById(userId)
+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")
 	}
@@ -103,8 +109,8 @@ func (s *UserService) GetProfile(userId string) (*model.User, error) {
 	return user, nil
 }
 
-func (s *UserService) UpdateProfile(userId string, req *UpdateProfileRequest) error {
-	user, err := s.userDao.GetUserById(userId)
+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")
 	}
@@ -112,25 +118,14 @@ func (s *UserService) UpdateProfile(userId string, req *UpdateProfileRequest) er
 	user.Email = req.Email
 	user.Nickname = req.Nickname
 
-	if err = s.userDao.UpdateUser(user); err != nil {
+	if err = s.userRepo.Update(ctx, user); err != nil {
 		return errors.Wrap(err, "failed to update user")
 	}
 
 	return nil
 }
 
-func (s *UserService) generateUserId() (string, error) {
-	// 生成分布式ID
-	id, err := s.sonyflake.NextID()
-	if err != nil {
-		return "", errors.Wrap(err, "failed to generate sonyflake ID")
-	}
-
-	// 将ID转换为字符串
-	return convert.IntToBase62(int(id)), nil
-}
-
-func (s *UserService) generateToken(userId string) (string, error) {
+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{

+ 94 - 0
mocks/repository/user.go

@@ -0,0 +1,94 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: internal/repository/user.go
+
+// Package mock_repository is a generated GoMock package.
+package mock_repository
+
+import (
+	context "context"
+	reflect "reflect"
+
+	model "github.com/go-nunu/nunu-layout-advanced/internal/model"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockUserRepository is a mock of UserRepository interface.
+type MockUserRepository struct {
+	ctrl     *gomock.Controller
+	recorder *MockUserRepositoryMockRecorder
+}
+
+// MockUserRepositoryMockRecorder is the mock recorder for MockUserRepository.
+type MockUserRepositoryMockRecorder struct {
+	mock *MockUserRepository
+}
+
+// NewMockUserRepository creates a new mock instance.
+func NewMockUserRepository(ctrl *gomock.Controller) *MockUserRepository {
+	mock := &MockUserRepository{ctrl: ctrl}
+	mock.recorder = &MockUserRepositoryMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockUserRepository) EXPECT() *MockUserRepositoryMockRecorder {
+	return m.recorder
+}
+
+// Create mocks base method.
+func (m *MockUserRepository) Create(ctx context.Context, user *model.User) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Create", ctx, user)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Create indicates an expected call of Create.
+func (mr *MockUserRepositoryMockRecorder) Create(ctx, user interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUserRepository)(nil).Create), ctx, user)
+}
+
+// GetByID mocks base method.
+func (m *MockUserRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetByID", ctx, id)
+	ret0, _ := ret[0].(*model.User)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetByID indicates an expected call of GetByID.
+func (mr *MockUserRepositoryMockRecorder) GetByID(ctx, id interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockUserRepository)(nil).GetByID), ctx, id)
+}
+
+// GetByUsername mocks base method.
+func (m *MockUserRepository) GetByUsername(ctx context.Context, username string) (*model.User, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetByUsername", ctx, username)
+	ret0, _ := ret[0].(*model.User)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetByUsername indicates an expected call of GetByUsername.
+func (mr *MockUserRepositoryMockRecorder) GetByUsername(ctx, username interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByUsername", reflect.TypeOf((*MockUserRepository)(nil).GetByUsername), ctx, username)
+}
+
+// Update mocks base method.
+func (m *MockUserRepository) Update(ctx context.Context, user *model.User) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Update", ctx, user)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Update indicates an expected call of Update.
+func (mr *MockUserRepositoryMockRecorder) Update(ctx, user interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserRepository)(nil).Update), ctx, user)
+}

+ 110 - 0
mocks/service/user.go

@@ -0,0 +1,110 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: internal/service/user.go
+
+// Package mock_service is a generated GoMock package.
+package mock_service
+
+import (
+	context "context"
+	reflect "reflect"
+
+	model "github.com/go-nunu/nunu-layout-advanced/internal/model"
+	service "github.com/go-nunu/nunu-layout-advanced/internal/service"
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockUserService is a mock of UserService interface.
+type MockUserService struct {
+	ctrl     *gomock.Controller
+	recorder *MockUserServiceMockRecorder
+}
+
+// MockUserServiceMockRecorder is the mock recorder for MockUserService.
+type MockUserServiceMockRecorder struct {
+	mock *MockUserService
+}
+
+// NewMockUserService creates a new mock instance.
+func NewMockUserService(ctrl *gomock.Controller) *MockUserService {
+	mock := &MockUserService{ctrl: ctrl}
+	mock.recorder = &MockUserServiceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockUserService) EXPECT() *MockUserServiceMockRecorder {
+	return m.recorder
+}
+
+// GenerateToken mocks base method.
+func (m *MockUserService) GenerateToken(ctx context.Context, userId string) (string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GenerateToken", ctx, userId)
+	ret0, _ := ret[0].(string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GenerateToken indicates an expected call of GenerateToken.
+func (mr *MockUserServiceMockRecorder) GenerateToken(ctx, userId interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateToken", reflect.TypeOf((*MockUserService)(nil).GenerateToken), ctx, userId)
+}
+
+// GetProfile mocks base method.
+func (m *MockUserService) GetProfile(ctx context.Context, userId string) (*model.User, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetProfile", ctx, userId)
+	ret0, _ := ret[0].(*model.User)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetProfile indicates an expected call of GetProfile.
+func (mr *MockUserServiceMockRecorder) GetProfile(ctx, userId interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProfile", reflect.TypeOf((*MockUserService)(nil).GetProfile), ctx, userId)
+}
+
+// Login mocks base method.
+func (m *MockUserService) Login(ctx context.Context, req *service.LoginRequest) (string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Login", ctx, req)
+	ret0, _ := ret[0].(string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Login indicates an expected call of Login.
+func (mr *MockUserServiceMockRecorder) Login(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockUserService)(nil).Login), ctx, req)
+}
+
+// Register mocks base method.
+func (m *MockUserService) Register(ctx context.Context, req *service.RegisterRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Register", ctx, req)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Register indicates an expected call of Register.
+func (mr *MockUserServiceMockRecorder) Register(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockUserService)(nil).Register), ctx, req)
+}
+
+// UpdateProfile mocks base method.
+func (m *MockUserService) UpdateProfile(ctx context.Context, userId string, req *service.UpdateProfileRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "UpdateProfile", ctx, userId, req)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// UpdateProfile indicates an expected call of UpdateProfile.
+func (mr *MockUserServiceMockRecorder) UpdateProfile(ctx, userId, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProfile", reflect.TypeOf((*MockUserService)(nil).UpdateProfile), ctx, userId, req)
+}

+ 32 - 0
pkg/helper/sid/sid.go

@@ -0,0 +1,32 @@
+package sid
+
+import (
+	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/convert"
+	"github.com/pkg/errors"
+	"github.com/sony/sonyflake"
+)
+
+type Sid struct {
+	sf *sonyflake.Sonyflake
+}
+
+func NewSid() *Sid {
+	sf := sonyflake.NewSonyflake(sonyflake.Settings{})
+	if sf == nil {
+		panic("sonyflake not created")
+	}
+	return &Sid{sf}
+}
+func (s Sid) GenString() (string, error) {
+	// 生成分布式ID
+	id, err := s.sf.NextID()
+	if err != nil {
+		return "", errors.Wrap(err, "failed to generate sonyflake ID")
+	}
+	// 将ID转换为字符串
+	return convert.IntToBase62(int(id)), nil
+}
+func (s Sid) GenUint64() (uint64, error) {
+	// 生成分布式ID
+	return s.sf.NextID()
+}

+ 0 - 18
pkg/helper/sonyflake/sonyflake.go

@@ -1,18 +0,0 @@
-package sonyflake
-
-import (
-	"github.com/sony/sonyflake"
-	"time"
-)
-
-func NewSonyflake() *sonyflake.Sonyflake {
-	sf := sonyflake.NewSonyflake(sonyflake.Settings{
-		StartTime:      time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC),
-		MachineID:      nil,
-		CheckMachineID: nil,
-	})
-	if sf == nil {
-		panic("sonyflake not created")
-	}
-	return sf
-}

+ 0 - 0
script/deploy.sh → scripts/deploy.sh


+ 125 - 65
test/server/handler/user_test.go

@@ -4,94 +4,154 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"github.com/go-nunu/nunu-layout-advanced/cmd/server/wire"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/config"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
-	"github.com/stretchr/testify/assert"
-	"io"
+	"github.com/go-nunu/nunu-layout-advanced/internal/handler"
+	"github.com/go-nunu/nunu-layout-advanced/mocks/service"
+
+	"github.com/go-nunu/nunu-layout-advanced/internal/server"
 	"net/http"
 	"net/http/httptest"
 	"os"
-	"strings"
 	"testing"
+
+	"github.com/gin-gonic/gin"
+	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
+	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+	"github.com/go-nunu/nunu-layout-advanced/internal/service"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/config"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
 )
 
-var headers = map[string]string{
-	"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJ5aHM2SGVzZmdGIiwiZXhwIjoxNjkzOTE0ODgwLCJuYmYiOjE2ODYxMzg4ODAsImlhdCI6MTY4NjEzODg4MH0.NnFrZFgc_333a9PXqaoongmIDksNvQoHzgM_IhJM4MQ",
-}
+var (
+	userId = "yhs6HesfgF"
+
+	token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJ5aHM2SGVzZmdGIiwiZXhwIjoxNjkzOTE0ODgwLCJuYmYiOjE2ODYxMzg4ODAsImlhdCI6MTY4NjEzODg4MH0.NnFrZFgc_333a9PXqaoongmIDksNvQoHzgM_IhJM4MQ"
+)
+var hdl *handler.Handler
 
 func TestMain(m *testing.M) {
 	fmt.Println("begin")
+	os.Setenv("APP_CONF", "../../../config/local.yml")
+	conf := config.NewConfig()
+
+	logger := log.NewLog(conf)
+	hdl = handler.NewHandler(logger)
 
 	code := m.Run()
 	fmt.Println("test end")
 
 	os.Exit(code)
-
 }
 
-type Response struct {
-	Code    int         `json:"code"`
-	Message string      `json:"message"`
-	Data    interface{} `json:"data"`
+func TestUserHandler_Register(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	params := service.RegisterRequest{
+		Username: "xxx",
+		Password: "123456",
+		Email:    "xxx@gmail.com",
+	}
+
+	mockUserService := mock_service.NewMockUserService(ctrl)
+	mockUserService.EXPECT().Register(gomock.Any(), &params).Return(nil)
+
+	router := setupRouter(mockUserService)
+	paramsJson, _ := json.Marshal(params)
+
+	resp := performRequest(router, "POST", "/register", bytes.NewBuffer(paramsJson))
+
+	assert.Equal(t, resp.Code, http.StatusOK)
+	// Add assertions for the response body if needed
 }
 
-func NewRequest(method, path string, header map[string]string, body io.Reader) (*Response, error) {
-	// 测试时需要定义好 gin 的路由定义函数
-	os.Setenv("APP_CONF", "../../../config/local.yml")
-	conf := config.NewConfig()
-	logger := log.NewLog(conf)
-	logger.Info("start")
+func TestUserHandler_Login(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
 
-	app, _, err := wire.NewApp(conf, logger)
-	if err != nil {
-		return nil, err
+	params := service.LoginRequest{
+		Username: "xxx",
+		Password: "123456",
 	}
-	req, _ := http.NewRequest(method, path, body)
-	for k, v := range header {
-		req.Header.Set(k, v)
-	}
-	if strings.ToUpper(method) != "GET" && body != nil {
-		req.Header.Set("Content-Type", "application/json")
 
-	}
-	w := httptest.NewRecorder()
-	app.ServeHTTP(w, req)
-	response := new(Response)
-	err = json.Unmarshal([]byte(w.Body.String()), response)
-	if err != nil {
-		return nil, err
-	}
-	return response, nil
+	mockUserService := mock_service.NewMockUserService(ctrl)
+	mockUserService.EXPECT().Login(gomock.Any(), &params).Return(token, nil)
+
+	router := setupRouter(mockUserService)
+	paramsJson, _ := json.Marshal(params)
+
+	resp := performRequest(router, "POST", "/login", bytes.NewBuffer(paramsJson))
+
+	assert.Equal(t, resp.Code, http.StatusOK)
+	// Add assertions for the response body if needed
 }
 
-func TestGetProfile(t *testing.T) {
-	response, err := NewRequest("GET",
-		fmt.Sprintf("/user"),
-		headers,
-		nil,
-	)
+func TestUserHandler_GetProfile(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
 
-	t.Log("response")
-	assert.Nil(t, err)
-	assert.Equal(t, 1, response.Code)
+	mockUserService := mock_service.NewMockUserService(ctrl)
+	mockUserService.EXPECT().GetProfile(gomock.Any(), userId).Return(&model.User{
+		Id:       1,
+		UserId:   userId,
+		Username: "xxxxx",
+		Nickname: "xxxxx",
+		Password: "xxxxx",
+		Email:    "xxxxx@gmail.com",
+	}, nil)
+
+	router := setupRouter(mockUserService)
+	req, _ := http.NewRequest("GET", "/user", nil)
+	req.Header.Set("Authorization", "Bearer "+token)
+	resp := httptest.NewRecorder()
+
+	router.ServeHTTP(resp, req)
+
+	assert.Equal(t, resp.Code, http.StatusOK)
+	// Add assertions for the response body if needed
 }
-func TestUpdateProfile(t *testing.T) {
-	params, err := json.Marshal(map[string]interface{}{
-		"email":    "5303221@gmail.com",
-		"username": "user1",
-		"nickname": "8888",
-	})
-	assert.Nil(t, err)
-	response, err := NewRequest("PUT",
-		"/user",
-		headers,
-		bytes.NewBuffer(params),
-	)
-
-	t.Log("响应结果")
-	assert.Nil(t, err)
-	//assert.NotEmpty(t, response.Data)
-	assert.Equal(t, 0, response.Code)
-	//tsms.SendSMS2("MotokApp", "18502100065", "1234")
+
+func TestUserHandler_UpdateProfile(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	params := service.UpdateProfileRequest{
+		Nickname: "alan",
+		Email:    "alan@gmail.com",
+		Avatar:   "xxx",
+	}
+
+	mockUserService := mock_service.NewMockUserService(ctrl)
+	mockUserService.EXPECT().UpdateProfile(gomock.Any(), userId, &params).Return(nil)
+
+	router := setupRouter(mockUserService)
+	paramsJson, _ := json.Marshal(params)
+
+	req, _ := http.NewRequest("PUT", "/user", bytes.NewBuffer(paramsJson))
+	req.Header.Set("Authorization", "Bearer "+token)
+	req.Header.Set("Content-Type", "application/json")
+	resp := httptest.NewRecorder()
+
+	router.ServeHTTP(resp, req)
+
+	assert.Equal(t, resp.Code, http.StatusOK)
+	// Add assertions for the response body if needed
+}
+
+func setupRouter(mockUserService *mock_service.MockUserService) *gin.Engine {
+	conf := config.NewConfig()
+	logger := log.NewLog(conf)
+	jwt := middleware.NewJwt(conf)
+	userHandler := handler.NewUserHandler(hdl, mockUserService)
+	gin.SetMode(gin.TestMode)
+	router := server.NewServerHTTP(logger, jwt, userHandler)
+	return router
+}
+
+func performRequest(r http.Handler, method, path string, body *bytes.Buffer) *httptest.ResponseRecorder {
+	req, _ := http.NewRequest(method, path, body)
+	resp := httptest.NewRecorder()
+	r.ServeHTTP(resp, req)
+	return resp
 }

+ 125 - 0
test/server/repository/user_test.go

@@ -0,0 +1,125 @@
+package repository
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/DATA-DOG/go-sqlmock"
+	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"github.com/go-redis/redismock/v9"
+	"github.com/stretchr/testify/assert"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+)
+
+func setupRepository(t *testing.T) (repository.UserRepository, sqlmock.Sqlmock) {
+	mockDB, mock, err := sqlmock.New()
+	if err != nil {
+		t.Fatalf("failed to create sqlmock: %v", err)
+	}
+
+	db, err := gorm.Open(mysql.New(mysql.Config{
+		Conn:                      mockDB,
+		SkipInitializeWithVersion: true,
+	}), &gorm.Config{})
+	if err != nil {
+		t.Fatalf("failed to open gorm connection: %v", err)
+	}
+
+	rdb, _ := redismock.NewClientMock()
+
+	repo := repository.NewRepository(db, rdb, nil)
+	userRepo := repository.NewUserRepository(repo)
+
+	return userRepo, mock
+}
+
+func TestUserRepository_Create(t *testing.T) {
+	userRepo, mock := setupRepository(t)
+
+	ctx := context.Background()
+	user := &model.User{
+		Id:        1,
+		UserId:    "123",
+		Username:  "test",
+		Nickname:  "Test",
+		Password:  "password",
+		Email:     "test@example.com",
+		CreatedAt: time.Now(),
+		UpdatedAt: time.Now(),
+	}
+
+	mock.ExpectBegin()
+	mock.ExpectExec("INSERT INTO `users`").
+		WithArgs(user.UserId, user.Username, user.Nickname, user.Password, user.Email, user.CreatedAt, user.UpdatedAt, user.DeletedAt, user.Id).
+		WillReturnResult(sqlmock.NewResult(1, 1))
+	mock.ExpectCommit()
+
+	err := userRepo.Create(ctx, user)
+	assert.NoError(t, err)
+
+	assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestUserRepository_Update(t *testing.T) {
+	userRepo, mock := setupRepository(t)
+
+	ctx := context.Background()
+	user := &model.User{
+		Id:        1,
+		UserId:    "123",
+		Username:  "test",
+		Nickname:  "Test",
+		Password:  "password",
+		Email:     "test@example.com",
+		CreatedAt: time.Now(),
+		UpdatedAt: time.Now(),
+	}
+
+	mock.ExpectBegin()
+	mock.ExpectExec("UPDATE `users`").WillReturnResult(sqlmock.NewResult(1, 1))
+	mock.ExpectCommit()
+
+	err := userRepo.Update(ctx, user)
+	assert.NoError(t, err)
+
+	assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestUserRepository_GetById(t *testing.T) {
+	userRepo, mock := setupRepository(t)
+
+	ctx := context.Background()
+	userId := "123"
+
+	rows := sqlmock.NewRows([]string{"id", "user_id", "username", "nickname", "password", "email", "created_at", "updated_at"}).
+		AddRow(1, "123", "test", "Test", "password", "test@example.com", time.Now(), time.Now())
+	mock.ExpectQuery("SELECT \\* FROM `users`").WillReturnRows(rows)
+
+	user, err := userRepo.GetByID(ctx, userId)
+	assert.NoError(t, err)
+	assert.NotNil(t, user)
+	assert.Equal(t, "123", user.UserId)
+
+	assert.NoError(t, mock.ExpectationsWereMet())
+}
+
+func TestUserRepository_GetByUsername(t *testing.T) {
+	userRepo, mock := setupRepository(t)
+
+	ctx := context.Background()
+	username := "test"
+
+	rows := sqlmock.NewRows([]string{"id", "user_id", "username", "nickname", "password", "email", "created_at", "updated_at"}).
+		AddRow(1, "123", "test", "Test", "password", "test@example.com", time.Now(), time.Now())
+	mock.ExpectQuery("SELECT \\* FROM `users`").WillReturnRows(rows)
+
+	user, err := userRepo.GetByUsername(ctx, username)
+	assert.NoError(t, err)
+	assert.NotNil(t, user)
+	assert.Equal(t, "test", user.Username)
+
+	assert.NoError(t, mock.ExpectationsWereMet())
+}

+ 195 - 27
test/server/service/user_test.go

@@ -1,19 +1,27 @@
-package service
+package service_test
 
 import (
+	"context"
+	"errors"
 	"fmt"
-	"github.com/go-nunu/nunu-layout-advanced/internal/dao"
+	"github.com/go-nunu/nunu-layout-advanced/mocks/repository"
+	"os"
+	"testing"
+
 	"github.com/go-nunu/nunu-layout-advanced/internal/middleware"
+	"github.com/go-nunu/nunu-layout-advanced/internal/model"
 	"github.com/go-nunu/nunu-layout-advanced/internal/service"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/config"
-	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sonyflake"
+	"github.com/go-nunu/nunu-layout-advanced/pkg/helper/sid"
 	"github.com/go-nunu/nunu-layout-advanced/pkg/log"
+	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
-	"os"
-	"testing"
+	"golang.org/x/crypto/bcrypt"
 )
 
-var userService *service.UserService
+var (
+	srv *service.Service
+)
 
 func TestMain(m *testing.M) {
 	fmt.Println("begin")
@@ -23,37 +31,197 @@ func TestMain(m *testing.M) {
 	conf := config.NewConfig()
 
 	logger := log.NewLog(conf)
-	db := dao.NewDB(conf)
-	rdb := dao.NewRedis(conf)
 	jwt := middleware.NewJwt(conf)
-	sf := sonyflake.NewSonyflake()
-	srv := service.NewService(logger, sf, jwt)
-	repo := dao.NewDao(db, rdb, logger)
-	userDao := dao.NewUserDao(repo)
-	userService = service.NewUserService(srv, userDao)
+	sf := sid.NewSid()
+	srv = service.NewService(logger, sf, jwt)
 
 	code := m.Run()
 	fmt.Println("test end")
 
 	os.Exit(code)
+}
+
+func TestUserService_Register(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	req := &service.RegisterRequest{
+		Username: "testuser",
+		Password: "password",
+		Email:    "test@example.com",
+	}
+
+	mockUserRepo.EXPECT().GetByUsername(ctx, req.Username).Return(nil, nil)
+	mockUserRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
+
+	err := userService.Register(ctx, req)
+
+	assert.NoError(t, err)
+}
+
+func TestUserService_Register_UsernameExists(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	req := &service.RegisterRequest{
+		Username: "testuser",
+		Password: "password",
+		Email:    "test@example.com",
+	}
+
+	mockUserRepo.EXPECT().GetByUsername(ctx, req.Username).Return(&model.User{}, nil)
+
+	err := userService.Register(ctx, req)
+
+	assert.Error(t, err)
+}
+
+func TestUserService_Login(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	req := &service.LoginRequest{
+		Username: "testuser",
+		Password: "password",
+	}
+	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
+	if err != nil {
+		t.Error("failed to hash password")
+	}
+
+	mockUserRepo.EXPECT().GetByUsername(ctx, req.Username).Return(&model.User{
+		Password: string(hashedPassword),
+	}, nil)
+
+	token, err := userService.Login(ctx, req)
+
+	assert.NoError(t, err)
+	assert.NotEmpty(t, token)
+}
 
+func TestUserService_Login_UserNotFound(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	req := &service.LoginRequest{
+		Username: "testuser",
+		Password: "password",
+	}
+
+	mockUserRepo.EXPECT().GetByUsername(ctx, req.Username).Return(nil, errors.New("user not found"))
+
+	_, err := userService.Login(ctx, req)
+
+	assert.Error(t, err)
+}
+
+func TestUserService_GetProfile(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	userId := "123"
+
+	mockUserRepo.EXPECT().GetByID(ctx, userId).Return(&model.User{
+		UserId:   userId,
+		Username: "testuser",
+		Email:    "test@example.com",
+	}, nil)
+
+	user, err := userService.GetProfile(ctx, userId)
+
+	assert.NoError(t, err)
+	assert.Equal(t, userId, user.UserId)
+	assert.Equal(t, "testuser", user.Username)
+	assert.Equal(t, "test@example.com", user.Email)
 }
-func TestRegister(t *testing.T) {
-	req := service.RegisterRequest{
-		Username: "user1",
-		Password: "123456",
-		Email:    "user1@mail.com",
+
+func TestUserService_UpdateProfile(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	userId := "123"
+	req := &service.UpdateProfileRequest{
+		Nickname: "testuser",
+		Email:    "test@example.com",
 	}
-	err := userService.Register(&req)
-	assert.Equal(t, err, nil, "they should be equal")
+
+	mockUserRepo.EXPECT().GetByID(ctx, userId).Return(&model.User{
+		UserId:   userId,
+		Username: "testuser",
+		Email:    "old@example.com",
+	}, nil)
+	mockUserRepo.EXPECT().Update(ctx, gomock.Any()).Return(nil)
+
+	err := userService.UpdateProfile(ctx, userId, req)
+
+	assert.NoError(t, err)
 }
 
-func TestLogin(t *testing.T) {
-	req := service.LoginRequest{
-		Username: "user1",
-		Password: "123456",
+func TestUserService_UpdateProfile_UserNotFound(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	userId := "123"
+	req := &service.UpdateProfileRequest{
+		Nickname: "testuser",
+		Email:    "test@example.com",
 	}
-	token, err := userService.Login(&req)
-	assert.Equal(t, err, nil, "they should be equal")
-	t.Log("token", token)
+
+	mockUserRepo.EXPECT().GetByID(ctx, userId).Return(nil, errors.New("user not found"))
+
+	err := userService.UpdateProfile(ctx, userId, req)
+
+	assert.Error(t, err)
+}
+
+func TestUserService_GenerateToken(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockUserRepo := mock_repository.NewMockUserRepository(ctrl)
+
+	userService := service.NewUserService(srv, mockUserRepo)
+
+	ctx := context.Background()
+	userId := "123"
+
+	result, err := userService.GenerateToken(ctx, userId)
+
+	assert.NoError(t, err)
+	assert.NotEmpty(t, result)
 }