feature(1.2.8): swagger 接口文档新增 Security

- 将 middleware 命名为 interceptor
- 将 deploy 命名为 deployments
- 移除 pkg/errno
- 使用 proposal 目录
- 优化代码
This commit is contained in:
新亮
2021-11-28 13:25:27 +08:00
parent fc7e58db04
commit 4c37a7e6b5
114 changed files with 2611 additions and 1478 deletions

View File

@@ -1,12 +1,12 @@
# FROM 基于 golang:1.15-alpine
FROM golang:1.15-alpine AS builder
# FROM 基于 golang:1.16-alpine
FROM golang:1.16-alpine AS builder
# ENV 设置环境变量
ENV GOPATH=/opt/repo
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.io,direct
# ADD 源路径 目标路径
# COPY 源路径 目标路径
COPY . $GOPATH/src/github.com/xinliangnote/go-gin-api
# RUN 执行 go build .

View File

@@ -89,14 +89,14 @@ func main() {
funcContent += fmt.Sprintf("// @Description%s \n", nameArr[1])
// Tags
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[1])
funcContent += fmt.Sprintf("// @Accept multipart/form-data \n")
funcContent += fmt.Sprintf("// @Accept application/x-www-form-urlencoded \n")
funcContent += fmt.Sprintf("// @Produce json \n")
funcContent += fmt.Sprintf("// @Param Request body %sRequest true \"请求信息\" \n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("// @Success 200 {object} %sResponse \n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("// @Failure 400 {object} code.Failure \n")
// Router
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[2])
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String())
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(ctx core.Context) {\n\n}}", v.Names[0].String())
funcFile.WriteString(funcContent)
funcFile.Close()

View File

