diff --git a/docs/docs.go b/docs/docs.go index d80d98f..6feaed1 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -656,6 +656,160 @@ const docTemplate = `{ } } }, + "/admin/create": { + "post": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "新增客服", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "新增客服", + "parameters": [ + { + "description": "请求参数", + "name": "RequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.createAdminRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.createAdminResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, + "/admin/delete": { + "post": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "删除客服", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "删除客服", + "parameters": [ + { + "description": "请求参数", + "name": "RequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.deleteAdminRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.deleteAdminResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, + "/admin/list": { + "get": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "客服列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "客服列表", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "当前页码", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 20, + "description": "每页返回的数据量,最多 100 条", + "name": "page_size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.listResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, "/admin/login": { "post": { "description": "管理员登录", @@ -845,6 +999,58 @@ const docTemplate = `{ } } }, + "/admin/{id}": { + "put": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "编辑客服", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "编辑客服", + "parameters": [ + { + "type": "string", + "description": "编号ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "请求参数", + "name": "RequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.modifyAdminRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.modifyAdminResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, "/app/messages": { "get": { "description": "获取消息日志", @@ -988,6 +1194,122 @@ const docTemplate = `{ } }, "definitions": { + "admin.createAdminRequest": { + "type": "object", + "required": [ + "nickname", + "password", + "username" + ], + "properties": { + "avatar": { + "description": "头像", + "type": "string" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "admin.createAdminResponse": { + "type": "object", + "properties": { + "message": { + "description": "提示信息", + "type": "string" + } + } + }, + "admin.deleteAdminRequest": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "ids": { + "description": "编号(多个用,分割)", + "type": "string" + } + } + }, + "admin.deleteAdminResponse": { + "type": "object", + "properties": { + "message": { + "description": "提示信息", + "type": "string" + } + } + }, + "admin.listData": { + "type": "object", + "properties": { + "avatar": { + "description": "头像", + "type": "string" + }, + "created_at": { + "description": "创建时间", + "type": "string" + }, + "id": { + "description": "编号", + "type": "integer" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "updated_at": { + "description": "更新时间", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "admin.listResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.listData" + } + }, + "page": { + "description": "当前页码", + "type": "integer" + }, + "page_size": { + "description": "每页返回的数据量", + "type": "integer" + }, + "total": { + "description": "符合查询条件的总记录数", + "type": "integer" + } + } + }, "admin.loginRequest": { "type": "object", "required": [ @@ -1018,6 +1340,45 @@ const docTemplate = `{ } } }, + "admin.modifyAdminRequest": { + "type": "object", + "required": [ + "nickname", + "password", + "username" + ], + "properties": { + "avatar": { + "description": "头像", + "type": "string" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "admin.modifyAdminResponse": { + "type": "object", + "properties": { + "message": { + "description": "提示信息", + "type": "string" + } + } + }, "app.adminSendMessageRequest": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 5a648e3..d385a74 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -648,6 +648,160 @@ } } }, + "/admin/create": { + "post": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "新增客服", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "新增客服", + "parameters": [ + { + "description": "请求参数", + "name": "RequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.createAdminRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.createAdminResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, + "/admin/delete": { + "post": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "删除客服", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "删除客服", + "parameters": [ + { + "description": "请求参数", + "name": "RequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.deleteAdminRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.deleteAdminResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, + "/admin/list": { + "get": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "客服列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "客服列表", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "昵称", + "name": "nickname", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "当前页码", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "integer", + "default": 20, + "description": "每页返回的数据量,最多 100 条", + "name": "page_size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.listResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, "/admin/login": { "post": { "description": "管理员登录", @@ -837,6 +991,58 @@ } } }, + "/admin/{id}": { + "put": { + "security": [ + { + "LoginVerifyToken": [] + } + ], + "description": "编辑客服", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "管理端.客服管理" + ], + "summary": "编辑客服", + "parameters": [ + { + "type": "string", + "description": "编号ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "请求参数", + "name": "RequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.modifyAdminRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/admin.modifyAdminResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/code.Failure" + } + } + } + } + }, "/app/messages": { "get": { "description": "获取消息日志", @@ -980,6 +1186,122 @@ } }, "definitions": { + "admin.createAdminRequest": { + "type": "object", + "required": [ + "nickname", + "password", + "username" + ], + "properties": { + "avatar": { + "description": "头像", + "type": "string" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "admin.createAdminResponse": { + "type": "object", + "properties": { + "message": { + "description": "提示信息", + "type": "string" + } + } + }, + "admin.deleteAdminRequest": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "ids": { + "description": "编号(多个用,分割)", + "type": "string" + } + } + }, + "admin.deleteAdminResponse": { + "type": "object", + "properties": { + "message": { + "description": "提示信息", + "type": "string" + } + } + }, + "admin.listData": { + "type": "object", + "properties": { + "avatar": { + "description": "头像", + "type": "string" + }, + "created_at": { + "description": "创建时间", + "type": "string" + }, + "id": { + "description": "编号", + "type": "integer" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "updated_at": { + "description": "更新时间", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "admin.listResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.listData" + } + }, + "page": { + "description": "当前页码", + "type": "integer" + }, + "page_size": { + "description": "每页返回的数据量", + "type": "integer" + }, + "total": { + "description": "符合查询条件的总记录数", + "type": "integer" + } + } + }, "admin.loginRequest": { "type": "object", "required": [ @@ -1010,6 +1332,45 @@ } } }, + "admin.modifyAdminRequest": { + "type": "object", + "required": [ + "nickname", + "password", + "username" + ], + "properties": { + "avatar": { + "description": "头像", + "type": "string" + }, + "mobile": { + "description": "手机号", + "type": "string" + }, + "nickname": { + "description": "昵称", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "admin.modifyAdminResponse": { + "type": "object", + "properties": { + "message": { + "description": "提示信息", + "type": "string" + } + } + }, "app.adminSendMessageRequest": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 45c31e2..228a26f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,87 @@ basePath: / definitions: + admin.createAdminRequest: + properties: + avatar: + description: 头像 + type: string + mobile: + description: 手机号 + type: string + nickname: + description: 昵称 + type: string + password: + description: 密码 + type: string + username: + description: 用户名 + type: string + required: + - nickname + - password + - username + type: object + admin.createAdminResponse: + properties: + message: + description: 提示信息 + type: string + type: object + admin.deleteAdminRequest: + properties: + ids: + description: 编号(多个用,分割) + type: string + required: + - ids + type: object + admin.deleteAdminResponse: + properties: + message: + description: 提示信息 + type: string + type: object + admin.listData: + properties: + avatar: + description: 头像 + type: string + created_at: + description: 创建时间 + type: string + id: + description: 编号 + type: integer + mobile: + description: 手机号 + type: string + nickname: + description: 昵称 + type: string + updated_at: + description: 更新时间 + type: string + username: + description: 用户名 + type: string + type: object + admin.listResponse: + properties: + list: + items: + $ref: '#/definitions/admin.listData' + type: array + page: + description: 当前页码 + type: integer + page_size: + description: 每页返回的数据量 + type: integer + total: + description: 符合查询条件的总记录数 + type: integer + type: object admin.loginRequest: properties: password: @@ -21,6 +103,34 @@ definitions: description: 登录成功后颁发的 Token type: string type: object + admin.modifyAdminRequest: + properties: + avatar: + description: 头像 + type: string + mobile: + description: 手机号 + type: string + nickname: + description: 昵称 + type: string + password: + description: 密码 + type: string + username: + description: 用户名 + type: string + required: + - nickname + - password + - username + type: object + admin.modifyAdminResponse: + properties: + message: + description: 提示信息 + type: string + type: object app.adminSendMessageRequest: properties: app_id: @@ -516,6 +626,39 @@ info: title: mini-chat 接口文档 version: v0.0.1 paths: + /admin/{id}: + put: + consumes: + - application/json + description: 编辑客服 + parameters: + - description: 编号ID + in: path + name: id + required: true + type: string + - description: 请求参数 + in: body + name: RequestBody + required: true + schema: + $ref: '#/definitions/admin.modifyAdminRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/admin.modifyAdminResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/code.Failure' + security: + - LoginVerifyToken: [] + summary: 编辑客服 + tags: + - 管理端.客服管理 /admin/app/{id}: put: consumes: @@ -928,6 +1071,104 @@ paths: summary: 小程序列表 tags: - 管理端.小程序 + /admin/create: + post: + consumes: + - application/json + description: 新增客服 + parameters: + - description: 请求参数 + in: body + name: RequestBody + required: true + schema: + $ref: '#/definitions/admin.createAdminRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/admin.createAdminResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/code.Failure' + security: + - LoginVerifyToken: [] + summary: 新增客服 + tags: + - 管理端.客服管理 + /admin/delete: + post: + consumes: + - application/json + description: 删除客服 + parameters: + - description: 请求参数 + in: body + name: RequestBody + required: true + schema: + $ref: '#/definitions/admin.deleteAdminRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/admin.deleteAdminResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/code.Failure' + security: + - LoginVerifyToken: [] + summary: 删除客服 + tags: + - 管理端.客服管理 + /admin/list: + get: + consumes: + - application/json + description: 客服列表 + parameters: + - description: 用户名 + in: query + name: username + type: string + - description: 昵称 + in: query + name: nickname + type: string + - default: 1 + description: 当前页码 + in: query + name: page + required: true + type: integer + - default: 20 + description: 每页返回的数据量,最多 100 条 + in: query + name: page_size + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/admin.listResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/code.Failure' + security: + - LoginVerifyToken: [] + summary: 客服列表 + tags: + - 管理端.客服管理 /admin/login: post: consumes: diff --git a/internal/api/admin/admin_create.go b/internal/api/admin/admin_create.go new file mode 100755 index 0000000..41aa557 --- /dev/null +++ b/internal/api/admin/admin_create.go @@ -0,0 +1,115 @@ +package admin + +import ( + "fmt" + "net/http" + "time" + + "mini-chat/internal/code" + "mini-chat/internal/pkg/core" + "mini-chat/internal/pkg/utils" + "mini-chat/internal/pkg/validation" + "mini-chat/internal/repository/mysql/model" + + "gorm.io/gorm" +) + +type createAdminRequest struct { + UserName string `json:"username" binding:"required"` // 用户名 + NickName string `json:"nickname" binding:"required"` // 昵称 + Mobile string `json:"mobile"` // 手机号 + Password string `json:"password" binding:"required"` // 密码 + Avatar string `json:"avatar"` // 头像 +} + +type createAdminResponse struct { + Message string `json:"message"` // 提示信息 +} + +// CreateAdmin 新增客服 +// @Summary 新增客服 +// @Description 新增客服 +// @Tags 管理端.客服管理 +// @Accept json +// @Produce json +// @Param RequestBody body createAdminRequest true "请求参数" +// @Success 200 {object} createAdminResponse +// @Failure 400 {object} code.Failure +// @Router /admin/create [post] +// @Security LoginVerifyToken +func (h *handler) CreateAdmin() core.HandlerFunc { + return func(ctx core.Context) { + req := new(createAdminRequest) + res := new(createAdminResponse) + if err := ctx.ShouldBindJSON(req); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)), + ) + return + } + + if ctx.SessionUserInfo().IsSuper != 1 { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.CreateAdminError, + fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), "禁止操作")), + ) + return + } + + info, err := h.readDB.Admin.WithContext(ctx.RequestContext()). + Where(h.readDB.Admin.Username.Eq(req.UserName)). + First() + if err != nil && err != gorm.ErrRecordNotFound { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.CreateAdminError, + fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), err.Error())), + ) + return + } + + if info != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.CreateAdminError, + fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), "该账号已存在")), + ) + return + } + + hashedPassword, err := utils.GenerateAdminHashedPassword(req.Password) + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.CreateAdminError, + fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), err.Error())), + ) + return + } + + Admin := new(model.Admin) + Admin.Username = req.UserName + Admin.Nickname = req.NickName + Admin.Mobile = req.Mobile + Admin.Password = hashedPassword + Admin.Avatar = req.Avatar + Admin.LoginStatus = 1 + Admin.IsSuper = 0 + Admin.CreatedUser = ctx.SessionUserInfo().UserName + Admin.CreatedAt = time.Now() + if err := h.writeDB.Admin.WithContext(ctx.RequestContext()).Create(Admin); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.CreateAdminError, + fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), err.Error())), + ) + return + } + + res.Message = "操作成功" + ctx.Payload(res) + } +} diff --git a/internal/api/admin/admin_delete.go b/internal/api/admin/admin_delete.go new file mode 100755 index 0000000..2ba4984 --- /dev/null +++ b/internal/api/admin/admin_delete.go @@ -0,0 +1,109 @@ +package admin + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "mini-chat/internal/code" + "mini-chat/internal/pkg/core" + "mini-chat/internal/pkg/validation" + "mini-chat/internal/repository/mysql/model" +) + +type deleteAdminRequest struct { + Ids string `json:"ids" binding:"required"` // 编号(多个用,分割) +} + +type deleteAdminResponse struct { + Message string `json:"message"` // 提示信息 +} + +// DeleteAdmin 删除客服 +// @Summary 删除客服 +// @Description 删除客服 +// @Tags 管理端.客服管理 +// @Accept json +// @Produce json +// @Param RequestBody body deleteAdminRequest true "请求参数" +// @Success 200 {object} deleteAdminResponse +// @Failure 400 {object} code.Failure +// @Router /admin/delete [post] +// @Security LoginVerifyToken +func (h *handler) DeleteAdmin() core.HandlerFunc { + return func(ctx core.Context) { + req := new(deleteAdminRequest) + res := new(deleteAdminResponse) + if err := ctx.ShouldBindJSON(req); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)), + ) + return + } + + if ctx.SessionUserInfo().IsSuper != 1 { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.DeleteAdminError, + fmt.Sprintf("%s: %s", code.Text(code.DeleteAdminError), "禁止操作")), + ) + return + } + + idList := strings.Split(req.Ids, ",") + if len(idList) == 0 || (len(idList) == 1 && idList[0] == "") { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + "编号不能为空"), + ) + return + } + + var ids []int32 + + for _, strID := range idList { + if strID == "" { + continue + } + + id, err := strconv.Atoi(strID) + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + fmt.Sprintf("无效的编号: %s", strID)), + ) + return + } + + ids = append(ids, int32(id)) + } + + if len(ids) == 0 { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + "编号不能为空"), + ) + return + } + + if _, err := h.writeDB.Admin.WithContext(ctx.RequestContext()). + Where(h.writeDB.Admin.IsSuper.Eq(0)). + Where(h.writeDB.Admin.ID.In(ids...)). + Delete(&model.Admin{}); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.DeleteAdminError, + fmt.Sprintf("%s: %s", code.Text(code.DeleteAdminError), err.Error())), + ) + return + } + res.Message = "操作成功" + ctx.Payload(res) + } +} diff --git a/internal/api/admin/admin_list.go b/internal/api/admin/admin_list.go new file mode 100755 index 0000000..4b07a64 --- /dev/null +++ b/internal/api/admin/admin_list.go @@ -0,0 +1,148 @@ +package admin + +import ( + "fmt" + "net/http" + + "mini-chat/internal/code" + "mini-chat/internal/pkg/core" + "mini-chat/internal/pkg/timeutil" + "mini-chat/internal/pkg/validation" + + "gorm.io/gorm" +) + +type listRequest struct { + Username string `form:"username"` // 用户名 + Nickname string `form:"nickname"` // 昵称 + Page int `form:"page"` // 当前页码,默认为第一页 + PageSize int `form:"page_size"` // 每页返回的数据量 +} + +type listData struct { + ID int32 `json:"id"` // 编号 + UserName string `json:"username"` // 用户名 + NickName string `json:"nickname"` // 昵称 + Mobile string `json:"mobile"` // 手机号 + Avatar string `json:"avatar"` // 头像 + CreatedAt string `json:"created_at"` // 创建时间 + UpdatedAt string `json:"updated_at"` // 更新时间 +} + +type listResponse struct { + Page int `json:"page"` // 当前页码 + PageSize int `json:"page_size"` // 每页返回的数据量 + Total int64 `json:"total"` // 符合查询条件的总记录数 + List []listData `json:"list"` +} + +// PageList 客服列表 +// @Summary 客服列表 +// @Description 客服列表 +// @Tags 管理端.客服管理 +// @Accept json +// @Produce json +// @Param username query string false "用户名" +// @Param nickname query string false "昵称" +// @Param page query int true "当前页码" default(1) +// @Param page_size query int true "每页返回的数据量,最多 100 条" default(20) +// @Success 200 {object} listResponse +// @Failure 400 {object} code.Failure +// @Router /admin/list [get] +// @Security LoginVerifyToken +func (h *handler) PageList() core.HandlerFunc { + return func(ctx core.Context) { + req := new(listRequest) + res := new(listResponse) + + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)), + ) + return + } + + if req.Page == 0 { + req.Page = 1 + } + + if req.PageSize == 0 { + req.PageSize = 20 + } + + if req.PageSize > 100 { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ListAdminError, + fmt.Sprintf("%s: 一次最多只能查询 100 条", code.Text(code.ListAdminError)), + )) + return + } + + query := h.readDB.Admin.WithContext(ctx.RequestContext()) + + if ctx.SessionUserInfo().IsSuper != 1 { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ListAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ListAdminError), "禁止操作")), + ) + return + } + + if req.Username != "" { + query = query.Where(h.readDB.Admin.Username.Like(fmt.Sprintf("%%%s%%", req.Username))) + } + + if req.Nickname != "" { + query = query.Where(h.readDB.Admin.Nickname.Like(fmt.Sprintf("%%%s%%", req.Nickname))) + } + + listQueryDB := query.Session(&gorm.Session{}) + countQueryDB := query.Session(&gorm.Session{}) + + resultData, err := listQueryDB. + Order(h.readDB.Admin.ID.Desc()). + Limit(req.PageSize). + Offset((req.Page - 1) * req.PageSize).Find() + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ListAdminError, + fmt.Sprintf("%s:%s", code.Text(code.ListAdminError), err.Error())), + ) + return + } + + count, err := countQueryDB.Count() + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ListAdminError, + fmt.Sprintf("%s:%s", code.Text(code.ListAdminError), err.Error())), + ) + return + } + + res.Page = req.Page + res.PageSize = req.PageSize + res.Total = count + res.List = make([]listData, len(resultData)) + + for k, v := range resultData { + res.List[k] = listData{ + ID: v.ID, + UserName: v.Username, + NickName: v.Nickname, + Mobile: v.Mobile, + Avatar: v.Avatar, + CreatedAt: timeutil.FriendlyTime(v.CreatedAt), + UpdatedAt: timeutil.FriendlyTime(v.CreatedAt), + } + } + + ctx.Payload(res) + } +} diff --git a/internal/api/admin/admin_modify.go b/internal/api/admin/admin_modify.go new file mode 100644 index 0000000..2c48a59 --- /dev/null +++ b/internal/api/admin/admin_modify.go @@ -0,0 +1,154 @@ +package admin + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "mini-chat/internal/code" + "mini-chat/internal/pkg/core" + "mini-chat/internal/pkg/utils" + "mini-chat/internal/pkg/validation" + + "gorm.io/gorm" +) + +type modifyAdminRequest struct { + UserName string `json:"username" binding:"required"` // 用户名 + NickName string `json:"nickname" binding:"required"` // 昵称 + Mobile string `json:"mobile"` // 手机号 + Password string `json:"password" binding:"required"` // 密码 + Avatar string `json:"avatar"` // 头像 +} + +type modifyAdminResponse struct { + Message string `json:"message"` // 提示信息 +} + +// ModifyAdmin 编辑客服 +// @Summary 编辑客服 +// @Description 编辑客服 +// @Tags 管理端.客服管理 +// @Accept json +// @Produce json +// @Param id path string true "编号ID" +// @Param RequestBody body modifyAdminRequest true "请求参数" +// @Success 200 {object} modifyAdminResponse +// @Failure 400 {object} code.Failure +// @Router /admin/{id} [put] +// @Security LoginVerifyToken +func (h *handler) ModifyAdmin() core.HandlerFunc { + return func(ctx core.Context) { + req := new(modifyAdminRequest) + res := new(modifyAdminResponse) + if err := ctx.ShouldBindJSON(req); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)), + ) + return + } + + if ctx.SessionUserInfo().IsSuper != 1 { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ModifyAdminError), "禁止操作")), + ) + return + } + + if req.UserName == "" && req.NickName == "" && req.Password == "" { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + "用户名、昵称、密码为必填"), + ) + return + } + + id, err := strconv.Atoi(ctx.Param("id")) + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ParamBindError, + "未传递编号ID"), + ) + return + } + + checkIdInfo, err := h.readDB.Admin.WithContext(ctx.RequestContext()). + Where(h.readDB.Admin.ID.Eq(int32(id))). + First() + if err != nil && err != gorm.ErrRecordNotFound { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ModifyAdminError), err.Error())), + ) + return + } + + if checkIdInfo == nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ModifyAdminError), "该账号不存在")), + ) + return + } + + checkUserNameInfo, err := h.readDB.Admin.WithContext(ctx.RequestContext()). + Where(h.readDB.Admin.ID.Neq(int32(id))). + Where(h.readDB.Admin.Username.Eq(req.UserName)). + First() + if err != nil && err != gorm.ErrRecordNotFound { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ModifyAdminError), err.Error())), + ) + return + } + + if checkUserNameInfo != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ModifyAdminError), "该账号已存在")), + ) + return + } + + hashedPassword, err := utils.GenerateAdminHashedPassword(req.Password) + if err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s: %s", code.Text(code.ModifyAdminError), err.Error())), + ) + return + } + + checkIdInfo.Username = req.UserName + checkIdInfo.Nickname = req.NickName + checkIdInfo.Mobile = req.Mobile + checkIdInfo.Password = hashedPassword + checkIdInfo.Avatar = req.Avatar + checkIdInfo.UpdatedUser = ctx.SessionUserInfo().UserName + checkIdInfo.UpdatedAt = time.Now() + if err := h.writeDB.Admin.WithContext(ctx.RequestContext()).Save(checkIdInfo); err != nil { + ctx.AbortWithError(core.Error( + http.StatusBadRequest, + code.ModifyAdminError, + fmt.Sprintf("%s:%s", code.Text(code.ModifyAdminError), err.Error())), + ) + return + } + + res.Message = "操作成功" + ctx.Payload(res) + } +} diff --git a/internal/api/admin/func_login.go b/internal/api/admin/login.go similarity index 100% rename from internal/api/admin/func_login.go rename to internal/api/admin/login.go diff --git a/internal/code/code.go b/internal/code/code.go index d72f6af..e445950 100644 --- a/internal/code/code.go +++ b/internal/code/code.go @@ -28,6 +28,10 @@ const ( ModifyAppError = 20204 CreateAppUserError = 20205 ListAppUserError = 20206 + CreateAdminError = 20207 + ListAdminError = 20208 + ModifyAdminError = 20209 + DeleteAdminError = 20210 CreateKeywordError = 20301 ListKeywordError = 20302 diff --git a/internal/code/zh-cn.go b/internal/code/zh-cn.go index 3a2fbb4..722dc1f 100644 --- a/internal/code/zh-cn.go +++ b/internal/code/zh-cn.go @@ -14,6 +14,10 @@ var zhCNText = map[int]string{ ModifyAppError: "修改小程序失败", CreateAppUserError: "创建用户失败", ListAppUserError: "获取用户列表失败", + CreateAdminError: "创建客服失败", + ListAdminError: "获取客服列表失败", + ModifyAdminError: "修改客服失败", + DeleteAdminError: "删除客服失败", CreateKeywordError: "创建关键字失败", DeleteKeywordError: "删除关键字失败", diff --git a/internal/repository/mysql/dao/admin.gen.go b/internal/repository/mysql/dao/admin.gen.go index ff3be73..0455cda 100644 --- a/internal/repository/mysql/dao/admin.gen.go +++ b/internal/repository/mysql/dao/admin.gen.go @@ -30,6 +30,7 @@ func newAdmin(db *gorm.DB, opts ...gen.DOOption) admin { _admin.ID = field.NewInt32(tableName, "id") _admin.Username = field.NewString(tableName, "username") _admin.Nickname = field.NewString(tableName, "nickname") + _admin.Avatar = field.NewString(tableName, "avatar") _admin.Mobile = field.NewString(tableName, "mobile") _admin.Password = field.NewString(tableName, "password") _admin.IsSuper = field.NewInt32(tableName, "is_super") @@ -55,6 +56,7 @@ type admin struct { ID field.Int32 // 主键 Username field.String // 用户名 Nickname field.String // 昵称 + Avatar field.String // 头像 Mobile field.String // 手机号 Password field.String // 密码 IsSuper field.Int32 // 是否为超管(1:是 0:否) @@ -85,6 +87,7 @@ func (a *admin) updateTableName(table string) *admin { a.ID = field.NewInt32(table, "id") a.Username = field.NewString(table, "username") a.Nickname = field.NewString(table, "nickname") + a.Avatar = field.NewString(table, "avatar") a.Mobile = field.NewString(table, "mobile") a.Password = field.NewString(table, "password") a.IsSuper = field.NewInt32(table, "is_super") @@ -112,10 +115,11 @@ func (a *admin) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (a *admin) fillFieldMap() { - a.fieldMap = make(map[string]field.Expr, 14) + a.fieldMap = make(map[string]field.Expr, 15) a.fieldMap["id"] = a.ID a.fieldMap["username"] = a.Username a.fieldMap["nickname"] = a.Nickname + a.fieldMap["avatar"] = a.Avatar a.fieldMap["mobile"] = a.Mobile a.fieldMap["password"] = a.Password a.fieldMap["is_super"] = a.IsSuper diff --git a/internal/repository/mysql/model/admin.gen.go b/internal/repository/mysql/model/admin.gen.go index 69eb595..1f4e1ae 100644 --- a/internal/repository/mysql/model/admin.gen.go +++ b/internal/repository/mysql/model/admin.gen.go @@ -15,6 +15,7 @@ type Admin struct { ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:主键" json:"id"` // 主键 Username string `gorm:"column:username;not null;comment:用户名" json:"username"` // 用户名 Nickname string `gorm:"column:nickname;not null;comment:昵称" json:"nickname"` // 昵称 + Avatar string `gorm:"column:avatar;not null;comment:头像" json:"avatar"` // 头像 Mobile string `gorm:"column:mobile;not null;comment:手机号" json:"mobile"` // 手机号 Password string `gorm:"column:password;not null;comment:密码" json:"password"` // 密码 IsSuper int32 `gorm:"column:is_super;not null;comment:是否为超管(1:是 0:否)" json:"is_super"` // 是否为超管(1:是 0:否) diff --git a/internal/router/router.go b/internal/router/router.go index 3f622fe..59d22d6 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -74,6 +74,11 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co adminAuthApiRouter.PUT("/app/:id", appHandler.ModifyApp()) // 修改小程序 adminAuthApiRouter.GET("/apps", appHandler.PageList()) // 小程序列表 + adminAuthApiRouter.POST("/create", adminHandler.CreateAdmin()) // 新增客服 + adminAuthApiRouter.POST("/delete", adminHandler.DeleteAdmin()) // 删除客服 + adminAuthApiRouter.PUT("/:id", adminHandler.ModifyAdmin()) // 编辑客服 + adminAuthApiRouter.GET("/list", adminHandler.PageList()) // 客服列表 + adminAuthApiRouter.POST("/app/keyword", keywordHandler.CreateKeyword()) // 添加意图关键字 adminAuthApiRouter.PUT("/app/keyword/:id", keywordHandler.ModifyKeyword()) // 修改意图关键字 adminAuthApiRouter.DELETE("/app/keyword/:id", keywordHandler.DeleteKeyword()) // 删除意图关键字