diff --git a/README.md b/README.md index 25ba345..a9754bb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Potato @@ -10,13 +10,13 @@ Go项目脚手架 -基于gin、gorm、zap的脚手架 +基于gin、gorm、zap、cron的脚手架 本脚手架包含以下内容: 1. mvc结构。 2. swagger接口文档。 -3. 配置、数据库、redis、日志、工具库封装。 +3. 配置、数据库、redis、日志、工具库、后台任务封装。 4. 单点登陆(jwt)。 ## 内容列表 @@ -40,6 +40,7 @@ potato ├── internal(内部模块) │   ├── controller(控制器层,用于存放控制器) │   ├── dao(数据访问层,所有与数据相关等操作都会在dao层进行) +│   ├── job(后台任务) │   ├── middleware(HTTP中间件) │   ├── model(模型层,用于存放model对象) │   ├── routers(路由相关逻辑处理) @@ -82,8 +83,11 @@ jaegertracing/all-in-one:1.16 ## 使用说明 ```sh +# 启动项目 $ go build -o potato main.go $ ./potato +# 生成api文档 +$ swag init ``` ## 相关仓库 @@ -91,6 +95,7 @@ $ ./potato - [Gin](https://github.com/gin-gonic/gin) — Web Framework - [Gorm](https://github.com/jinzhu/gorm) — ORM - [Swag](https://github.com/swaggo/swag) - RESTful API Doc +- [Cron](https://github.com/robfig/cron) - A cron library ## 如何贡献 diff --git a/docs/docs.go b/docs/docs.go index bf133bb..0703723 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -59,6 +59,82 @@ var doc = `{ } } }, + "/user/login": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "summary": "用户登录", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "密码", + "name": "password", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/errcode.Error" + } + } + } + } + }, + "/user/register": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "summary": "用户注册", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "密码", + "name": "password", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/errcode.Error" + } + } + } + } + }, "/v1/upload": { "post": { "consumes": [ @@ -101,6 +177,248 @@ var doc = `{ } } }, + "/v1/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "用户列表", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "page_size", + "in": "query" + }, + { + "maxLength": 30, + "type": "string", + "description": "用户名", + "name": "username", + "in": "query" + }, + { + "maxLength": 30, + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "query" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + }, + "post": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "新增用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "maxLength": 30, + "minLength": 1, + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData", + "required": true + }, + { + "maxLength": 18, + "minLength": 1, + "type": "string", + "description": "密码", + "name": "password", + "in": "formData", + "required": true + }, + { + "maxLength": 30, + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + } + }, + "/v1/users/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "用户 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "删除用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + }, + "patch": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "更新用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "maxLength": 30, + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData" + }, + { + "maxLength": 18, + "type": "string", + "description": "密码", + "name": "password", + "in": "formData" + }, + { + "maxLength": 30, + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + } + }, "/v1/vendors": { "get": { "consumes": [ @@ -328,7 +646,18 @@ var doc = `{ }, "definitions": { "basic.Vendor": { - "type": "object" + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "uuid": { + "type": "integer" + } + } }, "errcode.Error": { "type": "object", @@ -343,6 +672,29 @@ var doc = `{ "type": "string" } } + }, + "model.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "type": "boolean" + }, + "nickname": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } } } }` diff --git a/docs/swagger.json b/docs/swagger.json index 48706b4..7685a78 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -43,6 +43,82 @@ } } }, + "/user/login": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "summary": "用户登录", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "密码", + "name": "password", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/errcode.Error" + } + } + } + } + }, + "/user/register": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "summary": "用户注册", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "密码", + "name": "password", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/errcode.Error" + } + } + } + } + }, "/v1/upload": { "post": { "consumes": [ @@ -85,6 +161,248 @@ } } }, + "/v1/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "用户列表", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "page_size", + "in": "query" + }, + { + "maxLength": 30, + "type": "string", + "description": "用户名", + "name": "username", + "in": "query" + }, + { + "maxLength": 30, + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "query" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + }, + "post": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "新增用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "maxLength": 30, + "minLength": 1, + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData", + "required": true + }, + { + "maxLength": 18, + "minLength": 1, + "type": "string", + "description": "密码", + "name": "password", + "in": "formData", + "required": true + }, + { + "maxLength": 30, + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + } + }, + "/v1/users/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "用户 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "删除用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + }, + "patch": { + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "users" + ], + "summary": "更新用户", + "parameters": [ + { + "type": "string", + "description": "auth token", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "用户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "maxLength": 30, + "type": "string", + "description": "用户名", + "name": "username", + "in": "formData" + }, + { + "maxLength": 18, + "type": "string", + "description": "密码", + "name": "password", + "in": "formData" + }, + { + "maxLength": 30, + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "请求成功", + "schema": { + "$ref": "#/definitions/model.User" + } + } + } + } + }, "/v1/vendors": { "get": { "consumes": [ @@ -312,7 +630,18 @@ }, "definitions": { "basic.Vendor": { - "type": "object" + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "uuid": { + "type": "integer" + } + } }, "errcode.Error": { "type": "object", @@ -327,6 +656,29 @@ "type": "string" } } + }, + "model.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "type": "boolean" + }, + "nickname": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d3f6a3c..546714e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,6 +1,13 @@ basePath: /api definitions: basic.Vendor: + properties: + id: + type: integer + name: + type: string + uuid: + type: integer type: object errcode.Error: properties: @@ -11,6 +18,21 @@ definitions: msg: type: string type: object + model.User: + properties: + created_at: + type: string + id: + type: integer + is_admin: + type: boolean + nickname: + type: string + updated_at: + type: string + username: + type: string + type: object info: contact: {} description: This is a potato use golang @@ -40,6 +62,56 @@ paths: schema: $ref: '#/definitions/errcode.Error' summary: 鉴权验证 + /user/login: + post: + consumes: + - multipart/form-data + parameters: + - description: 用户名 + in: formData + name: username + required: true + type: string + - description: 密码 + in: formData + name: password + required: true + type: string + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/errcode.Error' + summary: 用户登录 + /user/register: + post: + consumes: + - multipart/form-data + parameters: + - description: 用户名 + in: formData + name: username + required: true + type: string + - description: 密码 + in: formData + name: password + required: true + type: string + - description: 昵称 + in: formData + name: nickname + type: string + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/errcode.Error' + summary: 用户注册 /v1/upload: post: consumes: @@ -68,6 +140,170 @@ paths: schema: $ref: '#/definitions/errcode.Error' summary: 上传文件 + /v1/users: + get: + consumes: + - application/json + parameters: + - description: auth token + in: header + name: token + required: true + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: page_size + type: integer + - description: 用户名 + in: query + maxLength: 30 + name: username + type: string + - description: 昵称 + in: query + maxLength: 30 + name: nickname + type: string + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/model.User' + summary: 用户列表 + tags: + - users + post: + consumes: + - multipart/form-data + parameters: + - description: auth token + in: header + name: token + required: true + type: string + - description: 用户名 + in: formData + maxLength: 30 + minLength: 1 + name: username + required: true + type: string + - description: 密码 + in: formData + maxLength: 18 + minLength: 1 + name: password + required: true + type: string + - description: 昵称 + in: formData + maxLength: 30 + name: nickname + type: string + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/model.User' + summary: 新增用户 + tags: + - users + /v1/users/{id}: + delete: + consumes: + - application/json + parameters: + - description: auth token + in: header + name: token + required: true + type: string + - description: 用户ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/model.User' + summary: 删除用户 + tags: + - users + get: + consumes: + - application/json + parameters: + - description: auth token + in: header + name: token + required: true + type: string + - description: 用户 ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/model.User' + summary: 用户 + tags: + - users + patch: + consumes: + - multipart/form-data + parameters: + - description: auth token + in: header + name: token + required: true + type: string + - description: 用户ID + in: path + name: id + required: true + type: integer + - description: 用户名 + in: formData + maxLength: 30 + name: username + type: string + - description: 密码 + in: formData + maxLength: 18 + name: password + type: string + - description: 昵称 + in: formData + maxLength: 30 + name: nickname + type: string + produces: + - application/json + responses: + "200": + description: 请求成功 + schema: + $ref: '#/definitions/model.User' + summary: 更新用户 + tags: + - users /v1/vendors: get: consumes: diff --git a/go.mod b/go.mod index abf0fbf..3e313d7 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/opentracing/opentracing-go v1.2.0 github.com/robfig/cron/v3 v3.0.0 github.com/smacker/opentracing-gorm v0.0.0-20181207094635-cd4974441042 // indirect - github.com/soheilhy/cmux v0.1.5 github.com/spf13/viper v1.7.1 github.com/swaggo/gin-swagger v1.3.0 github.com/swaggo/swag v1.7.0 diff --git a/initialize/gorm.go b/initialize/gorm.go index a4ceb5d..40f6006 100644 --- a/initialize/gorm.go +++ b/initialize/gorm.go @@ -1,7 +1,7 @@ /* * @Date: 2021-03-22 10:12:38 * @LastEditors: viletyy - * @LastEditTime: 2021-06-14 21:11:29 + * @LastEditTime: 2021-07-09 14:27:45 * @FilePath: /potato/initialize/gorm.go */ package initialize @@ -60,8 +60,9 @@ func GormSet(db *gorm.DB) { // 设置迁移 db.AutoMigrate( - basic.Vendor{}, - model.Auth{}, + &basic.Vendor{}, + &model.User{}, + &model.Auth{}, ) // 设置空闲连接池中的最大连接数 diff --git a/internal/controller/api/user.go b/internal/controller/api/user.go new file mode 100644 index 0000000..f417ad1 --- /dev/null +++ b/internal/controller/api/user.go @@ -0,0 +1,85 @@ +/* + * @Date: 2021-07-09 14:44:50 + * @LastEditors: viletyy + * @LastEditTime: 2021-07-09 14:45:52 + * @FilePath: /potato/internal/controller/api/user.go + */ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/viletyy/potato/global" + "github.com/viletyy/potato/internal/service" + "github.com/viletyy/potato/pkg/app" + "github.com/viletyy/potato/pkg/errcode" +) + +// @Summary 用户注册 +// @Description +// @Accept mpfd +// @Produce json +// @Param username formData string true "用户名" +// @Param password formData string true "密码" +// @Param nickname formData string false "昵称" +// @Success 200 {object} errcode.Error "请求成功" +// @Router /user/register [post] +func UserRegister(c *gin.Context) { + param := service.UserRegisterRequest{} + response := app.NewResponse(c) + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.GO_LOG.Sugar().Errorf("app.BindAndValid errs: %v", errs) + response.ToResponseErrors(errs.Errors()) + return + } + + svc := service.New(c.Request.Context()) + dbUser, err := svc.RegisterUser(¶m) + if err != nil { + global.GO_LOG.Sugar().Errorf("svc.RegisterUser err: %v", err) + response.ToErrorResponse(errcode.ErrorRegisterUserError) + return + } + + response.ToResponse(dbUser) +} + +// @Summary 用户登录 +// @Description +// @Accept mpfd +// @Produce json +// @Param username formData string true "用户名" +// @Param password formData string true "密码" +// @Success 200 {object} errcode.Error "请求成功" +// @Router /user/login [post] +func UserLogin(c *gin.Context) { + param := service.UserLoginRequest{} + response := app.NewResponse(c) + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.GO_LOG.Sugar().Errorf("app.BindAndValid errs: %v", errs) + response.ToResponseErrors(errs.Errors()) + return + } + + svc := service.New(c.Request.Context()) + dbUser, err := svc.LoginUser(¶m) + if err != nil { + global.GO_LOG.Sugar().Errorf("svc.LoginUser err: %v", err) + response.ToErrorResponse(errcode.ErrorLoginUserError) + return + } + + token, err := app.GenerateToken(dbUser.Username, dbUser.Password) + if err != nil { + global.GO_LOG.Sugar().Errorf("app.GenerateToken err: %v", err) + response.ToErrorResponse(errcode.UnauthorizedTokenGenerate) + return + } + + response.ToResponse(gin.H{ + "username": dbUser.Username, + "is_admin": dbUser.IsAdmin, + "token": token, + }) +} diff --git a/internal/controller/api/v1/user.go b/internal/controller/api/v1/user.go index 07c6d4e..c7f2963 100644 --- a/internal/controller/api/v1/user.go +++ b/internal/controller/api/v1/user.go @@ -1,7 +1,214 @@ /* * @Date: 2021-03-21 19:54:57 * @LastEditors: viletyy - * @LastEditTime: 2021-06-10 19:02:24 + * @LastEditTime: 2021-07-09 14:32:27 * @FilePath: /potato/internal/controller/api/v1/user.go */ package v1 + +import ( + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/viletyy/potato/global" + "github.com/viletyy/potato/internal/service" + "github.com/viletyy/potato/pkg/app" + "github.com/viletyy/potato/pkg/errcode" + "github.com/viletyy/yolk/convert" +) + +type User struct{} + +func NewUser() User { + return User{} +} + +// @Summary 用户列表 +// @Tags users +// @Description +// @Accept json +// @Produce json +// @Param token header string true "auth token" +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Param username query string false "用户名" maxlength(30) +// @Param nickname query string false "昵称" maxlength(30) +// @Success 200 {object} model.User "请求成功" +// @Router /v1/users [get] +func (user User) List(c *gin.Context) { + param := service.UserListRequest{} + response := app.NewResponse(c) + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.GO_LOG.Sugar().Errorf("app.BindAndValid errs: %v", errs) + response.ToResponseErrors(errs.Errors()) + return + } + + svc := service.New(c.Request.Context()) + paper := app.Pager{Page: app.GetPage(c), PageSize: app.GetPageSize(c)} + total, err := svc.CountUser(&service.CountUserRequest{Username: param.Username, Nickname: param.Nickname}) + if err != nil { + global.GO_LOG.Sugar().Errorf("svc.CountUser err: %v", err) + response.ToErrorResponse(errcode.ErrorCountUserFail) + return + } + + users, err := svc.GetUserList(¶m, &paper) + if err != nil { + global.GO_LOG.Sugar().Errorf("svc.GetUserList err: %v", err) + response.ToErrorResponse(errcode.ErrorGetUserListFail) + return + } + + response.ToResponseList(users, total) +} + +// @Summary 用户 +// @Tags users +// @Description +// @Accept json +// @Produce json +// @Param token header string true "auth token" +// @Param id path int true "用户 ID" +// @Success 200 {object} model.User "请求成功" +// @Router /v1/users/{id} [get] +func (user User) Get(c *gin.Context) { + userID, err := convert.StrTo(c.Param("id")).Int64() + response := app.NewResponse(c) + if err != nil { + global.GO_LOG.Sugar().Errorf("convert.StrTo err: %v", err) + response.ToErrorResponse(errcode.InvalidParams) + return + } + param := service.UserRequest{ID: userID} + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.GO_LOG.Sugar().Errorf("app.BindAndValid errs: %v", errs) + response.ToResponseErrors(errs.Errors()) + return + } + + svc := service.New(c.Request.Context()) + dbUser, err := svc.GetUser(¶m) + if err != nil { + global.GO_LOG.Sugar().Errorf("svc.GetUser err: %v", err) + response.ToErrorResponse(errcode.ErrorGetUserFail) + return + } + + response.ToResponse(dbUser) +} + +// @Summary 新增用户 +// @Tags users +// @Description +// @Accept mpfd +// @Produce json +// @Param token header string true "auth token" +// @Param username formData string true "用户名" minlength(1) maxlength(30) +// @Param password formData string true "密码" minlength(1) maxlength(18) +// @param nickname formData string false "昵称" maxlength(30) +// @Success 200 {object} model.User "请求成功" +// @Router /v1/users [post] +func (user User) Create(c *gin.Context) { + param := service.CreateUserRequest{} + response := app.NewResponse(c) + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.GO_LOG.Sugar().Errorf("app.BindAndValid errs: %v", errs) + response.ToResponseErrors(errs.Errors()) + return + } + + svc := service.New(c.Request.Context()) + dbUser, err := svc.CreateUser(¶m) + if err != nil { + global.GO_LOG.Sugar().Errorf("svc.CreateUser err: %v", err) + response.ToErrorResponse(errcode.ErrorCreateUserFail) + return + } + + response.ToResponse(dbUser) +} + +// @Summary 更新用户 +// @Tags users +// @Description +// @Accept mpfd +// @Produce json +// @Param token header string true "auth token" +// @Param id path int true "用户ID" +// @Param username formData string false "用户名" maxlength(30) +// @Param password formData string false "密码" maxlength(18) +// @param nickname formData string false "昵称" maxlength(30) +// @Success 200 {object} model.User "请求成功" +// @Router /v1/users/{id} [patch] +func (user User) Update(c *gin.Context) { + userID, err := convert.StrTo(c.Param("id")).Int64() + response := app.NewResponse(c) + if err != nil { + global.GO_LOG.Sugar().Errorf("convert.StrTo err: %v", err) + response.ToErrorResponse(errcode.InvalidParams) + return + } + + param := service.UpdateUserRequest{ID: userID} + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + global.GO_LOG.Sugar().Errorf("app.BindAndValid errs: %v", errs) + response.ToResponseErrors(errs) + return + } + svc := service.New(c.Request.Context()) + dbUser, err := svc.UpdateUser(¶m) + if err != nil { + if err == gorm.ErrRecordNotFound { + global.GO_LOG.Sugar().Errorf("svc.UpdateUser err: %v", err) + response.ToErrorResponse(errcode.ErrorGetUserFail) + return + } else { + global.GO_LOG.Sugar().Errorf("svc.UpdateUser err: %v", err) + response.ToErrorResponse(errcode.ErrorUpdateUserFail) + return + } + } + + response.ToResponse(dbUser) +} + +// @Summary 删除用户 +// @Tags users +// @Description +// @Accept json +// @Produce json +// @Param token header string true "auth token" +// @Param id path int true "用户ID" +// @Success 200 {object} model.User "请求成功" +// @Router /v1/users/{id} [delete] +func (user User) Delete(c *gin.Context) { + userID, err := convert.StrTo(c.Param("id")).Int64() + response := app.NewResponse(c) + if err != nil { + global.GO_LOG.Sugar().Errorf("convert.StrTo err: %v", err) + response.ToErrorResponse(errcode.InvalidParams) + return + } + + param := service.DeleteUserRequest{ID: userID} + svc := service.New(c.Request.Context()) + dbUser, err := svc.DeleteUser(¶m) + if err != nil { + if err == gorm.ErrRecordNotFound { + global.GO_LOG.Sugar().Errorf("svc.DeleteUser err: %v", err) + response.ToErrorResponse(errcode.ErrorGetUserFail) + return + } else { + global.GO_LOG.Sugar().Errorf("svc.DeleteUser err: %v", err) + response.ToErrorResponse(errcode.ErrorDeleteUserFail) + return + } + } + + errcode.Success.Data = dbUser + response.ToErrorResponse(errcode.Success) +} diff --git a/internal/dao/user.go b/internal/dao/user.go new file mode 100644 index 0000000..b4d2a2b --- /dev/null +++ b/internal/dao/user.go @@ -0,0 +1,98 @@ +/* + * @Date: 2021-07-09 14:30:19 + * @LastEditors: viletyy + * @LastEditTime: 2021-07-09 14:30:40 + * @FilePath: /potato/internal/dao/user.go + */ +package dao + +import ( + "github.com/viletyy/potato/internal/model" + "github.com/viletyy/potato/pkg/app" + "github.com/viletyy/yolk/crypt" +) + +func (d *Dao) RegisterUser(username string, password string, nickname string) (model.User, error) { + user := model.User{ + Username: username, + Password: crypt.Md5Encode(password), + Nickname: nickname, + } + + return user, user.Create(d.Engine) +} + +func (d *Dao) LoginUser(username string, password string) (model.User, error) { + user := model.User{ + Username: username, + Password: crypt.Md5Encode(password), + } + + return user.GetByUsernameAndPassword(d.Engine) +} + +func (d *Dao) CountUser(username, nickname string) (int, error) { + vendor := model.User{Username: username, Nickname: nickname} + return vendor.Count(d.Engine) +} + +func (d *Dao) GetUserList(username, nickname string, page, pageSize int) ([]model.User, error) { + user := model.User{Username: username, Nickname: nickname} + pageOffset := app.GetPageOffset(page, pageSize) + return user.List(d.Engine, pageOffset, pageSize) +} + +func (d *Dao) GetUser(id int64) (model.User, error) { + user := model.User{ + Model: &model.Model{ID: id}, + } + + return user.Get(d.Engine) +} + +func (d *Dao) CreateUser(username, password, nickname string) (model.User, error) { + user := model.User{ + Username: username, + Password: crypt.Md5Encode(password), + Nickname: nickname, + } + + return user, user.Create(d.Engine) +} + +func (d *Dao) UpdateUser(id int64, username, password, nickname string) (model.User, error) { + + user := model.User{ + Username: username, + Nickname: nickname, + Model: &model.Model{ID: id}, + } + + dbUser, err := user.Get(d.Engine) + + if password == "" { + user.Password = dbUser.Password + } else { + user.Password = crypt.Md5Encode(password) + } + + if err != nil { + return user, err + } + + return dbUser, user.Update(d.Engine) +} + +func (d *Dao) DeleteUser(id int64) (model.User, error) { + user := model.User{ + Model: &model.Model{ID: id}, + } + + dbUser, err := user.Get(d.Engine) + + if err != nil { + return user, err + } + + return dbUser, dbUser.Delete(d.Engine) +} diff --git a/internal/model/user.go b/internal/model/user.go index 3cefc5c..d2acee0 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -1,11 +1,13 @@ /* * @Date: 2021-03-21 19:54:57 * @LastEditors: viletyy - * @LastEditTime: 2021-06-11 10:38:24 + * @LastEditTime: 2021-07-09 14:31:04 * @FilePath: /potato/internal/model/user.go */ package model +import "github.com/jinzhu/gorm" + type User struct { *Model @@ -14,3 +16,65 @@ type User struct { Nickname string `json:"nickname"` IsAdmin bool `json:"is_admin" gorm:"default: false"` } + +func (u User) Count(db *gorm.DB) (int, error) { + var count int + if u.Username != "" { + db = db.Where("username = ?", u.Username) + } + if u.Nickname != "" { + db = db.Where("nickname = ?", u.Nickname) + } + if err := db.Model(&u).Count(&count).Error; err != nil { + return 0, err + } + + return count, nil +} + +func (u User) List(db *gorm.DB, pageOffset, pageSize int) (users []User, err error) { + if pageOffset >= 0 && pageSize > 0 { + db = db.Offset(pageOffset).Limit(pageSize) + } + if u.Username != "" { + db = db.Where("name = ?", u.Username) + } + if u.Nickname != "" { + db = db.Where("nickname = ?", u.Nickname) + } + + if err = db.Find(&users).Error; err != nil { + return nil, err + } + + return +} + +func (u User) GetByUsernameAndPassword(db *gorm.DB) (user User, err error) { + if notFound := db.Where("username = ? AND password = ?", u.Username, u.Password).First(&user).RecordNotFound(); notFound { + return u, gorm.ErrRecordNotFound + } + + return user, nil +} + +func (u User) Get(db *gorm.DB) (user User, err error) { + if notFound := db.Where("id = ?", u.ID).First(&user).RecordNotFound(); notFound { + return u, gorm.ErrRecordNotFound + } + + return user, nil +} + +func (u *User) Create(db *gorm.DB) error { + return db.Create(u).Error +} + +func (u *User) Update(db *gorm.DB) error { + err := db.Model(&User{}).Update(u).Error + return err +} + +func (u *User) Delete(db *gorm.DB) error { + return db.Where("id = ?", u.ID).Delete(u).Error +} diff --git a/internal/routers/router.go b/internal/routers/router.go index bdebe3e..7997c9b 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -1,7 +1,7 @@ /* * @Date: 2021-03-21 19:54:57 * @LastEditors: viletyy - * @LastEditTime: 2021-06-14 23:32:23 + * @LastEditTime: 2021-07-09 14:44:37 * @FilePath: /potato/internal/routers/router.go */ package routers @@ -52,6 +52,8 @@ func InitRouter() *gin.Engine { Engine.StaticFS("/static", http.Dir(global.GO_CONFIG.App.UploadSavePath)) Engine.POST("/api/auth", api.GetAuth) + Engine.POST("/api/user/register", api.UserRegister) + Engine.POST("/api/user/login", api.UserLogin) Engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) v1RouterGroup := Engine.Group("../api/v1") @@ -59,6 +61,16 @@ func InitRouter() *gin.Engine { upload := v1.NewUpload() v1RouterGroup.POST("/upload", upload.Create) + + users := v1RouterGroup.Group("/users") + user := v1.NewUser() + { + users.GET("", user.List) + users.POST("", user.Create) + users.GET("/:id", user.Get) + users.PATCH("/:id", user.Update) + users.DELETE("/:id", user.Delete) + } V1InitBasicRouter() return Engine diff --git a/internal/service/user.go b/internal/service/user.go new file mode 100644 index 0000000..954debc --- /dev/null +++ b/internal/service/user.go @@ -0,0 +1,88 @@ +/* + * @Date: 2021-07-09 14:29:44 + * @LastEditors: viletyy + * @LastEditTime: 2021-07-09 14:30:11 + * @FilePath: /potato/internal/service/user.go + */ + +package service + +import ( + "github.com/viletyy/potato/internal/model" + "github.com/viletyy/potato/pkg/app" +) + +type UserRegisterRequest struct { + Username string `form:"username" json:"username" validate:"max=30"` + Password string `form:"password" json:"password" validate:"max=18"` + Nickname string `form:"nickname" json:"nickname" validate:"max=30"` +} + +type UserLoginRequest struct { + Username string `form:"username" json:"username" validate:"max=30"` + Password string `form:"password" json:"password" validate:"max=18"` +} + +type UserListRequest struct { + Username string `form:"username" json:"username" validate:"max=30"` + Nickname string `form:"nickname" json:"nickname" validate:"max=30"` +} + +type CountUserRequest struct { + Username string `form:"username" json:"username" validate:"max=30"` + Password string `form:"password" json:"password" validate:"max=18"` + Nickname string `form:"nickname" json:"nickname" validate:"max=30"` +} + +type UserRequest struct { + ID int64 `form:"id" json:"id" validate:"required,gte=1"` +} + +type CreateUserRequest struct { + Username string `form:"username" json:"username" validate:"required,max=30"` + Password string `form:"password" json:"password" validate:"required,max=18"` + Nickname string `form:"nickname" json:"nickname" validate:"max=30"` +} + +type UpdateUserRequest struct { + ID int64 `form:"id" json:"id" validate:"required,gte=1"` + Username string `form:"username" json:"username" validate:"max=30"` + Password string `form:"password" json:"password" validate:"max=18"` + Nickname string `form:"nickname" json:"nickname" validate:"max=30"` +} + +type DeleteUserRequest struct { + ID int64 `form:"id" json:"id" validate:"required,gte=1"` +} + +func (svc *Service) RegisterUser(param *UserRegisterRequest) (model.User, error) { + return svc.Dao.RegisterUser(param.Username, param.Password, param.Nickname) +} + +func (svc *Service) LoginUser(param *UserLoginRequest) (model.User, error) { + return svc.Dao.LoginUser(param.Username, param.Password) +} + +func (svc *Service) CountUser(param *CountUserRequest) (int, error) { + return svc.Dao.CountUser(param.Username, param.Nickname) +} + +func (svc *Service) GetUserList(param *UserListRequest, pager *app.Pager) ([]model.User, error) { + return svc.Dao.GetUserList(param.Username, param.Nickname, pager.Page, pager.PageSize) +} + +func (svc *Service) GetUser(param *UserRequest) (model.User, error) { + return svc.Dao.GetUser(param.ID) +} + +func (svc *Service) CreateUser(param *CreateUserRequest) (model.User, error) { + return svc.Dao.CreateUser(param.Username, param.Password, param.Nickname) +} + +func (svc *Service) UpdateUser(param *UpdateUserRequest) (model.User, error) { + return svc.Dao.UpdateUser(param.ID, param.Username, param.Password, param.Nickname) +} + +func (svc *Service) DeleteUser(param *DeleteUserRequest) (model.User, error) { + return svc.Dao.DeleteUser(param.ID) +} diff --git a/main.go b/main.go index 4f1ff71..dcd27e9 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,14 @@ /* * @Date: 2021-03-21 19:54:57 * @LastEditors: viletyy - * @LastEditTime: 2021-07-08 13:59:06 + * @LastEditTime: 2021-07-09 14:37:35 * @FilePath: /potato/main.go */ package main import ( "flag" - "net" - "github.com/soheilhy/cmux" "github.com/viletyy/potato/global" "github.com/viletyy/potato/initialize" "github.com/viletyy/yolk/convert" @@ -20,12 +18,8 @@ import ( // @version 1.0 // @description This is a potato use golang // @BasePath /api - -var port string - -func runTcpServer(port string) (net.Listener, error) { - return net.Listen("tcp", ":"+port) -} +var grpcPort string +var httpPort string func main() { global.GO_VP = initialize.Viper() @@ -33,35 +27,32 @@ func main() { global.GO_DB = initialize.Gorm() global.GO_REDIS = initialize.Redis() global.GO_TRACER = initialize.Tracer() - initialize.Cron() + go initialize.Cron() defer global.GO_DB.Close() defer global.GO_REDIS.Close() - flag.StringVar(&port, "port", convert.ToString(global.GO_CONFIG.Server.Port), "启动端口号") + flag.StringVar(&grpcPort, "grpc_port", convert.ToString(global.GO_CONFIG.Server.GrpcPort), "启动grpc服务端口号") + flag.StringVar(&httpPort, "http_port", convert.ToString(global.GO_CONFIG.Server.HttpPort), "启动http服务端口号") + flag.Parse() - l, err := runTcpServer(port) - if err != nil { - global.GO_LOG.Sugar().Fatalf("Run Tcp Server err: %v", err) - } - m := cmux.New(l) - grpcL := m.MatchWithWriters( - cmux.HTTP2MatchHeaderFieldSendSettings( - "content-type", - "application/grpc", - ), - ) - httpL := m.Match(cmux.HTTP1Fast()) + errs := make(chan error) + go func() { + err := initialize.RunHttpServer(httpPort) + if err != nil { + errs <- err + } + }() - grpcS := initialize.RunGrpcServer() - httpS := initialize.RunServer(port) - go grpcS.Serve(grpcL) - go httpS.Serve(httpL) - - err = m.Serve() - if err != nil { + go func() { + err := initialize.RunGrpcServer(grpcPort) + if err != nil { + errs <- err + } + }() + select { + case err := <-errs: global.GO_LOG.Sugar().Fatalf("Run Server err: %v", err) } - } diff --git a/pkg/errcode/module_code.go b/pkg/errcode/module_code.go index 5047b61..aa1d4e6 100644 --- a/pkg/errcode/module_code.go +++ b/pkg/errcode/module_code.go @@ -1,17 +1,25 @@ /* * @Date: 2021-06-10 23:09:09 * @LastEditors: viletyy - * @LastEditTime: 2021-06-11 17:25:58 + * @LastEditTime: 2021-07-09 14:32:17 * @FilePath: /potato/pkg/errcode/module_code.go */ package errcode var ( ErrorUploadFileFail = NewError(20001, "上传文件失败") - ErrorGetVendorListFail = NewError(20101, "获取系统厂商列表失败") - ErrorGetVendorFail = NewError(20102, "获取系统厂商失败") - ErrorCreateVendorFail = NewError(20103, "创建系统厂商失败") - ErrorUpdateVendorFail = NewError(20104, "更新系统厂商失败") - ErrorDeleteVendorFail = NewError(20105, "删除系统厂商失败") - ErrorCountVendorFail = NewError(20106, "统计系统厂商失败") + ErrorRegisterUserError = NewError(20010, "用户注册失败") + ErrorLoginUserError = NewError(20011, "用户登录失败") + ErrorGetUserListFail = NewError(20101, "获取用户列表失败") + ErrorGetUserFail = NewError(20102, "获取用户失败") + ErrorCreateUserFail = NewError(20103, "创建用户失败") + ErrorUpdateUserFail = NewError(20104, "更新用户失败") + ErrorDeleteUserFail = NewError(20105, "删除用户失败") + ErrorCountUserFail = NewError(20106, "统计用户失败") + ErrorGetVendorListFail = NewError(20201, "获取系统厂商列表失败") + ErrorGetVendorFail = NewError(20202, "获取系统厂商失败") + ErrorCreateVendorFail = NewError(20203, "创建系统厂商失败") + ErrorUpdateVendorFail = NewError(20204, "更新系统厂商失败") + ErrorDeleteVendorFail = NewError(20205, "删除系统厂商失败") + ErrorCountVendorFail = NewError(20206, "统计系统厂商失败") ) diff --git a/proto/user.pb.go b/proto/user.pb.go new file mode 100644 index 0000000..a03d10d --- /dev/null +++ b/proto/user.pb.go @@ -0,0 +1,357 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.1 +// source: proto/user.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetUserListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"` + Page int64 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` +} + +func (x *GetUserListRequest) Reset() { + *x = GetUserListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetUserListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUserListRequest) ProtoMessage() {} + +func (x *GetUserListRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUserListRequest.ProtoReflect.Descriptor instead. +func (*GetUserListRequest) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{0} +} + +func (x *GetUserListRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *GetUserListRequest) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *GetUserListRequest) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *GetUserListRequest) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` + IsAdmin bool `protobuf:"varint,5,opt,name=is_admin,json=isAdmin,proto3" json:"is_admin,omitempty"` +} + +func (x *User) Reset() { + *x = User{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{1} +} + +func (x *User) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *User) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *User) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *User) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *User) GetIsAdmin() bool { + if x != nil { + return x.IsAdmin + } + return false +} + +type GetUserListReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []*User `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` + Pager *Pager `protobuf:"bytes,2,opt,name=pager,proto3" json:"pager,omitempty"` +} + +func (x *GetUserListReply) Reset() { + *x = GetUserListReply{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_user_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetUserListReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUserListReply) ProtoMessage() {} + +func (x *GetUserListReply) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUserListReply.ProtoReflect.Descriptor instead. +func (*GetUserListReply) Descriptor() ([]byte, []int) { + return file_proto_user_proto_rawDescGZIP(), []int{2} +} + +func (x *GetUserListReply) GetList() []*User { + if x != nil { + return x.List + } + return nil +} + +func (x *GetUserListReply) GetPager() *Pager { + if x != nil { + return x.Pager + } + return nil +} + +var File_proto_user_proto protoreflect.FileDescriptor + +var file_proto_user_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x85, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x22, 0x4b, 0x0a, + 0x10, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x19, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x05, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x05, + 0x70, 0x61, 0x67, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x50, 0x61, + 0x67, 0x65, 0x72, 0x52, 0x05, 0x70, 0x61, 0x67, 0x65, 0x72, 0x32, 0x46, 0x0a, 0x0b, 0x55, 0x73, + 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x47, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, + 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x22, 0x00, 0x42, 0x21, 0x5a, 0x1f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x76, 0x69, 0x6c, 0x65, 0x74, 0x79, 0x79, 0x2f, 0x70, 0x6f, 0x74, 0x61, 0x74, 0x6f, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_user_proto_rawDescOnce sync.Once + file_proto_user_proto_rawDescData = file_proto_user_proto_rawDesc +) + +func file_proto_user_proto_rawDescGZIP() []byte { + file_proto_user_proto_rawDescOnce.Do(func() { + file_proto_user_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_user_proto_rawDescData) + }) + return file_proto_user_proto_rawDescData +} + +var file_proto_user_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_proto_user_proto_goTypes = []interface{}{ + (*GetUserListRequest)(nil), // 0: GetUserListRequest + (*User)(nil), // 1: User + (*GetUserListReply)(nil), // 2: GetUserListReply + (*Pager)(nil), // 3: Pager +} +var file_proto_user_proto_depIdxs = []int32{ + 1, // 0: GetUserListReply.list:type_name -> User + 3, // 1: GetUserListReply.pager:type_name -> Pager + 0, // 2: UserService.GetUserList:input_type -> GetUserListRequest + 2, // 3: UserService.GetUserList:output_type -> GetUserListReply + 3, // [3:4] is the sub-list for method output_type + 2, // [2:3] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_proto_user_proto_init() } +func file_proto_user_proto_init() { + if File_proto_user_proto != nil { + return + } + file_proto_common_proto_init() + if !protoimpl.UnsafeEnabled { + file_proto_user_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetUserListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_user_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_user_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetUserListReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_user_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_user_proto_goTypes, + DependencyIndexes: file_proto_user_proto_depIdxs, + MessageInfos: file_proto_user_proto_msgTypes, + }.Build() + File_proto_user_proto = out.File + file_proto_user_proto_rawDesc = nil + file_proto_user_proto_goTypes = nil + file_proto_user_proto_depIdxs = nil +} diff --git a/proto/user.proto b/proto/user.proto new file mode 100644 index 0000000..9c7857c --- /dev/null +++ b/proto/user.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +option go_package= "github.com/viletyy/potato/proto"; + +import "proto/common.proto"; + +service UserService { + rpc GetUserList(GetUserListRequest) returns (GetUserListReply) {} +} + +message GetUserListRequest { + string username = 1; + string nickname = 2; + int64 page = 3; + int64 page_size = 4; +} + +message User { + int64 id = 1; + string username = 2; + string password = 3; + string nickname = 4; + bool is_admin = 5; +} + +message GetUserListReply { + repeated User list = 1; + Pager pager = 2; +} \ No newline at end of file diff --git a/proto/user_grpc.pb.go b/proto/user_grpc.pb.go new file mode 100644 index 0000000..08fab9b --- /dev/null +++ b/proto/user_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// UserServiceClient is the client API for UserService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type UserServiceClient interface { + GetUserList(ctx context.Context, in *GetUserListRequest, opts ...grpc.CallOption) (*GetUserListReply, error) +} + +type userServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient { + return &userServiceClient{cc} +} + +func (c *userServiceClient) GetUserList(ctx context.Context, in *GetUserListRequest, opts ...grpc.CallOption) (*GetUserListReply, error) { + out := new(GetUserListReply) + err := c.cc.Invoke(ctx, "/UserService/GetUserList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// UserServiceServer is the server API for UserService service. +// All implementations must embed UnimplementedUserServiceServer +// for forward compatibility +type UserServiceServer interface { + GetUserList(context.Context, *GetUserListRequest) (*GetUserListReply, error) + mustEmbedUnimplementedUserServiceServer() +} + +// UnimplementedUserServiceServer must be embedded to have forward compatible implementations. +type UnimplementedUserServiceServer struct { +} + +func (UnimplementedUserServiceServer) GetUserList(context.Context, *GetUserListRequest) (*GetUserListReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetUserList not implemented") +} +func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {} + +// UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to UserServiceServer will +// result in compilation errors. +type UnsafeUserServiceServer interface { + mustEmbedUnimplementedUserServiceServer() +} + +func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) { + s.RegisterService(&UserService_ServiceDesc, srv) +} + +func _UserService_GetUserList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetUserListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServiceServer).GetUserList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/UserService/GetUserList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServiceServer).GetUserList(ctx, req.(*GetUserListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// UserService_ServiceDesc is the grpc.ServiceDesc for UserService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var UserService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "UserService", + HandlerType: (*UserServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetUserList", + Handler: _UserService_GetUserList_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/user.proto", +} diff --git a/server/user.go b/server/user.go new file mode 100644 index 0000000..b3f6f34 --- /dev/null +++ b/server/user.go @@ -0,0 +1,57 @@ +/* + * @Date: 2021-07-09 14:33:26 + * @LastEditors: viletyy + * @LastEditTime: 2021-07-09 14:34:16 + * @FilePath: /potato/server/user.go + */ + +package server + +import ( + "context" + "encoding/json" + + "github.com/viletyy/potato/internal/service" + "github.com/viletyy/potato/pkg/app" + "github.com/viletyy/potato/pkg/errcode/rpc" + pb "github.com/viletyy/potato/proto" +) + +type UserServer struct { + pb.UnimplementedUserServiceServer +} + +func NewVendorServer() *UserServer { + return &UserServer{} +} + +func (t *UserServer) GetUserList(ctx context.Context, r *pb.GetUserListRequest) (*pb.GetUserListReply, error) { + svc := service.New(ctx) + dbUserList, err := svc.Dao.GetUserList(r.GetUsername(), r.GetNickname(), int(r.GetPage()), int(r.GetPageSize())) + if err != nil { + return nil, rpc.ToRpcError(rpc.RpcErrorGetVendorListFail) + } + total, err := svc.Dao.CountUser(r.GetUsername(), r.GetNickname()) + if err != nil { + return nil, rpc.ToRpcError(rpc.RpcErrorCountVendorFail) + } + data := map[string]interface{}{ + "list": dbUserList, + "pager": app.Pager{ + Page: int(r.GetPage()), + PageSize: int(r.GetPageSize()), + Total: total, + }, + } + byteData, err := json.Marshal(data) + if err != nil { + return nil, rpc.ToRpcError(rpc.RpcInvalidParams) + } + userList := pb.GetUserListReply{} + err = json.Unmarshal(byteData, &userList) + if err != nil { + return nil, rpc.ToRpcError(rpc.RpcInvalidParams) + } + + return &userList, nil +}