@@ -1,5 +1,7 @@
package configs
import "time"
const (
// MinGoVersion 最小 Go 版本
MinGoVersion = 1.16
@@ -28,12 +30,15 @@ const (
// HeaderLoginToken 登录验证 TokenHeader 中传递的参数
HeaderLoginToken = "Token"
// HeaderSignToken 签名验证 TokenHeader 中传递的参数
// HeaderSignToken 签名验证 AuthorizationHeader 中传递的参数
HeaderSignToken = "Authorization"
// HeaderSignTokenDate 签名验证 DateHeader 中传递的参数
HeaderSignTokenDate = "Authorization-Date"
// HeaderSignTokenTimeout 签名有效期为 2 分钟
HeaderSignTokenTimeout = time.Minute * 2
// RedisKeyPrefixLoginUser Redis Key 前缀 - 登录用户信息
RedisKeyPrefixLoginUser = ProjectName + ":login-user:"
@@ -48,4 +53,7 @@ const (
// MaxRequestsPerSecond 每秒最大请求量
MaxRequestsPerSecond = 10000
// LoginSessionTTL 登录有效期为 24 小时
LoginSessionTTL = time.Hour * 24
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
basePath: /
definitions:
admin.ListMenuData:
properties:
@@ -428,6 +429,21 @@ definitions:
description: 主键ID
type: integer
type: object
helper.md5Response:
properties:
md5_str:
description: MD5后的字符串
type: string
type: object
helper.signResponse:
properties:
authorization:
description: 签名信息-Authorization
type: string
authorization_date:
description: 签名信息-Authorization-Date
type: string
type: object
menu.createActionResponse:
properties:
id:
@@ -647,7 +663,6 @@ definitions:
$ref: '#/definitions/tool.tableData'
type: array
type: object
host: 127.0.0.1:9999
info:
contact: {}
license:
@@ -659,17 +674,21 @@ paths:
/api/admin:
get:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 管理员列表
parameters:
- description: 第几页
- default: 1
description: 第几页
in: query
name: page
required: true
type: integer
- description: 每页显示条数
- default: 10
description: 每页显示条数
in: query
name: page_size
type: string
required: true
type: integer
- description: 用户名
in: query
name: username
@@ -693,12 +712,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 管理员列表
tags:
- API.admin
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 新增管理员
parameters:
- description: 用户名
@@ -716,7 +737,7 @@ paths:
name: mobile
required: true
type: string
- description: 密码
- description: MD5后的密码
in: formData
name: password
required: true
@@ -732,6 +753,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 新增管理员
tags:
- API.admin
@@ -757,13 +780,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 删除管理员
tags:
- API.admin
/api/admin/info:
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: 管理员详情
produces:
- application/json
@@ -776,43 +801,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 管理员详情
tags:
- API.admin
/api/admin/login:
post:
consumes:
- multipart/form-data
description: 管理员登录
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: OK
schema:
$ref: '#/definitions/admin.loginResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 管理员登录
tags:
- API.admin
/api/admin/logout:
post:
consumes:
- application/json
- application/x-www-form-urlencoded
description: 管理员登出
produces:
- application/json
@@ -825,13 +822,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 管理员登出
tags:
- API.admin
/api/admin/menu:
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 提交菜单授权
parameters:
- description: Hashid
@@ -855,13 +854,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 提交菜单授权
tags:
- API.admin
/api/admin/menu/:id:
/api/admin/menu/{id}:
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: 菜单授权列表
parameters:
- description: hashId
@@ -880,13 +881,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 菜单授权列表
tags:
- API.admin
/api/admin/modify_password:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 修改密码
parameters:
- description: 旧密码
@@ -910,13 +913,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 修改密码
tags:
- API.admin
/api/admin/modify_personal_info:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 修改个人信息
parameters:
- description: 昵称
@@ -940,13 +945,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 修改个人信息
tags:
- API.admin
/api/admin/offline:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 下线管理员
parameters:
- description: Hashid
@@ -965,6 +972,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 下线管理员
tags:
- API.admin
@@ -990,13 +999,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 重置密码
tags:
- API.admin
/api/admin/used:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 更新管理员为启用/禁用
parameters:
- description: Hashid
@@ -1020,23 +1031,29 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 更新管理员为启用/禁用
tags:
- API.admin
/api/authorized:
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: 调用方列表
parameters:
- description: 第几页
- default: 1
description: 第几页
in: query
name: page
required: true
type: integer
- description: 每页显示条数
- default: 10
description: 每页显示条数
in: query
name: page_size
type: string
required: true
type: integer
- description: 调用方key
in: query
name: business_key
@@ -1064,12 +1081,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 调用方列表
tags:
- API.authorized
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 新增调用方
parameters:
- description: 调用方key
@@ -1098,6 +1117,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 新增调用方
tags:
- API.authorized
@@ -1123,16 +1144,18 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 删除调用方
tags:
- API.authorized
/api/authorized/used:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 更新调用方为启用/禁用
parameters:
- description: Hashid
- description: hashID
in: formData
name: id
required: true
@@ -1153,13 +1176,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 更新调用方为启用/禁用
tags:
- API.authorized
/api/authorized_api:
get:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 调用方接口地址列表
parameters:
- description: hashID
@@ -1178,12 +1203,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 调用方接口地址列表
tags:
- API.authorized
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 授权调用方接口地址
parameters:
- description: HashID
@@ -1212,6 +1239,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 授权调用方接口地址
tags:
- API.authorized
@@ -1237,13 +1266,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 删除调用方接口地址
tags:
- API.authorized
/api/config/email:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 修改邮件配置
parameters:
- description: 邮箱服务器
@@ -1282,23 +1313,29 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 修改邮件配置
tags:
- API.config
/api/cron:
get:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 任务列表
parameters:
- description: 第几页
- default: 1
description: 第几页
in: query
name: page
required: true
type: integer
- description: 每页显示条数
- default: 10
description: 每页显示条数
in: query
name: page_size
type: string
required: true
type: integer
- description: 任务名称
in: query
name: name
@@ -1322,12 +1359,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 任务列表
tags:
- API.cron
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 创建任务
parameters:
- description: 任务名称
@@ -1406,10 +1445,12 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 创建任务
tags:
- API.cron
/api/cron/:id:
/api/cron/{id}:
get:
consumes:
- application/json
@@ -1431,6 +1472,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 获取单条任务详情
tags:
- API.cron
@@ -1455,16 +1498,17 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 手动执行单条任务
tags:
- API.cron
/api/cron/{id}:
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 编辑任务
parameters:
- description: Hashid
- description: hashID
in: formData
name: id
required: true
@@ -1545,16 +1589,18 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 编辑任务
tags:
- API.cron
/api/cron/used:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 更新任务为启用/禁用
parameters:
- description: Hashid
- description: hashID
in: formData
name: id
required: true
@@ -1575,13 +1621,47 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 更新任务为启用/禁用
tags:
- API.cron
/api/login:
post:
consumes:
- application/x-www-form-urlencoded
description: 管理员登录
parameters:
- description: 用户名
in: formData
name: username
required: true
type: string
- description: MD5后的密码
in: formData
name: password
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/admin.loginResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 管理员登录
tags:
- API.admin
/api/menu:
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: 菜单列表
produces:
- application/json
@@ -1594,12 +1674,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 菜单列表
tags:
- API.menu
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 创建/编辑菜单
parameters:
- description: 请求信息
@@ -1619,6 +1701,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 创建/编辑菜单
tags:
- API.menu
@@ -1644,12 +1728,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 删除菜单
tags:
- API.menu
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: 菜单详情
parameters:
- description: hashId
@@ -1668,16 +1754,18 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 菜单详情
tags:
- API.menu
/api/menu/sort:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 更新菜单排序
parameters:
- description: Hashid
- description: hashId
in: formData
name: id
required: true
@@ -1698,16 +1786,18 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 更新菜单排序
tags:
- API.menu
/api/menu/used:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 更新菜单为启用/禁用
parameters:
- description: Hashid
- description: hashId
in: formData
name: id
required: true
@@ -1728,13 +1818,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 更新菜单为启用/禁用
tags:
- API.menu
/api/menu_action:
get:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 功能权限列表
parameters:
- description: hashID
@@ -1753,12 +1845,14 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 功能权限列表
tags:
- API.menu
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 创建功能权限
parameters:
- description: HashID
@@ -1787,6 +1881,8 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 创建功能权限
tags:
- API.menu
@@ -1812,13 +1908,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 删除功能权限
tags:
- API.menu
/api/tool/cache/clear:
patch:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 清空缓存
parameters:
- description: Redis Key
@@ -1837,13 +1935,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 清空缓存
tags:
- API.tool
/api/tool/cache/search:
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 查询缓存
parameters:
- description: Redis Key
@@ -1862,13 +1962,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 查询缓存
tags:
- API.tool
/api/tool/data/dbs:
get:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 查询 DB
produces:
- application/json
@@ -1881,13 +1983,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 查询 DB
tags:
- API.tool
/api/tool/data/mysql:
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 执行 SQL 语句
parameters:
- description: 数据库名称
@@ -1916,13 +2020,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 执行 SQL 语句
tags:
- API.tool
/api/tool/data/tables:
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 查询 Table
parameters:
- description: 数据库名称
@@ -1941,13 +2047,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 查询 Table
tags:
- API.tool
/api/tool/hashids/decode/{id}:
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: HashIds 解密
parameters:
- description: 需解密的密文
@@ -1966,13 +2074,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: HashIds 解密
tags:
- API.tool
/api/tool/hashids/encode/{id}:
get:
consumes:
- application/json
- application/x-www-form-urlencoded
description: HashIds 加密
parameters:
- description: 需加密的数字
@@ -1991,13 +2101,15 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: HashIds 加密
tags:
- API.tool
/api/tool/send_message:
post:
consumes:
- multipart/form-data
- application/x-www-form-urlencoded
description: 发送消息
parameters:
- description: 消息内容
@@ -2016,7 +2128,79 @@ paths:
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginToken: []
summary: 发送消息
tags:
- API.tool
/helper/md5/{str}:
get:
consumes:
- application/x-www-form-urlencoded
description: 加密
parameters:
- description: 需要加密的字符串
in: path
name: str
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/helper.md5Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 加密
tags:
- Helper
/helper/sign:
post:
consumes:
- application/x-www-form-urlencoded
description: 签名
parameters:
- description: 调用方 KEY
in: formData
name: key
required: true
type: string
- description: 请求路径 (不附带 querystring),例如:/api/login
in: formData
name: path
required: true
type: string
- description: 请求方式例如POST
in: formData
name: method
required: true
type: string
- description: 请求参数例如username=tom&password=123456
in: formData
name: params
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/helper.signResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 签名
tags:
- Helper
securityDefinitions:
LoginToken:
in: header
name: token
type: apiKey
swagger: "2.0"

53
internal/alert/alert.go Normal file
View File

@@ -0,0 +1,53 @@
package alert
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/proposal"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/mail"
"go.uber.org/zap"
)
// NotifyHandler 告警通知
func NotifyHandler(logger *zap.Logger) func(msg *proposal.AlertMessage) {
if logger == nil {
panic("logger required")
}
return func(msg *proposal.AlertMessage) {
cfg := configs.Get().Mail
if cfg.Host == "" || cfg.Port == 0 || cfg.User == "" || cfg.Pass == "" || cfg.To == "" {
logger.Error("Mail config error")
return
}
subject, body, err := newHTMLEmail(
msg.Method,
msg.HOST,
msg.URI,
msg.TraceID,
msg.ErrorMessage,
msg.ErrorStack,
)
if err != nil {
logger.Error("email template error", zap.Error(err))
return
}
options := &mail.Options{
MailHost: cfg.Host,
MailPort: cfg.Port,
MailUser: cfg.User,
MailPass: cfg.Pass,
MailTo: cfg.To,
Subject: subject,
Body: body,
}
if err := mail.Send(options); err != nil {
logger.Error("发送告警通知邮件失败", zap.Error(errors.WithStack(err)))
}
return
}
}

View File

@@ -1,6 +1,54 @@
package templates
package alert
const PanicMail = `
import (
"bytes"
"fmt"
"html/template"
"time"
)
// NewHTMLEmail 告警邮件模板
func newHTMLEmail(method, host, uri, id string, msg interface{}, stack string) (subject string, body string, err error) {
mailData := &struct {
URL string
ID string
Msg string
Stack string
Year int
}{
URL: fmt.Sprintf("%s %s%s", method, host, uri),
ID: id,
Msg: fmt.Sprintf("%+v", msg),
Stack: stack,
Year: time.Now().Year(),
}
// subject 邮件主题
subject = fmt.Sprintf("[系统告警]-%s", uri)
// body 邮件内容
body, err = getEmailHTMLContent(mailTemplate, mailData)
return
}
// getEmailHTMLContent 获取邮件模板
func getEmailHTMLContent(mailTpl string, mailData interface{}) (string, error) {
tpl, err := template.New("email tpl").Parse(mailTpl)
if err != nil {
return "", err
}
buffer := new(bytes.Buffer)
err = tpl.Execute(buffer, mailData)
if err != nil {
return "", err
}
return buffer.String(), nil
}
const mailTemplate = `
<!DOCTYPE html>
<html>
@@ -27,7 +75,7 @@ const PanicMail = `
<!-- Logo -->
<tr>
<td style="padding: 25px 0; text-align: center;">
系统异常
系统告警
</td>
</tr>

View File

@@ -7,14 +7,13 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createRequest struct {
Username string `form:"username" binding:"required"` // 用户名
Nickname string `form:"nickname" binding:"required"` // 昵称
Mobile string `form:"mobile" binding:"required"` // 手机号
Password string `form:"password" binding:"required"` // 密码
Password string `form:"password" binding:"required"` // MD5后的密码
}
type createResponse struct {
@@ -25,24 +24,25 @@ type createResponse struct {
// @Summary 新增管理员
// @Description 新增管理员
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param username formData string true "用户名"
// @Param nickname formData string true "昵称"
// @Param mobile formData string true "手机号"
// @Param password formData string true "密码"
// @Param password formData string true "MD5后的密码"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin [post]
// @Security LoginToken
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
@@ -55,10 +55,10 @@ func (h *handler) Create() core.HandlerFunc {
id, err := h.adminService.Create(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminCreateError,
code.Text(code.AdminCreateError)).WithErr(err),
code.Text(code.AdminCreateError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createAdminMenuRequest struct {
@@ -22,32 +21,33 @@ type createAdminMenuResponse struct {
// @Summary 提交菜单授权
// @Description 提交菜单授权
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param actions formData string true "功能权限ID,多个用,分割"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/menu [post]
// @Security LoginToken
func (h *handler) CreateAdminMenu() core.HandlerFunc {
return func(c core.Context) {
req := new(createAdminMenuRequest)
res := new(createAdminMenuResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -58,10 +58,10 @@ func (h *handler) CreateAdminMenu() core.HandlerFunc {
err = h.adminService.CreateMenu(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminMenuCreateError,
code.Text(code.AdminMenuCreateError)).WithErr(err),
code.Text(code.AdminMenuCreateError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
@@ -26,25 +25,26 @@ type deleteResponse struct {
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/{id} [delete]
// @Security LoginToken
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) Delete() core.HandlerFunc {
err = h.adminService.Delete(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminDeleteError,
code.Text(code.AdminDeleteError)).WithErr(err),
code.Text(code.AdminDeleteError)).WithError(err),
)
return
}

View File

@@ -10,9 +10,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type detailResponse struct {
@@ -26,35 +23,36 @@ type detailResponse struct {
// @Summary 管理员详情
// @Description 管理员详情
// @Tags API.admin
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/info [get]
// @Security LoginToken
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
return func(ctx core.Context) {
res := new(detailResponse)
searchOneData := new(admin.SearchOneData)
searchOneData.Id = cast.ToInt32(c.UserID())
searchOneData.Id = ctx.SessionUserInfo().UserID
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
info, err := h.adminService.Detail(ctx, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminDetailError,
code.Text(code.AdminDetailError)).WithErr(err),
code.Text(code.AdminDetailError)).WithError(err),
)
return
}
menuCacheData, err := h.cache.Get(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(searchOneData.Id)+":menu", redis.WithTrace(c.Trace()))
menuCacheData, err := h.cache.Get(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(searchOneData.Id)+":menu", redis.WithTrace(ctx.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminDetailError,
code.Text(code.AdminDetailError)).WithErr(err),
code.Text(code.AdminDetailError)).WithError(err),
)
return
}
@@ -62,10 +60,10 @@ func (h *handler) Detail() core.HandlerFunc {
var menuData []admin.ListMyMenuData
err = json.Unmarshal([]byte(menuCacheData), &menuData)
if err != nil {
c.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminDetailError,
code.Text(code.AdminDetailError)).WithErr(err),
code.Text(code.AdminDetailError)).WithError(err),
)
return
}
@@ -74,6 +72,6 @@ func (h *handler) Detail() core.HandlerFunc {
res.Nickname = info.Nickname
res.Mobile = info.Mobile
res.Menu = menuData
c.Payload(res)
ctx.Payload(res)
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/timeutil"
"github.com/spf13/cast"
@@ -49,25 +48,26 @@ type listResponse struct {
// @Summary 管理员列表
// @Description 管理员列表
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param page query int false "第几页"
// @Param page_size query string false "每页显示条数"
// @Param page query int true "第几页" default(1)
// @Param page_size query int true "每页显示条数" default(10)
// @Param username query string false "用户名"
// @Param nickname query string false "昵称"
// @Param mobile query string false "手机号"
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin [get]
// @Security LoginToken
func (h *handler) List() core.HandlerFunc {
return func(c core.Context) {
req := new(listRequest)
res := new(listResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -91,20 +91,20 @@ func (h *handler) List() core.HandlerFunc {
resListData, err := h.adminService.PageList(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminListError,
code.Text(code.AdminListError)).WithErr(err),
code.Text(code.AdminListError)).WithError(err),
)
return
}
resCountData, err := h.adminService.PageListCount(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminListError,
code.Text(code.AdminListError)).WithErr(err),
code.Text(code.AdminListError)).WithError(err),
)
return
}
@@ -116,10 +116,10 @@ func (h *handler) List() core.HandlerFunc {
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type listAdminMenuRequest struct {
@@ -22,31 +21,32 @@ type listAdminMenuResponse struct {
// @Summary 菜单授权列表
// @Description 菜单授权列表
// @Tags API.admin
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} listAdminMenuResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/menu/:id [get]
// @Router /api/admin/menu/{id} [get]
// @Security LoginToken
func (h *handler) ListAdminMenu() core.HandlerFunc {
return func(c core.Context) {
req := new(listAdminMenuRequest)
res := new(listAdminMenuResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -57,10 +57,10 @@ func (h *handler) ListAdminMenu() core.HandlerFunc {
info, err := h.adminService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminMenuListError,
code.Text(code.AdminMenuListError)).WithErr(err),
code.Text(code.AdminMenuListError)).WithError(err),
)
return
}
@@ -72,10 +72,10 @@ func (h *handler) ListAdminMenu() core.HandlerFunc {
listData, err := h.adminService.ListMenu(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminMenuListError,
code.Text(code.AdminMenuListError)).WithErr(err),
code.Text(code.AdminMenuListError)).WithError(err),
)
return
}

View File

@@ -3,15 +3,14 @@ package admin
import (
"encoding/json"
"net/http"
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/internal/proposal"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
@@ -28,22 +27,23 @@ type loginResponse struct {
// @Summary 管理员登录
// @Description 管理员登录
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param username formData string true "用户名"
// @Param password formData string true "密码"
// @Param password formData string true "MD5后的密码"
// @Success 200 {object} loginResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/login [post]
// @Router /api/login [post]
// @Security LoginToken
func (h *handler) Login() core.HandlerFunc {
return func(c core.Context) {
req := new(loginRequest)
res := new(loginResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -55,47 +55,38 @@ func (h *handler) Login() core.HandlerFunc {
info, err := h.adminService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.Text(code.AdminLoginError)).WithError(err),
)
return
}
if info == nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(errors.New("未查询出符合条件的用户")),
code.Text(code.AdminLoginError)).WithError(errors.New("未查询出符合条件的用户")),
)
return
}
token := password.GenerateLoginToken(info.Id)
adminCacheData := &struct {
Id int32 `json:"id"` // 主键ID
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Mobile string `json:"mobile"` // 手机号
}{
Id: info.Id,
Username: info.Username,
Nickname: info.Nickname,
Mobile: info.Mobile,
// 用户信息
sessionUserInfo := &proposal.SessionUserInfo{
UserID: info.Id,
UserName: info.Username,
}
// 用户信息
adminJsonInfo, _ := json.Marshal(adminCacheData)
// 将用户信息记录到 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token, string(adminJsonInfo), time.Hour*24, redis.WithTrace(c.Trace()))
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token, string(sessionUserInfo.Marshal()), configs.LoginSessionTTL, redis.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.Text(code.AdminLoginError)).WithError(err),
)
return
}
@@ -104,10 +95,10 @@ func (h *handler) Login() core.HandlerFunc {
searchMenuData.AdminId = info.Id
menu, err := h.adminService.MyMenu(c, searchMenuData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.Text(code.AdminLoginError)).WithError(err),
)
return
}
@@ -116,12 +107,12 @@ func (h *handler) Login() core.HandlerFunc {
menuJsonInfo, _ := json.Marshal(menu)
// 将菜单栏信息记录到 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token+":menu", string(menuJsonInfo), time.Hour*24, redis.WithTrace(c.Trace()))
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token+":menu", string(menuJsonInfo), configs.LoginSessionTTL, redis.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.Text(code.AdminLoginError)).WithError(err),
)
return
}
@@ -130,10 +121,10 @@ func (h *handler) Login() core.HandlerFunc {
searchActionData.AdminId = info.Id
action, err := h.adminService.MyAction(c, searchActionData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.Text(code.AdminLoginError)).WithError(err),
)
return
}
@@ -142,12 +133,12 @@ func (h *handler) Login() core.HandlerFunc {
actionJsonInfo, _ := json.Marshal(action)
// 将可访问接口信息记录到 Redis 中
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token+":action", string(actionJsonInfo), time.Hour*24, redis.WithTrace(c.Trace()))
err = h.cache.Set(configs.RedisKeyPrefixLoginUser+token+":action", string(actionJsonInfo), configs.LoginSessionTTL, redis.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLoginError,
code.Text(code.AdminLoginError)).WithErr(err),
code.Text(code.AdminLoginError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
@@ -19,21 +18,22 @@ type logoutResponse struct {
// @Summary 管理员登出
// @Description 管理员登出
// @Tags API.admin
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Success 200 {object} logoutResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/logout [post]
// @Security LoginToken
func (h *handler) Logout() core.HandlerFunc {
return func(c core.Context) {
res := new(logoutResponse)
res.Username = c.UserName()
res.Username = c.SessionUserInfo().UserName
if !h.cache.Del(configs.RedisKeyPrefixLoginUser+c.GetHeader(configs.HeaderLoginToken), redis.WithTrace(c.Trace())) {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminLogOutError,
code.Text(code.AdminLogOutError)).WithErr(errors.New("cache del err")),
code.Text(code.AdminLogOutError)).WithError(errors.New("cache del err")),
)
return
}

View File

@@ -7,9 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type modifyPasswordRequest struct {
@@ -25,53 +22,52 @@ type modifyPasswordResponse struct {
// @Summary 修改密码
// @Description 修改密码
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param old_password formData string true "旧密码"
// @Param new_password formData string true "新密码"
// @Success 200 {object} modifyPasswordResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/modify_password [patch]
// @Security LoginToken
func (h *handler) ModifyPassword() core.HandlerFunc {
return func(c core.Context) {
return func(ctx core.Context) {
req := new(modifyPasswordRequest)
res := new(modifyPasswordResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
userId := cast.ToInt32(c.UserID())
searchOneData := new(admin.SearchOneData)
searchOneData.Id = userId
searchOneData.Id = ctx.SessionUserInfo().UserID
searchOneData.Password = password.GeneratePassword(req.OldPassword)
searchOneData.IsUsed = 1
info, err := h.adminService.Detail(c, searchOneData)
info, err := h.adminService.Detail(ctx, searchOneData)
if err != nil || info == nil {
c.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminModifyPasswordError,
code.Text(code.AdminModifyPasswordError)).WithErr(err),
code.Text(code.AdminModifyPasswordError)).WithError(err),
)
return
}
if err := h.adminService.ModifyPassword(c, userId, req.NewPassword); err != nil {
c.AbortWithError(errno.NewError(
if err := h.adminService.ModifyPassword(ctx, ctx.SessionUserInfo().UserID, req.NewPassword); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminModifyPasswordError,
code.Text(code.AdminModifyPasswordError)).WithErr(err),
code.Text(code.AdminModifyPasswordError)).WithError(err),
)
return
}
res.Username = c.UserName()
c.Payload(res)
res.Username = ctx.SessionUserInfo().UserName
ctx.Payload(res)
}
}

View File

@@ -6,9 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
type modifyPersonalInfoRequest struct {
@@ -24,42 +21,41 @@ type modifyPersonalInfoResponse struct {
// @Summary 修改个人信息
// @Description 修改个人信息
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param nickname formData string true "昵称"
// @Param mobile formData string true "手机号"
// @Success 200 {object} modifyPersonalInfoResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/modify_personal_info [patch]
// @Security LoginToken
func (h *handler) ModifyPersonalInfo() core.HandlerFunc {
return func(c core.Context) {
return func(ctx core.Context) {
req := new(modifyPersonalInfoRequest)
res := new(modifyPersonalInfoResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
userId := cast.ToInt32(c.UserID())
modifyData := new(admin.ModifyData)
modifyData.Nickname = req.Nickname
modifyData.Mobile = req.Mobile
if err := h.adminService.ModifyPersonalInfo(c, userId, modifyData); err != nil {
c.AbortWithError(errno.NewError(
if err := h.adminService.ModifyPersonalInfo(ctx, ctx.SessionUserInfo().UserID, modifyData); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminModifyPersonalInfoError,
code.Text(code.AdminModifyPersonalInfoError)).WithErr(err),
code.Text(code.AdminModifyPersonalInfoError)).WithError(err),
)
return
}
res.Username = c.UserName()
c.Payload(res)
res.Username = ctx.SessionUserInfo().UserName
ctx.Payload(res)
}
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/password"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type offlineRequest struct {
@@ -23,31 +22,32 @@ type offlineResponse struct {
// @Summary 下线管理员
// @Description 下线管理员
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Success 200 {object} offlineResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/offline [patch]
// @Security LoginToken
func (h *handler) Offline() core.HandlerFunc {
return func(c core.Context) {
req := new(offlineRequest)
res := new(offlineResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -56,7 +56,7 @@ func (h *handler) Offline() core.HandlerFunc {
b := h.cache.Del(configs.RedisKeyPrefixLoginUser+password.GenerateLoginToken(id), redis.WithTrace(c.Trace()))
if !b {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminOfflineError,
code.Text(code.AdminOfflineError)),

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type resetPasswordRequest struct {
@@ -26,25 +25,26 @@ type resetPasswordResponse struct {
// @Success 200 {object} resetPasswordResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/reset_password/{id} [patch]
// @Security LoginToken
func (h *handler) ResetPassword() core.HandlerFunc {
return func(c core.Context) {
req := new(resetPasswordRequest)
res := new(resetPasswordResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) ResetPassword() core.HandlerFunc {
err = h.adminService.ResetPassword(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminResetPasswordError,
code.Text(code.AdminResetPasswordError)).WithErr(err),
code.Text(code.AdminResetPasswordError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateUsedRequest struct {
@@ -21,32 +20,33 @@ type updateUsedResponse struct {
// @Summary 更新管理员为启用/禁用
// @Description 更新管理员为启用/禁用
// @Tags API.admin
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/used [patch]
// @Security LoginToken
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -55,10 +55,10 @@ func (h *handler) UpdateUsed() core.HandlerFunc {
err = h.adminService.UpdateUsed(c, id, req.Used)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminUpdateError,
code.Text(code.AdminUpdateError)).WithErr(err),
code.Text(code.AdminUpdateError)).WithError(err),
)
return
}

View File

@@ -18,7 +18,7 @@ type Handler interface {
// Login 管理员登录
// @Tags API.admin
// @Router /api/admin/login [post]
// @Router /api/login [post]
Login() core.HandlerFunc
// Logout 管理员登出
@@ -78,7 +78,7 @@ type Handler interface {
// ListAdminMenu 菜单授权列表
// @Tags API.admin
// @Router /api/admin/menu/:id [get]
// @Router /api/admin/menu/{id} [get]
ListAdminMenu() core.HandlerFunc
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/authorized"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createRequest struct {
@@ -23,7 +22,7 @@ type createResponse struct {
// @Summary 新增调用方
// @Description 新增调用方
// @Tags API.authorized
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param business_key formData string true "调用方key"
// @Param business_developer formData string true "调用方对接人"
@@ -31,15 +30,16 @@ type createResponse struct {
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized [post]
// @Security LoginToken
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -51,10 +51,10 @@ func (h *handler) Create() core.HandlerFunc {
id, err := h.authorizedService.Create(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedCreateError,
code.Text(code.AuthorizedCreateError)).WithErr(err),
code.Text(code.AuthorizedCreateError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/authorized"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createAPIRequest struct {
@@ -23,7 +22,7 @@ type createAPIResponse struct {
// @Summary 授权调用方接口地址
// @Description 授权调用方接口地址
// @Tags API.authorized
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "HashID"
// @Param method formData string true "请求方法"
@@ -31,25 +30,26 @@ type createAPIResponse struct {
// @Success 200 {object} createAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api [post]
// @Security LoginToken
func (h *handler) CreateAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(createAPIRequest)
res := new(createAPIResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -59,10 +59,10 @@ func (h *handler) CreateAPI() core.HandlerFunc {
// 通过 id 查询出 business_key
authorizedInfo, err := h.authorizedService.Detail(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedDetailError,
code.Text(code.AuthorizedDetailError)).WithErr(err),
code.Text(code.AuthorizedDetailError)).WithError(err),
)
return
}
@@ -74,10 +74,10 @@ func (h *handler) CreateAPI() core.HandlerFunc {
createId, err := h.authorizedService.CreateAPI(c, createAPIData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedCreateAPIError,
code.Text(code.AuthorizedCreateAPIError)).WithErr(err),
code.Text(code.AuthorizedCreateAPIError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
@@ -26,25 +25,26 @@ type deleteResponse struct {
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized/{id} [delete]
// @Security LoginToken
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) Delete() core.HandlerFunc {
err = h.authorizedService.Delete(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedDeleteError,
code.Text(code.AuthorizedDeleteError)).WithErr(err),
code.Text(code.AuthorizedDeleteError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteAPIRequest struct {
@@ -26,25 +25,26 @@ type deleteAPIResponse struct {
// @Success 200 {object} deleteAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api/{id} [delete]
// @Security LoginToken
func (h *handler) DeleteAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteAPIRequest)
res := new(deleteAPIResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) DeleteAPI() core.HandlerFunc {
err = h.authorizedService.DeleteAPI(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedDeleteAPIError,
code.Text(code.AuthorizedDeleteAPIError)).WithErr(err),
code.Text(code.AuthorizedDeleteAPIError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/authorized"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/timeutil"
"github.com/spf13/cast"
@@ -48,10 +47,10 @@ type listResponse struct {
// @Summary 调用方列表
// @Description 调用方列表
// @Tags API.authorized
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param page query int false "第几页"
// @Param page_size query string false "每页显示条数"
// @Param page query int true "第几页" default(1)
// @Param page_size query int true "每页显示条数" default(10)
// @Param business_key query string false "调用方key"
// @Param business_secret query string false "调用方secret"
// @Param business_developer query string false "调用方对接人"
@@ -59,15 +58,16 @@ type listResponse struct {
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized [get]
// @Security LoginToken
func (h *handler) List() core.HandlerFunc {
return func(c core.Context) {
req := new(listRequest)
res := new(listResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -91,20 +91,20 @@ func (h *handler) List() core.HandlerFunc {
resListData, err := h.authorizedService.PageList(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedListError,
code.Text(code.AuthorizedListError)).WithErr(err),
code.Text(code.AuthorizedListError)).WithError(err),
)
return
}
resCountData, err := h.authorizedService.PageListCount(c, searchData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedListError,
code.Text(code.AuthorizedListError)).WithErr(err),
code.Text(code.AuthorizedListError)).WithError(err),
)
return
}
@@ -116,10 +116,10 @@ func (h *handler) List() core.HandlerFunc {
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/authorized"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -31,31 +30,32 @@ type listAPIResponse struct {
// @Summary 调用方接口地址列表
// @Description 调用方接口地址列表
// @Tags API.authorized
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id query string true "hashID"
// @Success 200 {object} listAPIResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized_api [get]
// @Security LoginToken
func (h *handler) ListAPI() core.HandlerFunc {
return func(c core.Context) {
req := new(listAPIRequest)
res := new(listAPIResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -65,10 +65,10 @@ func (h *handler) ListAPI() core.HandlerFunc {
// 通过 id 查询出 business_key
authorizedInfo, err := h.authorizedService.Detail(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedDetailError,
code.Text(code.AuthorizedDetailError)).WithErr(err),
code.Text(code.AuthorizedDetailError)).WithError(err),
)
return
}
@@ -80,10 +80,10 @@ func (h *handler) ListAPI() core.HandlerFunc {
resListData, err := h.authorizedService.ListAPI(c, searchAPIData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedListAPIError,
code.Text(code.AuthorizedListAPIError)).WithErr(err),
code.Text(code.AuthorizedListAPIError)).WithError(err),
)
return
}
@@ -93,10 +93,10 @@ func (h *handler) ListAPI() core.HandlerFunc {
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateUsedRequest struct {
@@ -21,32 +20,33 @@ type updateUsedResponse struct {
// @Summary 更新调用方为启用/禁用
// @Description 更新调用方为启用/禁用
// @Tags API.authorized
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param id formData string true "hashID"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/authorized/used [patch]
// @Security LoginToken
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -55,10 +55,10 @@ func (h *handler) UpdateUsed() core.HandlerFunc {
err = h.authorizedService.UpdateUsed(c, id, req.Used)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedUpdateError,
code.Text(code.AuthorizedUpdateError)).WithErr(err),
code.Text(code.AuthorizedUpdateError)).WithError(err),
)
return
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/mail"
"github.com/spf13/cast"
@@ -31,7 +30,7 @@ type emailResponse struct {
// @Summary 修改邮件配置
// @Description 修改邮件配置
// @Tags API.config
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param host formData string true "邮箱服务器"
// @Param port formData string true "端口"
@@ -41,15 +40,16 @@ type emailResponse struct {
// @Success 200 {object} emailResponse
// @Failure 400 {object} code.Failure
// @Router /api/config/email [patch]
// @Security LoginToken
func (h *handler) Email() core.HandlerFunc {
return func(c core.Context) {
req := new(emailRequest)
res := new(emailResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -64,10 +64,10 @@ func (h *handler) Email() core.HandlerFunc {
Body: fmt.Sprintf("%s[%s] 已添加您为系统告警通知人。", configs.ProjectName, env.Active().Value()),
}
if err := mail.Send(options); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.SendEmailError,
code.Text(code.SendEmailError)+err.Error()).WithErr(err),
code.Text(code.SendEmailError)+err.Error()).WithError(err),
)
return
}
@@ -80,10 +80,10 @@ func (h *handler) Email() core.HandlerFunc {
err := viper.WriteConfig()
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.WriteConfigError,
code.Text(code.WriteConfigError)).WithErr(err),
code.Text(code.WriteConfigError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/internal/services/cron"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createRequest struct {
@@ -35,7 +34,7 @@ type createResponse struct {
// @Summary 创建任务
// @Description 创建任务
// @Tags API.cron
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param name formData string true "任务名称"
// @Param spec formData string true "crontab 表达式"
@@ -54,15 +53,16 @@ type createResponse struct {
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/cron [post]
// @Security LoginToken
func (h *handler) Create() core.HandlerFunc {
return func(ctx core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
@@ -85,10 +85,10 @@ func (h *handler) Create() core.HandlerFunc {
id, err := h.cronService.Create(ctx, createData)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.CronCreateError,
code.Text(code.CronCreateError)).WithErr(err),
code.Text(code.CronCreateError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/internal/services/cron"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -42,26 +41,27 @@ type detailResponse struct {
// @Param id path string true "hashId"
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Router /api/cron/:id [get]
// @Router /api/cron/{id} [get]
// @Security LoginToken
func (h *handler) Detail() core.HandlerFunc {
return func(ctx core.Context) {
req := new(detailRequest)
res := new(detailResponse)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -71,10 +71,10 @@ func (h *handler) Detail() core.HandlerFunc {
info, err := h.cronService.Detail(ctx, searchOneData)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.CronDetailError,
code.Text(code.CronDetailError)).WithErr(err),
code.Text(code.CronDetailError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -28,36 +27,37 @@ type executeResponse struct {
// @Param id path string true "hashId"
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Router /api/cron/:id [patch]
// @Router /api/cron/{id} [patch]
// @Security LoginToken
func (h *handler) Execute() core.HandlerFunc {
return func(ctx core.Context) {
req := new(executeRequest)
res := new(executeResponse)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
err = h.cronService.Execute(ctx, cast.ToInt32(ids[0]))
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.CronExecuteError,
code.Text(code.CronExecuteError)).WithErr(err),
code.Text(code.CronExecuteError)).WithError(err),
)
return
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql/cron_task"
"github.com/xinliangnote/go-gin-api/internal/services/cron"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/timeutil"
"github.com/spf13/cast"
@@ -58,25 +57,26 @@ type listResponse struct {
// @Summary 任务列表
// @Description 任务列表
// @Tags API.cron
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param page query int false "第几页"
// @Param page_size query string false "每页显示条数"
// @Param page query int true "第几页" default(1)
// @Param page_size query int true "每页显示条数" default(10)
// @Param name query string false "任务名称"
// @Param protocol query int false "执行方式 1:shell 2:http"
// @Param is_used query int false "是否启用 1:是 -1:否"
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/cron [get]
// @Security LoginToken
func (h *handler) List() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listRequest)
res := new(listResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
@@ -100,20 +100,20 @@ func (h *handler) List() core.HandlerFunc {
resListData, err := h.cronService.PageList(ctx, searchData)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.CronListError,
code.Text(code.CronListError)).WithErr(err),
code.Text(code.CronListError)).WithError(err),
)
return
}
resCountData, err := h.cronService.PageListCount(ctx, searchData)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.CronListError,
code.Text(code.CronListError)).WithErr(err),
code.Text(code.CronListError)).WithError(err),
)
return
}
@@ -126,10 +126,10 @@ func (h *handler) List() core.HandlerFunc {
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/internal/services/cron"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type modifyRequest struct {
@@ -36,9 +35,9 @@ type modifyResponse struct {
// @Summary 编辑任务
// @Description 编辑任务
// @Tags API.cron
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param id formData string true "hashID"
// @Param name formData string true "任务名称"
// @Param spec formData string true "crontab 表达式"
// @Param command formData string true "执行命令"
@@ -56,25 +55,26 @@ type modifyResponse struct {
// @Success 200 {object} modifyResponse
// @Failure 400 {object} code.Failure
// @Router /api/cron/{id} [post]
// @Security LoginToken
func (h *handler) Modify() core.HandlerFunc {
return func(ctx core.Context) {
req := new(modifyRequest)
res := new(modifyResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -98,10 +98,10 @@ func (h *handler) Modify() core.HandlerFunc {
modifyData.IsUsed = req.IsUsed
if err := h.cronService.Modify(ctx, id, modifyData); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.CronUpdateError,
code.Text(code.CronUpdateError)).WithErr(err),
code.Text(code.CronUpdateError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateUsedRequest struct {
@@ -22,32 +21,33 @@ type updateUsedResponse struct {
// @Summary 更新任务为启用/禁用
// @Description 更新任务为启用/禁用
// @Tags API.cron
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param id formData string true "hashID"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/cron/used [patch]
// @Security LoginToken
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(ctx core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -56,10 +56,10 @@ func (h *handler) UpdateUsed() core.HandlerFunc {
err = h.cronService.UpdateUsed(ctx, id, req.Used)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AdminUpdateError,
code.Text(code.AdminUpdateError)).WithErr(err),
code.Text(code.AdminUpdateError)).WithError(err),
)
return
}

View File

@@ -39,12 +39,12 @@ type Handler interface {
// Detail 获取单条任务详情
// @Tags API.cron
// @Router /api/cron/:id [get]
// @Router /api/cron/{id} [get]
Detail() core.HandlerFunc
// Execute 手动执行任务
// @Tags API.cron
// @Router /api/cron/:id [patch]
// @Router /api/cron/{id} [patch]
Execute() core.HandlerFunc
}

49
internal/api/helper/func_md5.go Executable file
View File

@@ -0,0 +1,49 @@
package helper
import (
"crypto/md5"
"encoding/hex"
"net/http"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type md5Request struct {
Str string `uri:"str" binding:"required"` // 需要加密的字符串
}
type md5Response struct {
Md5Str string `json:"md5_str"` // MD5后的字符串
}
// Md5 加密
// @Summary 加密
// @Description 加密
// @Tags Helper
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param str path string true "需要加密的字符串"
// @Success 200 {object} md5Response
// @Failure 400 {object} code.Failure
// @Router /helper/md5/{str} [get]
func (h *handler) Md5() core.HandlerFunc {
return func(ctx core.Context) {
req := new(md5Request)
res := new(md5Response)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithError(err),
)
return
}
m := md5.New()
m.Write([]byte(req.Str))
res.Md5Str = hex.EncodeToString(m.Sum(nil))
ctx.Payload(res)
}
}

100
internal/api/helper/func_sign.go Executable file
View File

@@ -0,0 +1,100 @@
package helper
import (
"fmt"
"net/http"
"net/url"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/signature"
)
type signRequest struct {
Key string `form:"key" binding:"required"` // 调用方 KEY
Path string `form:"path" binding:"required"` // 请求路径 (不附带 querystring),例如:/api/login
Method string `form:"method" binding:"required"` // 请求方式例如POST
Params string `form:"params" binding:"required"` // 请求参数例如username=tom&password=123456
}
type signResponse struct {
Authorization string `json:"authorization"` // 签名信息-Authorization
AuthorizationDate string `json:"authorization_date"` // 签名信息-Authorization-Date
}
// Sign 签名
// @Summary 签名
// @Description 签名
// @Tags Helper
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param key formData string true "调用方 KEY"
// @Param path formData string true "请求路径 (不附带 querystring),例如:/api/login"
// @Param method formData string true "请求方式例如POST"
// @Param params formData string true "请求参数例如username=tom&password=123456"
// @Success 200 {object} signResponse
// @Failure 400 {object} code.Failure
// @Router /helper/sign [post]
func (h *handler) Sign() core.HandlerFunc {
return func(ctx core.Context) {
req := new(signRequest)
res := new(signResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithError(err),
)
return
}
authorizedInfo, err := h.authorizedService.DetailByKey(ctx, req.Key)
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithError(err),
)
return
}
if authorizedInfo.IsUsed == -1 {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithError(errors.New(req.Key + " 已被禁止调用")),
)
return
}
fmt.Println(req.Params)
params, err := url.ParseQuery(req.Params)
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
"params 传递格式不正确"),
)
return
}
sign := signature.New(req.Key, authorizedInfo.Secret, configs.HeaderSignTokenTimeout)
authorized, date, err := sign.Generate(req.Path, req.Method, params)
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
"sign 生成失败"),
)
return
}
res.Authorization = authorized
res.AuthorizationDate = date
ctx.Payload(res)
}
}

View File

@@ -0,0 +1,42 @@
package helper
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/internal/services/authorized"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
i()
// Md5 加密
// @Tags Helper
// @Router /helper/md5/{str} [get]
Md5() core.HandlerFunc
// Sign 签名
// @Tags Helper
// @Router /helper/sign [post]
Sign() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
db mysql.Repo
authorizedService authorized.Service
}
func New(logger *zap.Logger, db mysql.Repo, cache redis.Repo) Handler {
return &handler{
logger: logger,
db: db,
authorizedService: authorized.New(db, cache),
}
}
func (h *handler) i() {}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/menu"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -28,21 +27,22 @@ type createResponse struct {
// @Summary 创建/编辑菜单
// @Description 创建/编辑菜单
// @Tags API.menu
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu [post]
// @Security LoginToken
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -50,10 +50,10 @@ func (h *handler) Create() core.HandlerFunc {
if req.Id != "" { // 编辑功能
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -67,10 +67,10 @@ func (h *handler) Create() core.HandlerFunc {
err = h.menuService.Modify(c, id, updateData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuUpdateError,
code.Text(code.MenuUpdateError)).WithErr(err),
code.Text(code.MenuUpdateError)).WithError(err),
)
return
}
@@ -97,10 +97,10 @@ func (h *handler) Create() core.HandlerFunc {
id, err := h.menuService.Create(c, createData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuCreateError,
code.Text(code.MenuCreateError)).WithErr(err),
code.Text(code.MenuCreateError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/menu"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type createActionRequest struct {
@@ -23,7 +22,7 @@ type createActionResponse struct {
// @Summary 创建功能权限
// @Description 创建功能权限
// @Tags API.menu
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "HashID"
// @Param method formData string true "请求方法"
@@ -31,25 +30,26 @@ type createActionResponse struct {
// @Success 200 {object} createActionResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu_action [post]
// @Security LoginToken
func (h *handler) CreateAction() core.HandlerFunc {
return func(c core.Context) {
req := new(createActionRequest)
res := new(createActionResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -60,10 +60,10 @@ func (h *handler) CreateAction() core.HandlerFunc {
searchOneData.Id = id
menuInfo, err := h.menuService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuDetailError,
code.Text(code.MenuDetailError)).WithErr(err),
code.Text(code.MenuDetailError)).WithError(err),
)
return
}
@@ -75,10 +75,10 @@ func (h *handler) CreateAction() core.HandlerFunc {
createId, err := h.menuService.CreateAction(c, createActionData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuCreateActionError,
code.Text(code.MenuCreateActionError)).WithErr(err),
code.Text(code.MenuCreateActionError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
@@ -26,25 +25,26 @@ type deleteResponse struct {
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu/{id} [delete]
// @Security LoginToken
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) Delete() core.HandlerFunc {
err = h.menuService.Delete(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuDeleteError,
code.Text(code.MenuDeleteError)).WithErr(err),
code.Text(code.MenuDeleteError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteActionRequest struct {
@@ -26,25 +25,26 @@ type deleteActionResponse struct {
// @Success 200 {object} deleteActionResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu_action/{id} [delete]
// @Security LoginToken
func (h *handler) DeleteAction() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteActionRequest)
res := new(deleteActionResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) DeleteAction() core.HandlerFunc {
err = h.menuService.DeleteAction(c, id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuDeleteActionError,
code.Text(code.MenuDeleteActionError)).WithErr(err),
code.Text(code.MenuDeleteActionError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/menu"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type detailRequest struct {
@@ -25,31 +24,32 @@ type detailResponse struct {
// @Summary 菜单详情
// @Description 菜单详情
// @Tags API.menu
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id path string true "hashId"
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu/{id} [get]
// @Security LoginToken
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
req := new(detailRequest)
res := new(detailResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -61,10 +61,10 @@ func (h *handler) Detail() core.HandlerFunc {
info, err := h.menuService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuDetailError,
code.Text(code.MenuDetailError)).WithErr(err),
code.Text(code.MenuDetailError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/menu"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -30,20 +29,21 @@ type listResponse struct {
// @Summary 菜单列表
// @Description 菜单列表
// @Tags API.menu
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Success 200 {object} listResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu [get]
// @Security LoginToken
func (h *handler) List() core.HandlerFunc {
return func(c core.Context) {
res := new(listResponse)
resListData, err := h.menuService.List(c, new(menu.SearchData))
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuListError,
code.Text(code.MenuListError)).WithErr(err),
code.Text(code.MenuListError)).WithError(err),
)
return
}
@@ -53,10 +53,10 @@ func (h *handler) List() core.HandlerFunc {
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/services/menu"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -31,31 +30,32 @@ type listActionResponse struct {
// @Summary 功能权限列表
// @Description 功能权限列表
// @Tags API.menu
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id query string true "hashID"
// @Success 200 {object} listActionResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu_action [get]
// @Security LoginToken
func (h *handler) ListAction() core.HandlerFunc {
return func(c core.Context) {
req := new(listActionRequest)
res := new(listActionResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -67,10 +67,10 @@ func (h *handler) ListAction() core.HandlerFunc {
menuInfo, err := h.menuService.Detail(c, searchOneData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuDetailError,
code.Text(code.MenuDetailError)).WithErr(err),
code.Text(code.MenuDetailError)).WithError(err),
)
return
}
@@ -82,10 +82,10 @@ func (h *handler) ListAction() core.HandlerFunc {
resListData, err := h.menuService.ListAction(c, searchListData)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizedListAPIError,
code.Text(code.AuthorizedListAPIError)).WithErr(err),
code.Text(code.AuthorizedListAPIError)).WithError(err),
)
return
}
@@ -95,10 +95,10 @@ func (h *handler) ListAction() core.HandlerFunc {
for k, v := range resListData {
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateSortRequest struct {
@@ -21,32 +20,33 @@ type updateSortResponse struct {
// @Summary 更新菜单排序
// @Description 更新菜单排序
// @Tags API.menu
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param id formData string true "hashId"
// @Param sort formData int true "排序"
// @Success 200 {object} updateSortResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu/sort [patch]
// @Security LoginToken
func (h *handler) UpdateSort() core.HandlerFunc {
return func(c core.Context) {
req := new(updateSortRequest)
res := new(updateSortResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -55,10 +55,10 @@ func (h *handler) UpdateSort() core.HandlerFunc {
err = h.menuService.UpdateSort(c, id, req.Sort)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuUpdateError,
code.Text(code.MenuUpdateError)).WithErr(err),
code.Text(code.MenuUpdateError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateUsedRequest struct {
@@ -21,32 +20,33 @@ type updateUsedResponse struct {
// @Summary 更新菜单为启用/禁用
// @Description 更新菜单为启用/禁用
// @Tags API.menu
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id formData string true "Hashid"
// @Param id formData string true "hashId"
// @Param used formData int true "是否启用 1:是 -1:否"
// @Success 200 {object} updateUsedResponse
// @Failure 400 {object} code.Failure
// @Router /api/menu/used [patch]
// @Security LoginToken
func (h *handler) UpdateUsed() core.HandlerFunc {
return func(c core.Context) {
req := new(updateUsedRequest)
res := new(updateUsedResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
ids, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}
@@ -55,10 +55,10 @@ func (h *handler) UpdateUsed() core.HandlerFunc {
err = h.menuService.UpdateUsed(c, id, req.Used)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MenuUpdateError,
code.Text(code.MenuUpdateError)).WithErr(err),
code.Text(code.MenuUpdateError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type clearCacheRequest struct {
@@ -21,27 +20,28 @@ type clearCacheResponse struct {
// @Summary 清空缓存
// @Description 清空缓存
// @Tags API.tool
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param redis_key formData string true "Redis Key"
// @Success 200 {object} searchCacheResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/cache/clear [patch]
// @Security LoginToken
func (h *handler) ClearCache() core.HandlerFunc {
return func(c core.Context) {
req := new(clearCacheRequest)
res := new(clearCacheResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
if b := h.cache.Exists(req.RedisKey); b != true {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.CacheNotExist,
code.Text(code.CacheNotExist)),
@@ -51,7 +51,7 @@ func (h *handler) ClearCache() core.HandlerFunc {
b := h.cache.Del(req.RedisKey, redis.WithTrace(c.Trace()))
if b != true {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.CacheDelError,
code.Text(code.CacheDelError)),

View File

@@ -17,11 +17,12 @@ type dbData struct {
// @Summary 查询 DB
// @Description 查询 DB
// @Tags API.tool
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Success 200 {object} dbsResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/data/dbs [get]
// @Security LoginToken
func (h *handler) Dbs() core.HandlerFunc {
return func(c core.Context) {
res := new(dbsResponse)

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type hashIdsDecodeRequest struct {
@@ -20,31 +19,32 @@ type hashIdsDecodeResponse struct {
// @Summary HashIds 解密
// @Description HashIds 解密
// @Tags API.tool
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id path string true "需解密的密文"
// @Success 200 {object} hashIdsDecodeResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/hashids/decode/{id} [get]
// @Security LoginToken
func (h *handler) HashIdsDecode() core.HandlerFunc {
return func(c core.Context) {
req := new(hashIdsDecodeRequest)
res := new(hashIdsDecodeResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
hashId, err := h.hashids.HashidsDecode(req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsDecodeError,
code.Text(code.HashIdsDecodeError)).WithErr(err),
code.Text(code.HashIdsDecodeError)).WithError(err),
)
return
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -22,31 +21,32 @@ type hashIdsEncodeResponse struct {
// @Summary HashIds 加密
// @Description HashIds 加密
// @Tags API.tool
// @Accept json
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param id path string true "需加密的数字"
// @Success 200 {object} hashIdsEncodeResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/hashids/encode/{id} [get]
// @Security LoginToken
func (h *handler) HashIdsEncode() core.HandlerFunc {
return func(c core.Context) {
req := new(hashIdsEncodeRequest)
res := new(hashIdsEncodeResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(req.Id)})
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.HashIdsEncodeError,
code.Text(code.HashIdsEncodeError)).WithErr(err),
code.Text(code.HashIdsEncodeError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type searchCacheRequest struct {
@@ -22,27 +21,28 @@ type searchCacheResponse struct {
// @Summary 查询缓存
// @Description 查询缓存
// @Tags API.tool
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param redis_key formData string true "Redis Key"
// @Success 200 {object} searchCacheResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/cache/search [post]
// @Security LoginToken
func (h *handler) SearchCache() core.HandlerFunc {
return func(c core.Context) {
req := new(searchCacheRequest)
res := new(searchCacheResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
if b := h.cache.Exists(req.RedisKey); b != true {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.CacheNotExist,
code.Text(code.CacheNotExist)),
@@ -52,20 +52,20 @@ func (h *handler) SearchCache() core.HandlerFunc {
val, err := h.cache.Get(req.RedisKey, redis.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.CacheGetError,
code.Text(code.CacheGetError)).WithErr(err),
code.Text(code.CacheGetError)).WithError(err),
)
return
}
ttl, err := h.cache.TTL(req.RedisKey)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.CacheGetError,
code.Text(code.CacheGetError)).WithErr(err),
code.Text(code.CacheGetError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/spf13/cast"
)
@@ -72,7 +71,7 @@ var filterListKeyword = []string{
// @Summary 执行 SQL 语句
// @Description 执行 SQL 语句
// @Tags API.tool
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param db_name formData string true "数据库名称"
// @Param table_name formData string true "数据表名称"
@@ -80,22 +79,23 @@ var filterListKeyword = []string{
// @Success 200 {object} searchMySQLResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/data/mysql [post]
// @Security LoginToken
func (h *handler) SearchMySQL() core.HandlerFunc {
return func(c core.Context) {
req := new(searchMySQLRequest)
res := new(searchMySQLResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
sql := strings.ToLower(strings.TrimSpace(req.SQL))
if sql == "" {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
"SQL 语句不能为空!"),
@@ -104,7 +104,7 @@ func (h *handler) SearchMySQL() core.HandlerFunc {
}
if preFilterList[string([]byte(sql)[:6])] {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
"SQL 语句不能以 "+string([]byte(sql)[:6])+" 开头!"),
@@ -124,7 +124,7 @@ func (h *handler) SearchMySQL() core.HandlerFunc {
}
if !isWhiteList {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
"SQL 语句存在敏感词: "+f+""),
@@ -142,10 +142,10 @@ func (h *handler) SearchMySQL() core.HandlerFunc {
// TODO 后期支持查询多个数据库
rows, err := h.db.GetDbR().Raw(sql).Rows()
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
"MySQL "+err.Error()).WithErr(err),
"MySQL "+err.Error()).WithError(err),
)
return
}
@@ -191,10 +191,10 @@ func (h *handler) SearchMySQL() core.HandlerFunc {
rows, err = h.db.GetDbR().Raw(sqlTableColumn).Rows()
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
"MySQL "+err.Error()).WithErr(err),
"MySQL "+err.Error()).WithError(err),
)
return
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/validation"
"github.com/xinliangnote/go-gin-api/internal/websocket/sysmessage"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/timeutil"
)
@@ -24,12 +23,13 @@ type sendMessageResponse struct {
// @Summary 发送消息
// @Description 发送消息
// @Tags API.tool
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param message formData string true "消息内容"
// @Success 200 {object} sendMessageResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/send_message [post]
// @Security LoginToken
func (h *handler) SendMessage() core.HandlerFunc {
type messageBody struct {
Username string `json:"username"`
@@ -41,45 +41,45 @@ func (h *handler) SendMessage() core.HandlerFunc {
req := new(sendMessageRequest)
res := new(sendMessageResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)).WithErr(err),
validation.Error(err)).WithError(err),
)
return
}
conn, err := sysmessage.GetConn()
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.SocketConnectError,
code.Text(code.SocketConnectError)).WithErr(err),
code.Text(code.SocketConnectError)).WithError(err),
)
return
}
messageData := new(messageBody)
messageData.Username = ctx.UserName()
messageData.Username = ctx.SessionUserInfo().UserName
messageData.Message = req.Message
messageData.Time = timeutil.CSTLayoutString()
messageJsonData, err := json.Marshal(messageData)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.SocketSendError,
code.Text(code.SocketSendError)).WithErr(err),
code.Text(code.SocketSendError)).WithError(err),
)
return
}
err = conn.OnSend(messageJsonData)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.SocketSendError,
code.Text(code.SocketSendError)).WithErr(err),
code.Text(code.SocketSendError)).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type tablesRequest struct {
@@ -26,21 +25,22 @@ type tableData struct {
// @Summary 查询 Table
// @Description 查询 Table
// @Tags API.tool
// @Accept multipart/form-data
// @Accept application/x-www-form-urlencoded
// @Produce json
// @Param db_name formData string true "数据库名称"
// @Success 200 {object} tablesResponse
// @Failure 400 {object} code.Failure
// @Router /api/tool/data/tables [post]
// @Security LoginToken
func (h *handler) Tables() core.HandlerFunc {
return func(c core.Context) {
req := new(tablesRequest)
res := new(tablesResponse)
if err := c.ShouldBindForm(req); err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -50,10 +50,10 @@ func (h *handler) Tables() core.HandlerFunc {
// TODO 后期支持查询多个数据库
rows, err := h.db.GetDbR().Raw(sqlTables).Rows()
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)).WithErr(err),
code.Text(code.MySQLExecError)).WithError(err),
)
return
}

View File

@@ -0,0 +1,26 @@
package metrics
import (
"github.com/xinliangnote/go-gin-api/internal/proposal"
"go.uber.org/zap"
)
// RecordHandler 指标处理
func RecordHandler(logger *zap.Logger) func(msg *proposal.MetricsMessage) {
if logger == nil {
panic("logger required")
}
return func(msg *proposal.MetricsMessage) {
RecordMetrics(
msg.Method,
msg.Path,
msg.IsSuccess,
msg.HTTPCode,
msg.BusinessCode,
msg.CostSeconds,
msg.TraceID,
)
}
}

View File

@@ -37,15 +37,15 @@ func init() {
}
// RecordMetrics 记录指标
func RecordMetrics(method, uri string, success bool, httpCode, businessCode int, costSeconds float64, traceId string) {
func RecordMetrics(method, path string, success bool, httpCode, businessCode int, costSeconds float64, traceId string) {
metricsRequestsTotal.With(prometheus.Labels{
"method": method,
"path": uri,
"path": path,
}).Inc()
metricsRequestsCost.With(prometheus.Labels{
"method": method,
"path": uri,
"path": path,
"success": cast.ToString(success),
"http_code": cast.ToString(httpCode),
"business_code": cast.ToString(businessCode),

View File

@@ -9,7 +9,7 @@ import (
"strings"
"sync"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/internal/proposal"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/gin-gonic/gin"
@@ -28,9 +28,9 @@ const (
_BodyName = "_body_"
_PayloadName = "_payload_"
_GraphPayloadName = "_graph_payload_"
_UserID = "_user_id_"
_UserName = "_user_name_"
_SessionUserInfo = "_session_user_info"
_AbortErrorName = "_abort_error_"
_IsRecordMetrics = "_is_record_metrics_"
)
var contextPool = &sync.Pool{
@@ -101,8 +101,8 @@ type Context interface {
HTML(name string, obj interface{})
// AbortWithError 错误返回
AbortWithError(err errno.Error)
abortError() errno.Error
AbortWithError(err BusinessError)
abortError() BusinessError
// Header 获取 Header 对象
Header() http.Header
@@ -111,18 +111,19 @@ type Context interface {
// SetHeader 设置 Header
SetHeader(key, value string)
// UserID 获取 UserID
UserID() int64
setUserID(userID int64)
// SessionUserInfo 当前用户信息
SessionUserInfo() proposal.SessionUserInfo
setSessionUserInfo(info proposal.SessionUserInfo)
// UserName 获取 UserName
UserName() string
setUserName(userName string)
// Alias 设置路由别名 for metrics uri
// Alias 设置路由别名 for metrics path
Alias() string
setAlias(path string)
// disableRecordMetrics 设置禁止记录指标
disableRecordMetrics()
ableRecordMetrics()
isRecordMetrics() bool
// RequestInputParams 获取所有参数
RequestInputParams() url.Values
// RequestPostFormParams 获取 PostForm 参数
@@ -279,35 +280,22 @@ func (c *context) SetHeader(key, value string) {
c.ctx.Header(key, value)
}
func (c *context) UserID() int64 {
val, ok := c.ctx.Get(_UserID)
func (c *context) SessionUserInfo() proposal.SessionUserInfo {
val, ok := c.ctx.Get(_SessionUserInfo)
if !ok {
return 0
return proposal.SessionUserInfo{}
}
return val.(int64)
return val.(proposal.SessionUserInfo)
}
func (c *context) setUserID(userID int64) {
c.ctx.Set(_UserID, userID)
func (c *context) setSessionUserInfo(info proposal.SessionUserInfo) {
c.ctx.Set(_SessionUserInfo, info)
}
func (c *context) UserName() string {
val, ok := c.ctx.Get(_UserName)
if !ok {
return ""
}
return val.(string)
}
func (c *context) setUserName(userName string) {
c.ctx.Set(_UserName, userName)
}
func (c *context) AbortWithError(err errno.Error) {
func (c *context) AbortWithError(err BusinessError) {
if err != nil {
httpCode := err.GetHttpCode()
httpCode := err.HTTPCode()
if httpCode == 0 {
httpCode = http.StatusInternalServerError
}
@@ -317,9 +305,9 @@ func (c *context) AbortWithError(err errno.Error) {
}
}
func (c *context) abortError() errno.Error {
func (c *context) abortError() BusinessError {
err, _ := c.ctx.Get(_AbortErrorName)
return err.(errno.Error)
return err.(BusinessError)
}
func (c *context) Alias() string {
@@ -337,6 +325,23 @@ func (c *context) setAlias(path string) {
}
}
func (c *context) isRecordMetrics() bool {
isRecordMetrics, ok := c.ctx.Get(_IsRecordMetrics)
if !ok {
return false
}
return isRecordMetrics.(bool)
}
func (c *context) ableRecordMetrics() {
c.ctx.Set(_IsRecordMetrics, true)
}
func (c *context) disableRecordMetrics() {
c.ctx.Set(_IsRecordMetrics, false)
}
// RequestInputParams 获取所有参数
func (c *context) RequestInputParams() url.Values {
_ = c.ctx.Request.ParseForm()

View File

@@ -12,10 +12,10 @@ import (
"github.com/xinliangnote/go-gin-api/configs"
_ "github.com/xinliangnote/go-gin-api/docs"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/proposal"
"github.com/xinliangnote/go-gin-api/pkg/browser"
"github.com/xinliangnote/go-gin-api/pkg/color"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/trace"
@@ -46,20 +46,13 @@ type option struct {
disablePProf bool
disableSwagger bool
disablePrometheus bool
panicNotify OnPanicNotify
recordMetrics RecordMetrics
enableCors bool
enableRate bool
enableOpenBrowser string
alertNotify proposal.NotifyHandler
recordHandler proposal.RecordHandler
}
// OnPanicNotify 发生panic时通知用
type OnPanicNotify func(ctx Context, err interface{}, stackInfo string)
// RecordMetrics 记录prometheus指标用
// 如果使用AliasForRecordMetrics配置了别名uri将被替换为别名。
type RecordMetrics func(method, uri string, success bool, httpCode, businessCode int, costSeconds float64, traceId string)
// WithDisablePProf 禁用 pprof
func WithDisablePProf() Option {
return func(opt *option) {
@@ -81,17 +74,17 @@ func WithDisablePrometheus() Option {
}
}
// WithPanicNotify 设置panic时的通知回调
func WithPanicNotify(notify OnPanicNotify) Option {
// WithAlertNotify 设置告警通知
func WithAlertNotify(notifyHandler proposal.NotifyHandler) Option {
return func(opt *option) {
opt.panicNotify = notify
opt.alertNotify = notifyHandler
}
}
// WithRecordMetrics 设置记录prometheus记录指标回调
func WithRecordMetrics(record RecordMetrics) Option {
// WithRecordMetrics 设置记录接口指标
func WithRecordMetrics(recordHandler proposal.RecordHandler) Option {
return func(opt *option) {
opt.recordMetrics = record
opt.recordHandler = recordHandler
}
}
@@ -102,41 +95,48 @@ func WithEnableOpenBrowser(uri string) Option {
}
}
// WithEnableCors 开启CORS
// WithEnableCors 设置支持跨域
func WithEnableCors() Option {
return func(opt *option) {
opt.enableCors = true
}
}
// WithEnableRate 设置支持限流
func WithEnableRate() Option {
return func(opt *option) {
opt.enableRate = true
}
}
func DisableTrace(ctx Context) {
// DisableTraceLog 禁止记录日志
func DisableTraceLog(ctx Context) {
ctx.disableTrace()
}
// AliasForRecordMetrics 对请求uri起个别名用于prometheus记录指标
// 如Get /user/:username 这样的uri因为username会有非常多的情况这样记录prometheus指标会非常的不有好。
// DisableRecordMetrics 禁止记录指标
func DisableRecordMetrics(ctx Context) {
ctx.disableRecordMetrics()
}
// AliasForRecordMetrics 对请求路径起个别名,用于记录指标。
// 如Get /user/:username 这样的路径,因为 username 会有非常多的情况,这样记录指标非常不友好。
func AliasForRecordMetrics(path string) HandlerFunc {
return func(ctx Context) {
ctx.setAlias(path)
}
}
// WrapAuthHandler 用来处理 Auth 的入口在之后的handler中只需 ctx.UserID() ctx.UserName() 即可。
func WrapAuthHandler(handler func(Context) (userID int64, userName string, err errno.Error)) HandlerFunc {
// WrapAuthHandler 用来处理 Auth 的入口
func WrapAuthHandler(handler func(Context) (sessionUserInfo proposal.SessionUserInfo, err BusinessError)) HandlerFunc {
return func(ctx Context) {
userID, userName, err := handler(ctx)
sessionUserInfo, err := handler(ctx)
if err != nil {
ctx.AbortWithError(err)
return
}
ctx.setUserID(userID)
ctx.setUserName(userName)
ctx.setSessionUserInfo(sessionUserInfo)
}
}
@@ -328,6 +328,11 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
})
mux.engine.Use(func(ctx *gin.Context) {
if ctx.Writer.Status() == http.StatusNotFound {
return
}
ts := time.Now()
context := newContext(ctx)
@@ -335,6 +340,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
context.init()
context.setLogger(logger)
context.ableRecordMetrics()
if !withoutTracePaths[ctx.Request.URL.Path] {
if traceId := context.GetHeader(trace.Header); traceId != "" {
@@ -345,24 +351,6 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
defer func() {
if err := recover(); err != nil {
stackInfo := string(debug.Stack())
logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo))
context.AbortWithError(errno.NewError(
http.StatusInternalServerError,
code.ServerError,
code.Text(code.ServerError)),
)
if notify := opt.panicNotify; notify != nil {
notify(context, err, stackInfo)
}
}
if ctx.Writer.Status() == http.StatusNotFound {
return
}
var (
response interface{}
businessCode int
@@ -372,57 +360,103 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
graphResponse interface{}
)
if ct := context.Trace(); ct != nil {
context.SetHeader(trace.Header, ct.ID())
traceId = ct.ID()
}
// region 发生 Panic 异常发送告警提醒
if err := recover(); err != nil {
stackInfo := string(debug.Stack())
logger.Error("got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo))
context.AbortWithError(Error(
http.StatusInternalServerError,
code.ServerError,
code.Text(code.ServerError)),
)
if notifyHandler := opt.alertNotify; notifyHandler != nil {
notifyHandler(&proposal.AlertMessage{
ProjectName: configs.ProjectName,
Env: env.Active().Value(),
TraceID: traceId,
HOST: context.Host(),
URI: context.URI(),
Method: context.Method(),
ErrorMessage: err,
ErrorStack: stackInfo,
Timestamp: time.Now(),
})
}
}
// endregion
// region 发生错误,进行返回
if ctx.IsAborted() {
for i := range ctx.Errors { // gin error
for i := range ctx.Errors {
multierr.AppendInto(&abortErr, ctx.Errors[i])
}
if err := context.abortError(); err != nil { // customer err
multierr.AppendInto(&abortErr, err.GetErr())
response = err
businessCode = err.GetBusinessCode()
businessCodeMsg = err.GetMsg()
if x := context.Trace(); x != nil {
context.SetHeader(trace.Header, x.ID())
traceId = x.ID()
}
ctx.JSON(err.GetHttpCode(), &code.Failure{
Code: businessCode,
Message: businessCodeMsg,
// 判断是否需要发送告警通知
if err.IsAlert() {
if notifyHandler := opt.alertNotify; notifyHandler != nil {
notifyHandler(&proposal.AlertMessage{
ProjectName: configs.ProjectName,
Env: env.Active().Value(),
TraceID: traceId,
HOST: context.Host(),
URI: context.URI(),
Method: context.Method(),
ErrorMessage: err.Message(),
ErrorStack: fmt.Sprintf("%+v", err.StackError()),
Timestamp: time.Now(),
})
}
} else {
}
multierr.AppendInto(&abortErr, err.StackError())
businessCode = err.BusinessCode()
businessCodeMsg = err.Message()
response = &code.Failure{
Code: businessCode,
Message: businessCodeMsg,
}
ctx.JSON(err.HTTPCode(), response)
}
}
// endregion
// region 正确返回
response = context.getPayload()
if response != nil {
if x := context.Trace(); x != nil {
context.SetHeader(trace.Header, x.ID())
traceId = x.ID()
}
ctx.JSON(http.StatusOK, response)
}
}
// endregion
graphResponse = context.getGraphPayload()
if opt.recordMetrics != nil {
uri := context.URI()
// region 记录指标
if opt.recordHandler != nil && context.isRecordMetrics() {
path := context.Path()
if alias := context.Alias(); alias != "" {
uri = alias
path = alias
}
opt.recordMetrics(
context.Method(),
uri,
!ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK,
ctx.Writer.Status(),
businessCode,
time.Since(ts).Seconds(),
traceId,
)
opt.recordHandler(&proposal.MetricsMessage{
ProjectName: configs.ProjectName,
Env: env.Active().Value(),
TraceID: traceId,
HOST: context.Host(),
Path: path,
Method: context.Method(),
HTTPCode: ctx.Writer.Status(),
BusinessCode: businessCode,
CostSeconds: time.Since(ts).Seconds(),
IsSuccess: !ctx.IsAborted() && (ctx.Writer.Status() == http.StatusOK),
})
}
// endregion
// region 记录日志
var t *trace.Trace
if x := context.Trace(); x != nil {
t = x.(*trace.Trace)
@@ -454,6 +488,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
responseBody = response
}
graphResponse = context.getGraphPayload()
if graphResponse != nil {
responseBody = graphResponse
}
@@ -468,10 +503,10 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
CostSeconds: time.Since(ts).Seconds(),
})
t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
t.Success = !ctx.IsAborted() && (ctx.Writer.Status() == http.StatusOK)
t.CostSeconds = time.Since(ts).Seconds()
logger.Info("core-interceptor",
logger.Info("trace-log",
zap.Any("method", ctx.Request.Method),
zap.Any("path", decodedURL),
zap.Any("http_code", ctx.Writer.Status()),
@@ -482,6 +517,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
zap.Any("trace_info", t),
zap.Error(abortErr),
)
// endregion
}()
ctx.Next()
@@ -494,7 +530,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
defer releaseContext(context)
if !limiter.Allow() {
context.AbortWithError(errno.NewError(
context.AbortWithError(Error(
http.StatusTooManyRequests,
code.TooManyRequests,
code.Text(code.TooManyRequests)),
@@ -506,8 +542,9 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
})
}
mux.engine.NoMethod(wrapHandlers(DisableTrace)...)
mux.engine.NoRoute(wrapHandlers(DisableTrace)...)
mux.engine.NoMethod(wrapHandlers(DisableTraceLog)...)
mux.engine.NoRoute(wrapHandlers(DisableTraceLog)...)
system := mux.Group("/system")
{
// 健康检查

View File

@@ -0,0 +1,82 @@
package core
import (
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
var _ BusinessError = (*businessError)(nil)
type BusinessError interface {
// i 为了避免被其他包实现
i()
// WithError 设置错误信息
WithError(err error) BusinessError
// WithAlert 设置告警通知
WithAlert() BusinessError
// BusinessCode 获取业务码
BusinessCode() int
// HTTPCode 获取 HTTP 状态码
HTTPCode() int
// Message 获取错误描述
Message() string
// StackError 获取带堆栈的错误信息
StackError() error
// IsAlert 是否开启告警通知
IsAlert() bool
}
type businessError struct {
httpCode int // HTTP 状态码
businessCode int // 业务码
message string // 错误描述
stackError error // 含有堆栈信息的错误
isAlert bool // 是否告警通知
}
func Error(httpCode, businessCode int, message string) BusinessError {
return &businessError{
httpCode: httpCode,
businessCode: businessCode,
message: message,
isAlert: false,
}
}
func (e *businessError) i() {}
func (e *businessError) WithError(err error) BusinessError {
e.stackError = errors.WithStack(err)
return e
}
func (e *businessError) WithAlert() BusinessError {
e.isAlert = true
return e
}
func (e *businessError) HTTPCode() int {
return e.httpCode
}
func (e *businessError) BusinessCode() int {
return e.businessCode
}
func (e *businessError) Message() string {
return e.message
}
func (e *businessError) StackError() error {
return e.stackError
}
func (e *businessError) IsAlert() bool {
return e.isAlert
}

View File

@@ -1,52 +0,0 @@
package notify
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/mail"
"go.uber.org/zap"
)
// Email 发生 panic 时进行邮件通知
func Email(ctx core.Context, err interface{}, stackInfo string) {
cfg := configs.Get().Mail
if cfg.Host == "" || cfg.Port == 0 || cfg.User == "" || cfg.Pass == "" || cfg.To == "" {
ctx.Logger().Error("Mail config error")
return
}
tractID := ""
if ctx.Trace() != nil {
tractID = ctx.Trace().ID()
}
subject, body, htmlErr := NewPanicHTMLEmail(
ctx.Method(),
ctx.Host(),
ctx.URI(),
tractID,
err,
stackInfo,
)
if htmlErr != nil {
ctx.Logger().Error("NewPanicHTMLEmail error", zap.Error(htmlErr))
return
}
options := &mail.Options{
MailHost: cfg.Host,
MailPort: cfg.Port,
MailUser: cfg.User,
MailPass: cfg.Pass,
MailTo: cfg.To,
Subject: subject,
Body: body,
}
sendErr := mail.Send(options)
if sendErr != nil {
ctx.Logger().Error("Mail Send error", zap.Error(sendErr))
}
return
}

View File

@@ -1,44 +0,0 @@
package notify
import (
"bytes"
"fmt"
"html/template"
"time"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify/templates"
)
// NewPanicHTMLEmail 发送系统异常邮件 html
func NewPanicHTMLEmail(method, host, uri, id string, msg interface{}, stack string) (subject string, body string, err error) {
mailData := &struct {
URL string
ID string
Msg string
Stack string
Year int
}{
URL: fmt.Sprintf("%s %s%s", method, host, uri),
ID: id,
Msg: fmt.Sprintf("%+v", msg),
Stack: stack,
Year: time.Now().Year(),
}
mailTplContent, err := getEmailHTMLContent(templates.PanicMail, mailData)
return fmt.Sprintf("[系统异常]-%s", uri), mailTplContent, err
}
// getEmailHTMLContent 获取邮件模板
func getEmailHTMLContent(mailTpl string, mailData interface{}) (string, error) {
tpl, err := template.New("email tpl").Parse(mailTpl)
if err != nil {
return "", err
}
buffer := new(bytes.Buffer)
err = tpl.Execute(buffer, mailData)
if err != nil {
return "", err
}
return buffer.String(), nil
}

View File

@@ -0,0 +1,28 @@
package proposal
import (
"encoding/json"
"time"
)
// AlertMessage 告警信息
type AlertMessage struct {
ProjectName string `json:"project_name"` // 项目名,用于区分不同项目告警信息
Env string `json:"env"` // 运行环境
TraceID string `json:"trace_id"` // 唯一ID用于追踪关联
HOST string `json:"host"` // 请求 HOST
URI string `json:"uri"` // 请求 URI
Method string `json:"method"` // 请求 Method
ErrorMessage interface{} `json:"error_message"` // 错误信息
ErrorStack string `json:"error_stack"` // 堆栈信息
Timestamp time.Time `json:"timestamp"` // 时间戳
}
// Marshal 序列化到JSON
func (a *AlertMessage) Marshal() (jsonRaw []byte) {
jsonRaw, _ = json.Marshal(a)
return
}
// NotifyHandler 告警的发送句柄
type NotifyHandler func(msg *AlertMessage)

View File

@@ -0,0 +1,28 @@
package proposal
import (
"encoding/json"
)
// MetricsMessage 指标信息
type MetricsMessage struct {
ProjectName string `json:"project_name"` // 项目名,用于区分不同项目告警信息
Env string `json:"env"` // 运行环境
TraceID string `json:"trace_id"` // 唯一ID用于追踪关联
HOST string `json:"host"` // 请求 HOST
Path string `json:"path"` // 请求 Path
Method string `json:"method"` // 请求 Method
HTTPCode int `json:"http_code"` // HTTP 状态码
BusinessCode int `json:"business_code"` // 业务码
CostSeconds float64 `json:"cost_seconds"` // 耗时,单位:秒
IsSuccess bool `json:"is_success"` // 状态,是否成功
}
// Marshal 序列化到JSON
func (m *MetricsMessage) Marshal() (jsonRaw []byte) {
jsonRaw, _ = json.Marshal(m)
return
}
// RecordHandler 指标的记录句柄
type RecordHandler func(msg *MetricsMessage)

View File

@@ -0,0 +1,15 @@
package proposal
import "encoding/json"
// SessionUserInfo 当前用户会话信息
type SessionUserInfo struct {
UserID int32 `json:"user_id"` // 用户ID
UserName string `json:"user_name"` // 用户名
}
// Marshal 序列化到JSON
func (user *SessionUserInfo) Marshal() (jsonRaw []byte) {
jsonRaw, _ = json.Marshal(user)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"go.uber.org/zap"
)
@@ -62,10 +61,10 @@ func (h *handler) AdminMenu() core.HandlerFunc {
return func(ctx core.Context) {
req := new(adminMenuRequest)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -89,10 +88,10 @@ func (h *handler) MenuAction() core.HandlerFunc {
return func(ctx core.Context) {
req := new(menuActionRequest)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"go.uber.org/zap"
)
@@ -56,10 +55,10 @@ func (h *handler) Api() core.HandlerFunc {
return func(ctx core.Context) {
req := new(apiRequest)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"go.uber.org/zap"
)
@@ -44,10 +43,10 @@ func (h *handler) Edit() core.HandlerFunc {
return func(ctx core.Context) {
req := new(editRequest)
if err := ctx.ShouldBindURI(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/proposal/tablesqls"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/go-redis/redis/v7"
"github.com/spf13/cast"
@@ -68,10 +67,10 @@ func (h *handler) Execute() core.HandlerFunc {
return func(ctx core.Context) {
req := new(initExecuteRequest)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -80,7 +79,7 @@ func (h *handler) Execute() core.HandlerFunc {
versionStr := runtime.Version()
version := cast.ToFloat32(versionStr[2:6])
if version < configs.MinGoVersion {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.GoVersionError,
code.Text(code.GoVersionError)),
@@ -101,10 +100,10 @@ func (h *handler) Execute() core.HandlerFunc {
})
if err := redisClient.Ping().Err(); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.RedisConnectError,
code.Text(code.RedisConnectError)).WithErr(err),
code.Text(code.RedisConnectError)).WithError(err),
)
return
}
@@ -131,10 +130,10 @@ func (h *handler) Execute() core.HandlerFunc {
})
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLConnectError,
code.Text(code.MySQLConnectError)).WithErr(err),
code.Text(code.MySQLConnectError)).WithError(err),
)
return
}
@@ -165,10 +164,10 @@ func (h *handler) Execute() core.HandlerFunc {
viper.Set("mysql.write.name", req.MySQLName)
if viper.WriteConfig() != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.WriteConfigError,
code.Text(code.WriteConfigError)).WithErr(err),
code.Text(code.WriteConfigError)).WithError(err),
)
return
}
@@ -182,10 +181,10 @@ func (h *handler) Execute() core.HandlerFunc {
if v["table_sql"] != "" {
// region 初始化表结构
if err = db.Exec(v["table_sql"]).Error; err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err),
code.Text(code.MySQLExecError)+" "+err.Error()).WithError(err),
)
return
}
@@ -196,10 +195,10 @@ func (h *handler) Execute() core.HandlerFunc {
// region 初始化默认数据
if v["table_data_sql"] != "" {
if err = db.Exec(v["table_data_sql"]).Error; err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err),
code.Text(code.MySQLExecError)+" "+err.Error()).WithError(err),
)
return
}
@@ -214,10 +213,10 @@ func (h *handler) Execute() core.HandlerFunc {
// region 生成 install 完成标识
f, err := os.Create(configs.ProjectInstallMark)
if err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err),
code.Text(code.MySQLExecError)+" "+err.Error()).WithError(err),
)
return
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/proposal/tablesqls"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
@@ -56,10 +55,10 @@ func (h *handler) UpgradeExecute() core.HandlerFunc {
return func(ctx core.Context) {
req := new(upgradeExecuteRequest)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
code.Text(code.ParamBindError)).WithError(err),
)
return
}
@@ -68,29 +67,29 @@ func (h *handler) UpgradeExecute() core.HandlerFunc {
db := h.db.GetDbW()
if upgradeTableList[req.TableName] == nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)).WithErr(errors.New("数据表不存在")),
code.Text(code.MySQLExecError)).WithError(errors.New("数据表不存在")),
)
return
}
if !upgradeTableOp[req.Op] {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)).WithErr(errors.New("非法操作")),
code.Text(code.MySQLExecError)).WithError(errors.New("非法操作")),
)
return
}
if req.Op == "table" {
if err := db.Exec(upgradeTableList[req.TableName]["table_sql"]).Error; err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err),
code.Text(code.MySQLExecError)+" "+err.Error()).WithError(err),
)
return
}
@@ -98,10 +97,10 @@ func (h *handler) UpgradeExecute() core.HandlerFunc {
outPutString = "初始化 MySQL 数据表:" + req.TableName + " 成功。"
} else if req.Op == "table_data" {
if err := db.Exec(upgradeTableList[req.TableName]["table_data_sql"]).Error; err != nil {
ctx.AbortWithError(errno.NewError(
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.MySQLExecError,
code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err),
code.Text(code.MySQLExecError)+" "+err.Error()).WithError(err),
)
return
}

View File

@@ -0,0 +1,6 @@
package authorized
const (
IsUsedYES = 1 // 启用
IsUsedNo = -1 // 禁用
)

View File

@@ -74,7 +74,10 @@ func after(db *gorm.DB) {
sqlInfo.Stack = utils.FileWithLineNum()
sqlInfo.Rows = db.Statement.RowsAffected
sqlInfo.CostSeconds = time.Since(ts).Seconds()
if ctx.Trace != nil {
ctx.Trace.AppendSQL(sqlInfo)
}
return
}

View File

@@ -0,0 +1,56 @@
package interceptor
import (
"encoding/json"
"net/http"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/proposal"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
func (i *interceptor) CheckLogin(ctx core.Context) (sessionUserInfo proposal.SessionUserInfo, err core.BusinessError) {
token := ctx.GetHeader(configs.HeaderLoginToken)
if token == "" {
err = core.Error(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithError(errors.New("Header 中缺少 Token 参数"))
return
}
if !i.cache.Exists(configs.RedisKeyPrefixLoginUser + token) {
err = core.Error(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithError(errors.New("请先登录"))
return
}
cacheData, cacheErr := i.cache.Get(configs.RedisKeyPrefixLoginUser+token, redis.WithTrace(ctx.Trace()))
if cacheErr != nil {
err = core.Error(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithError(cacheErr)
return
}
jsonErr := json.Unmarshal([]byte(cacheData), &sessionUserInfo)
if jsonErr != nil {
core.Error(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithError(jsonErr)
return
}
return
}

View File

@@ -1,4 +1,4 @@
package middleware
package interceptor
import (
"encoding/json"
@@ -9,47 +9,46 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/urltable"
)
func (m *middleware) RBAC() core.HandlerFunc {
func (i *interceptor) CheckRBAC() core.HandlerFunc {
return func(c core.Context) {
token := c.GetHeader("Token")
if token == "" {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Token 参数")),
code.Text(code.AuthorizationError)).WithError(errors.New("Header 中缺少 Token 参数")),
)
return
}
if !m.cache.Exists(configs.RedisKeyPrefixLoginUser + token) {
c.AbortWithError(errno.NewError(
if !i.cache.Exists(configs.RedisKeyPrefixLoginUser + token) {
c.AbortWithError(core.Error(
http.StatusUnauthorized,
code.CacheGetError,
code.Text(code.CacheGetError)).WithErr(errors.New("请先登录 1")),
code.Text(code.CacheGetError)).WithError(errors.New("请先登录")),
)
return
}
if !m.cache.Exists(configs.RedisKeyPrefixLoginUser + token + ":action") {
c.AbortWithError(errno.NewError(
if !i.cache.Exists(configs.RedisKeyPrefixLoginUser + token + ":action") {
c.AbortWithError(core.Error(
http.StatusUnauthorized,
code.CacheGetError,
code.Text(code.CacheGetError)).WithErr(errors.New("请先登录 2")),
code.Text(code.CacheGetError)).WithError(errors.New("当前账号未配置 RBAC 权限")),
)
return
}
actionData, err := m.cache.Get(configs.RedisKeyPrefixLoginUser+token+":action", redis.WithTrace(c.Trace()))
actionData, err := i.cache.Get(configs.RedisKeyPrefixLoginUser+token+":action", redis.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusUnauthorized,
code.CacheGetError,
code.Text(code.CacheGetError)).WithErr(err),
code.Text(code.CacheGetError)).WithError(err),
)
return
}
@@ -57,10 +56,10 @@ func (m *middleware) RBAC() core.HandlerFunc {
var actions []admin.MyActionData
err = json.Unmarshal([]byte(actionData), &actions)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
code.Text(code.AuthorizationError)).WithError(err),
)
return
}
@@ -72,10 +71,10 @@ func (m *middleware) RBAC() core.HandlerFunc {
}
if pattern, _ := table.Mapping(c.Method() + c.Path()); pattern == "" {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.RBACError,
code.Text(code.RBACError)).WithErr(errors.New(c.Method() + c.Path() + " 未进行 RBAC 授权")),
code.Text(code.RBACError)).WithError(errors.New(c.Method() + c.Path() + " 未进行 RBAC 授权")),
)
return
}

View File

@@ -1,39 +1,36 @@
package middleware
package interceptor
import (
"net/http"
"strings"
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql/authorized"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/signature"
"github.com/xinliangnote/go-gin-api/pkg/urltable"
)
const (
ttl = time.Minute * 2 // 签名超时时间 2 分钟
minLength = 2 // split space 最小长度
notUsed = -1 // -1 表示被禁用
)
var whiteListPath = map[string]bool{
"/login/web": true,
}
func (m *middleware) Signature() core.HandlerFunc {
func (i *interceptor) CheckSignature() core.HandlerFunc {
return func(c core.Context) {
if !env.Active().IsPro() {
return
}
// 签名信息
authorization := c.GetHeader(configs.HeaderSignToken)
if authorization == "" {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Authorization 参数")),
code.Text(code.AuthorizationError)).WithError(errors.New("Header 中缺少 Authorization 参数")),
)
return
}
@@ -41,51 +38,51 @@ func (m *middleware) Signature() core.HandlerFunc {
// 时间信息
date := c.GetHeader(configs.HeaderSignTokenDate)
if date == "" {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Date 参数")),
code.Text(code.AuthorizationError)).WithError(errors.New("Header 中缺少 Date 参数")),
)
return
}
// 通过签名信息获取 key
authorizationSplit := strings.Split(authorization, " ")
if len(authorizationSplit) < minLength {
c.AbortWithError(errno.NewError(
if len(authorizationSplit) < 2 {
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中 Authorization 格式错误")),
code.Text(code.AuthorizationError)).WithError(errors.New("Header 中 Authorization 格式错误")),
)
return
}
key := authorizationSplit[0]
data, err := m.authorizedService.DetailByKey(c, key)
data, err := i.authorizedService.DetailByKey(c, key)
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
code.Text(code.AuthorizationError)).WithError(err),
)
return
}
if data.IsUsed == notUsed {
c.AbortWithError(errno.NewError(
if data.IsUsed == authorized.IsUsedNo {
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New(key + " 已被禁止调用")),
code.Text(code.AuthorizationError)).WithError(errors.New(key + " 已被禁止调用")),
)
return
}
if len(data.Apis) < 1 {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New(key + " 未进行接口授权")),
code.Text(code.AuthorizationError)).WithError(errors.New(key + " 未进行接口授权")),
)
return
}
@@ -98,30 +95,30 @@ func (m *middleware) Signature() core.HandlerFunc {
}
if pattern, _ := table.Mapping(c.Method() + c.Path()); pattern == "" {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New(c.Method() + c.Path() + " 未进行接口授权")),
code.Text(code.AuthorizationError)).WithError(errors.New(c.Method() + c.Path() + " 未进行接口授权")),
)
return
}
}
ok, err := signature.New(key, data.Secret, ttl).Verify(authorization, date, c.Path(), c.Method(), c.RequestInputParams())
ok, err := signature.New(key, data.Secret, configs.HeaderSignTokenTimeout).Verify(authorization, date, c.Path(), c.Method(), c.RequestInputParams())
if err != nil {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
code.Text(code.AuthorizationError)).WithError(err),
)
return
}
if !ok {
c.AbortWithError(errno.NewError(
c.AbortWithError(core.Error(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中 Authorization 信息错误")),
code.Text(code.AuthorizationError)).WithError(errors.New("Header 中 Authorization 信息错误")),
)
return
}

View File

@@ -1,36 +1,33 @@
package middleware
package interceptor
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/proposal"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/internal/services/admin"
"github.com/xinliangnote/go-gin-api/internal/services/authorized"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"go.uber.org/zap"
)
var _ Middleware = (*middleware)(nil)
var _ Interceptor = (*interceptor)(nil)
type Interceptor interface {
// CheckLogin 验证是否登录
CheckLogin(ctx core.Context) (info proposal.SessionUserInfo, err core.BusinessError)
// CheckRBAC 验证 RBAC 权限是否合法
CheckRBAC() core.HandlerFunc
// CheckSignature 验证签名是否合法,对用签名算法 pkg/signature
CheckSignature() core.HandlerFunc
type Middleware interface {
// i 为了避免被其他包实现
i()
// DisableLog 不记录日志
DisableLog() core.HandlerFunc
// Signature 签名验证,对用签名算法 pkg/signature
Signature() core.HandlerFunc
// Token 签名验证,对登录用户的验证
Token(ctx core.Context) (userId int64, userName string, err errno.Error)
// RBAC 权限验证
RBAC() core.HandlerFunc
}
type middleware struct {
type interceptor struct {
logger *zap.Logger
cache redis.Repo
db mysql.Repo
@@ -38,8 +35,8 @@ type middleware struct {
adminService admin.Service
}
func New(logger *zap.Logger, cache redis.Repo, db mysql.Repo) Middleware {
return &middleware{
func New(logger *zap.Logger, cache redis.Repo, db mysql.Repo) Interceptor {
return &interceptor{
logger: logger,
cache: cache,
db: db,
@@ -48,4 +45,4 @@ func New(logger *zap.Logger, cache redis.Repo, db mysql.Repo) Middleware {
}
}
func (m *middleware) i() {}
func (i *interceptor) i() {}

View File

@@ -1,9 +0,0 @@
package middleware
import "github.com/xinliangnote/go-gin-api/internal/pkg/core"
func (m *middleware) DisableLog() core.HandlerFunc {
return func(c core.Context) {
core.DisableTrace(c)
}
}

View File

@@ -1,66 +0,0 @@
package middleware
import (
"encoding/json"
"net/http"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/errors"
)
func (m *middleware) Token(ctx core.Context) (userId int64, userName string, err errno.Error) {
token := ctx.GetHeader(configs.HeaderLoginToken)
if token == "" {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Token 参数"))
return
}
if !m.cache.Exists(configs.RedisKeyPrefixLoginUser + token) {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("请先登录"))
return
}
cacheData, cacheErr := m.cache.Get(configs.RedisKeyPrefixLoginUser+token, redis.WithTrace(ctx.Trace()))
if cacheErr != nil {
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(cacheErr)
return
}
type userInfo struct {
Id int64 `json:"id"` // 用户ID
Username string `json:"username"` // 用户名
}
var userData userInfo
jsonErr := json.Unmarshal([]byte(cacheData), &userData)
if jsonErr != nil {
errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(jsonErr)
return
}
userId = userData.Id
userName = userData.Username
return
}

View File

@@ -2,13 +2,13 @@ package router
import (
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/alert"
"github.com/xinliangnote/go-gin-api/internal/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
"github.com/xinliangnote/go-gin-api/internal/repository/cron"
"github.com/xinliangnote/go-gin-api/internal/repository/mysql"
"github.com/xinliangnote/go-gin-api/internal/repository/redis"
"github.com/xinliangnote/go-gin-api/internal/router/middleware"
"github.com/xinliangnote/go-gin-api/internal/router/interceptor"
"github.com/xinliangnote/go-gin-api/pkg/errors"
"github.com/xinliangnote/go-gin-api/pkg/file"
@@ -20,7 +20,7 @@ type resource struct {
logger *zap.Logger
db mysql.Repo
cache redis.Repo
middles middleware.Middleware
interceptors interceptor.Interceptor
cronServer cron.Server
}
@@ -73,8 +73,8 @@ func NewHTTPServer(logger *zap.Logger, cronLogger *zap.Logger) (*Server, error)
core.WithEnableOpenBrowser(openBrowserUri),
core.WithEnableCors(),
core.WithEnableRate(),
core.WithPanicNotify(notify.Email),
core.WithRecordMetrics(metrics.RecordMetrics),
core.WithAlertNotify(alert.NotifyHandler(logger)),
core.WithRecordMetrics(metrics.RecordHandler(logger)),
)
if err != nil {
@@ -82,7 +82,7 @@ func NewHTTPServer(logger *zap.Logger, cronLogger *zap.Logger) (*Server, error)
}
r.mux = mux
r.middles = middleware.New(logger, r.cache, r.db)
r.interceptors = interceptor.New(logger, r.cache, r.db)
// 设置 Render 路由
setRenderRouter(r)

View File

@@ -5,24 +5,33 @@ import (
"github.com/xinliangnote/go-gin-api/internal/api/authorized"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/cron"
"github.com/xinliangnote/go-gin-api/internal/api/helper"
"github.com/xinliangnote/go-gin-api/internal/api/menu"
"github.com/xinliangnote/go-gin-api/internal/api/tool"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func setApiRouter(r *resource) {
// helper
helperHandler := helper.New(r.logger, r.db, r.cache)
helpers := r.mux.Group("/helper")
{
helpers.GET("/md5/:str", helperHandler.Md5())
helpers.POST("/sign", helperHandler.Sign())
}
// admin
adminHandler := admin.New(r.logger, r.db, r.cache)
// 需要签名验证,无需登录验证,无需 RBAC 权限验证
login := r.mux.Group("/api", r.middles.Signature())
login := r.mux.Group("/api", r.interceptors.CheckSignature())
{
login.POST("/login", adminHandler.Login())
}
// 需要签名验证、登录验证,无需 RBAC 权限验证
notRBAC := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature())
notRBAC := r.mux.Group("/api", core.WrapAuthHandler(r.interceptors.CheckLogin), r.interceptors.CheckSignature())
{
notRBAC.POST("/admin/logout", adminHandler.Logout())
notRBAC.PATCH("/admin/modify_password", adminHandler.ModifyPassword())
@@ -31,7 +40,7 @@ func setApiRouter(r *resource) {
}
// 需要签名验证、登录验证、RBAC 权限验证
api := r.mux.Group("/api", core.WrapAuthHandler(r.middles.Token), r.middles.Signature(), r.middles.RBAC())
api := r.mux.Group("/api", core.WrapAuthHandler(r.interceptors.CheckLogin), r.interceptors.CheckSignature(), r.interceptors.CheckRBAC())
{
// authorized
authorizedHandler := authorized.New(r.logger, r.db, r.cache)

View File

@@ -1,6 +1,7 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/render/admin"
"github.com/xinliangnote/go-gin-api/internal/render/authorized"
"github.com/xinliangnote/go-gin-api/internal/render/config"
@@ -27,7 +28,7 @@ func setRenderRouter(r *resource) {
renderCron := cron.New(r.logger, r.db, r.cache)
// 无需记录日志,无需 RBAC 权限验证
notRBAC := r.mux.Group("", r.middles.DisableLog())
notRBAC := r.mux.Group("", core.DisableTraceLog, core.DisableRecordMetrics)
{
// 首页
notRBAC.GET("", renderIndex.Index())
@@ -46,7 +47,7 @@ func setRenderRouter(r *resource) {
}
// 无需记录日志,需要 RBAC 权限验证
render := r.mux.Group("", r.middles.DisableLog())
render := r.mux.Group("", core.DisableTraceLog, core.DisableRecordMetrics)
{
// 配置信息
render.GET("/config/email", renderConfig.Email())

View File

@@ -1,6 +1,7 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/websocket/sysmessage"
)
@@ -8,7 +9,7 @@ func setSocketRouter(r *resource) {
systemMessage := sysmessage.New(r.logger, r.db, r.cache)
// 无需记录日志
socket := r.mux.Group("/socket", r.middles.DisableLog())
socket := r.mux.Group("/socket", core.DisableTraceLog, core.DisableRecordMetrics)
{
// 系统消息
socket.GET("/system/message", systemMessage.Connect())

View File

@@ -19,7 +19,7 @@ func (s *service) Create(ctx core.Context, adminData *CreateAdminData) (id int32
model.Password = password.GeneratePassword(adminData.Password)
model.Nickname = adminData.Nickname
model.Mobile = adminData.Mobile
model.CreatedUser = ctx.UserName()
model.CreatedUser = ctx.SessionUserInfo().UserName
model.IsUsed = 1
model.IsDeleted = -1

View File

@@ -27,7 +27,7 @@ func (s *service) CreateMenu(ctx core.Context, menuData *CreateMenuData) (err er
createModel := admin_menu.NewModel()
createModel.AdminId = menuData.AdminId
createModel.MenuId = cast.ToInt32(v)
createModel.CreatedUser = ctx.UserName()
createModel.CreatedUser = ctx.SessionUserInfo().UserName
_, err = createModel.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {

View File

@@ -12,7 +12,7 @@ import (
func (s *service) Delete(ctx core.Context, id int32) (err error) {
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := admin.NewQueryBuilder()

View File

@@ -12,7 +12,7 @@ import (
func (s *service) ModifyPassword(ctx core.Context, id int32, newPassword string) (err error) {
data := map[string]interface{}{
"password": password.GeneratePassword(newPassword),
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := admin.NewQueryBuilder()

View File

@@ -15,7 +15,7 @@ func (s *service) ModifyPersonalInfo(ctx core.Context, id int32, modifyData *Mod
data := map[string]interface{}{
"nickname": modifyData.Nickname,
"mobile": modifyData.Mobile,
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := admin.NewQueryBuilder()

View File

@@ -12,7 +12,7 @@ import (
func (s *service) ResetPassword(ctx core.Context, id int32) (err error) {
data := map[string]interface{}{
"password": password.ResetPassword(),
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := admin.NewQueryBuilder()

View File

@@ -12,7 +12,7 @@ import (
func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error) {
data := map[string]interface{}{
"is_used": used,
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := admin.NewQueryBuilder()

View File

@@ -25,7 +25,7 @@ func (s *service) Create(ctx core.Context, authorizedData *CreateAuthorizedData)
model.BusinessSecret = secret
model.BusinessDeveloper = authorizedData.BusinessDeveloper
model.Remark = authorizedData.Remark
model.CreatedUser = ctx.UserName()
model.CreatedUser = ctx.SessionUserInfo().UserName
model.IsUsed = 1
model.IsDeleted = -1

View File

@@ -18,7 +18,7 @@ func (s *service) CreateAPI(ctx core.Context, authorizedAPIData *CreateAuthorize
model.BusinessKey = authorizedAPIData.BusinessKey
model.Method = authorizedAPIData.Method
model.Api = authorizedAPIData.API
model.CreatedUser = ctx.UserName()
model.CreatedUser = ctx.SessionUserInfo().UserName
model.IsDeleted = -1
id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext()))

View File

@@ -23,7 +23,7 @@ func (s *service) Delete(ctx core.Context, id int32) (err error) {
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := authorized.NewQueryBuilder()

View File

@@ -23,7 +23,7 @@ func (s *service) DeleteAPI(ctx core.Context, id int32) (err error) {
data := map[string]interface{}{
"is_deleted": 1,
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := authorized_api.NewQueryBuilder()

View File

@@ -2,7 +2,6 @@ package authorized
import (
"encoding/json"
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
@@ -68,7 +67,7 @@ func (s *service) DetailByKey(ctx core.Context, key string) (cacheData *CacheAut
cacheDataByte, _ := json.Marshal(cacheData)
err = s.cache.Set(cacheKey, string(cacheDataByte), time.Hour*24, redis.WithTrace(ctx.Trace()))
err = s.cache.Set(cacheKey, string(cacheDataByte), configs.LoginSessionTTL, redis.WithTrace(ctx.Trace()))
if err != nil {
return nil, err
}

View File

@@ -22,7 +22,7 @@ func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error)
data := map[string]interface{}{
"is_used": used,
"updated_user": ctx.UserName(),
"updated_user": ctx.SessionUserInfo().UserName,
}
qb := authorized.NewQueryBuilder()

Some files were not shown because too many files have changed in this diff Show More