upgrade
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
## 错误码规则
|
||||
|
||||
- 错误码需在 `code.go` 文件中定义。
|
||||
- 错误码需为 > 0 的数,反之表示正确。
|
||||
- 错误码需为 > 1 的数,反之表示正确。
|
||||
|
||||
#### 错误码为 5 位数
|
||||
|
||||
28
internal/api/code/code.go
Normal file
28
internal/api/code/code.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package code
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/pkg/errno"
|
||||
)
|
||||
|
||||
var (
|
||||
// OK
|
||||
OK = errno.NewError(1, "OK")
|
||||
|
||||
// 服务级错误码
|
||||
ErrServer = errno.NewError(10101, http.StatusText(http.StatusInternalServerError))
|
||||
ErrManyRequest = errno.NewError(10102, "Too many requests")
|
||||
|
||||
ErrParam = errno.NewError(10110, "参数有误")
|
||||
ErrSignParam = errno.NewError(10111, "缺少签名")
|
||||
ErrSign = errno.NewError(10112, "签名有误")
|
||||
|
||||
// 模块级错误码 - 用户模块
|
||||
ErrUser = errno.NewError(20101, "非法用户")
|
||||
ErrUserCreate = errno.NewError(20102, "创建用户失败")
|
||||
ErrUserUpdate = errno.NewError(20103, "更新用户失败")
|
||||
ErrUserSearch = errno.NewError(20104, "查询用户失败")
|
||||
|
||||
// ...
|
||||
)
|
||||
9
internal/api/controller/README.md
Normal file
9
internal/api/controller/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## controller
|
||||
|
||||
控制器层。
|
||||
|
||||
主要接收参数、验证参数、调用 `service` 层的业务逻辑处理,最后返回数据。
|
||||
|
||||
命名规范:
|
||||
|
||||
- 包名以 `_handler` 结尾。
|
||||
@@ -1,18 +1,13 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/code"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/jsonparse"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/aes"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/md5"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/rsa"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -41,16 +36,16 @@ func (d *Demo) Get() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(request)
|
||||
if err := c.ShouldBindURI(req); err != nil {
|
||||
c.SetPayload(errno.ErrParam)
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "Tom" {
|
||||
c.SetPayload(errno.ErrUser)
|
||||
c.SetPayload(code.ErrUser)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetPayload(errno.OK.WithData(&response{
|
||||
c.SetPayload(code.OK.WithData(&response{
|
||||
Name: "Tom",
|
||||
Job: "Student",
|
||||
}))
|
||||
@@ -70,16 +65,16 @@ func (d *Demo) Post() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(request)
|
||||
if err := c.ShouldBindPostForm(req); err != nil {
|
||||
c.SetPayload(errno.ErrParam)
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "Jack" {
|
||||
c.SetPayload(errno.ErrUser)
|
||||
c.SetPayload(code.ErrUser)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetPayload(errno.OK.WithData(&response{
|
||||
c.SetPayload(code.OK.WithData(&response{
|
||||
Name: "Jack",
|
||||
Job: "Teacher",
|
||||
}))
|
||||
@@ -109,18 +104,18 @@ func (d *Demo) User() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(request)
|
||||
if err := c.ShouldBindURI(req); err != nil {
|
||||
c.SetPayload(errno.ErrParam)
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "Tom" {
|
||||
c.SetPayload(errno.ErrUser)
|
||||
c.SetPayload(code.ErrUser)
|
||||
return
|
||||
}
|
||||
|
||||
body1, err1 := httpclient.Get("http://127.0.0.1:9999/demo/get/"+req.Name, nil,
|
||||
httpclient.WithTTL(time.Second*2),
|
||||
httpclient.WithJournal(c.Journal()),
|
||||
httpclient.WithTrace(c.Trace()),
|
||||
httpclient.WithLogger(c.Logger()),
|
||||
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
|
||||
)
|
||||
@@ -132,7 +127,7 @@ func (d *Demo) User() core.HandlerFunc {
|
||||
params.Set("name", "Jack")
|
||||
body2, err2 := httpclient.PostForm("http://127.0.0.1:9999/demo/post", params,
|
||||
httpclient.WithTTL(time.Second*2),
|
||||
httpclient.WithJournal(c.Journal()),
|
||||
httpclient.WithTrace(c.Trace()),
|
||||
httpclient.WithLogger(c.Logger()),
|
||||
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
|
||||
)
|
||||
@@ -153,81 +148,6 @@ func (d *Demo) User() core.HandlerFunc {
|
||||
},
|
||||
}
|
||||
}
|
||||
c.SetPayload(errno.OK.WithData(data))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Demo) RsaTest() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
startTime := time.Now()
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 500
|
||||
|
||||
cfg := configs.Get().Rsa
|
||||
rsaPublic := rsa.NewPublic(cfg.Public)
|
||||
rsaPrivate := rsa.NewPrivate(cfg.Private)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
sn, err := rsaPublic.Encrypt(encryptStr)
|
||||
if err != nil {
|
||||
d.logger.Error("rsa public encrypt err", zap.Error(err))
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
_, err = rsaPrivate.Decrypt(sn)
|
||||
if err != nil {
|
||||
d.logger.Error("rsa private decrypt err", zap.Error(err))
|
||||
}
|
||||
}
|
||||
c.SetPayload(errno.OK.
|
||||
WithData(fmt.Sprintf("%v次 - %v", count, time.Since(startTime))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Demo) AesTest() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
startTime := time.Now()
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 1000000
|
||||
|
||||
cfg := configs.Get().Aes
|
||||
aes := aes.New(cfg.Key, cfg.Iv)
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
sn, err := aes.Encrypt(encryptStr)
|
||||
if err != nil {
|
||||
d.logger.Error("aes encrypt err", zap.Error(err))
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
_, err = aes.Decrypt(sn)
|
||||
if err != nil {
|
||||
d.logger.Error("aes decrypt err", zap.Error(err))
|
||||
}
|
||||
}
|
||||
c.SetPayload(errno.OK.
|
||||
WithData(fmt.Sprintf("%v次 - %v", count, time.Since(startTime))))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Demo) MD5Test() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
startTime := time.Now()
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 1000000
|
||||
|
||||
md5 := md5.New()
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
md5.Encrypt(encryptStr)
|
||||
|
||||
// 验证签名
|
||||
md5.Encrypt(encryptStr)
|
||||
}
|
||||
c.SetPayload(errno.OK.
|
||||
WithData(fmt.Sprintf("%v次 - %v", count, time.Since(startTime))),
|
||||
)
|
||||
c.SetPayload(code.OK.WithData(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/token"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type loginRequest struct {
|
||||
UserID int `json:"user_id" form:"user_id"` // 用户ID(>0)
|
||||
UserName string `json:"user_name" form:"user_name"` // 用户名
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
Authorization string `json:"authorization"` // 签名
|
||||
}
|
||||
|
||||
// 登录获取 Authorization 码
|
||||
// @Summary 登录获取 Authorization 码
|
||||
// @Description 登录获取 Authorization 码
|
||||
// @Tags Demo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param loginRequest body loginRequest true "请求信息"
|
||||
// @Success 200 {object} loginResponse "签名信息"
|
||||
// @Router /user/login [post]
|
||||
func (d *Demo) Login() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(loginRequest)
|
||||
res := new(loginResponse)
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
c.SetPayload(errno.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
tokenString, err := token.Sign(req.UserID, req.UserName)
|
||||
if err != nil {
|
||||
d.logger.Error("token sign err", zap.Error(err))
|
||||
res.Authorization = ""
|
||||
} else {
|
||||
res.Authorization = tokenString
|
||||
}
|
||||
c.SetPayload(errno.OK.WithData(res))
|
||||
}
|
||||
}
|
||||
180
internal/api/controller/user_handler/user.go
Normal file
180
internal/api/controller/user_handler/user.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package user_handler
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/code"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/model/user_model"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/token"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ UserDemo = (*userDemo)(nil)
|
||||
|
||||
type UserDemo interface {
|
||||
// i 为了避免被其他包实现
|
||||
i()
|
||||
// 创建用户
|
||||
Create() core.HandlerFunc
|
||||
// 通过用户主键ID更新用户昵称
|
||||
UpdateNickNameByID() core.HandlerFunc
|
||||
// 用户登录
|
||||
Login() core.HandlerFunc
|
||||
// 用户详情
|
||||
Detail() core.HandlerFunc
|
||||
}
|
||||
|
||||
type userDemo struct {
|
||||
logger *zap.Logger
|
||||
userService user_service.UserService
|
||||
}
|
||||
|
||||
func NewUserDemo(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) UserDemo {
|
||||
return &userDemo{
|
||||
logger: logger,
|
||||
userService: user_service.NewUserService(db, cache),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *userDemo) i() {}
|
||||
|
||||
// 创建用户
|
||||
// @Summary 创建用户
|
||||
// @Description 创建用户
|
||||
// @Tags Demo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param RequestInfo body user_model.CreateRequest true "请求信息"
|
||||
// @Success 200 {object} user_model.CreateResponse "返回信息"
|
||||
// @Router /user/create [post]
|
||||
func (u *userDemo) Create() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(user_model.CreateRequest)
|
||||
res := new(user_model.CreateResponse)
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
u.logger.Error("[user] should bind json err", zap.Error(err))
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := u.userService.Create(c, req)
|
||||
if err != nil {
|
||||
u.logger.Error("[user] Create err", zap.Error(err))
|
||||
c.SetPayload(code.ErrUserCreate)
|
||||
return
|
||||
}
|
||||
|
||||
res.Id = id
|
||||
c.SetPayload(code.OK.WithData(res))
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户名称
|
||||
// @Summary 更新用户名称
|
||||
// @Description 更新用户名称
|
||||
// @Tags Demo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param RequestInfo body user_model.UpdateNickNameByIDRequest true "请求信息"
|
||||
// @Success 200 {object} user_model.UpdateNickNameByIDResponse "返回信息"
|
||||
// @Router /user/update [post]
|
||||
func (u *userDemo) UpdateNickNameByID() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(user_model.UpdateNickNameByIDRequest)
|
||||
res := new(user_model.UpdateNickNameByIDResponse)
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
u.logger.Error("[user] should bind json err", zap.Error(err))
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
err := u.userService.UpdateNickNameByID(c, req.Id, req.NickName)
|
||||
if err != nil {
|
||||
u.logger.Error("[user] UpdateNickNameByID err", zap.Error(err))
|
||||
c.SetPayload(code.ErrUserUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
res.Id = req.Id
|
||||
c.SetPayload(code.OK.WithData(res))
|
||||
}
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
// @Summary 用户登录
|
||||
// @Description 用户登录
|
||||
// @Tags Demo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param RequestInfo body user_model.LoginRequest true "请求信息"
|
||||
// @Success 200 {object} user_model.LoginResponse "返回信息"
|
||||
// @Router /user/login [post]
|
||||
func (u *userDemo) Login() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(user_model.LoginRequest)
|
||||
res := new(user_model.LoginResponse)
|
||||
if err := c.ShouldBindJSON(req); err != nil {
|
||||
u.logger.Error("should bind json err", zap.Error(err))
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := configs.Get().JWT
|
||||
tokenString, err := token.New(cfg.Secret).Sign(req.UserID, req.UserName)
|
||||
if err != nil {
|
||||
u.logger.Error("token sign err", zap.Error(err))
|
||||
c.SetPayload(code.ErrSign)
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := token.New(cfg.Secret).Parse(tokenString)
|
||||
if err != nil {
|
||||
u.logger.Error("token parse err", zap.Error(err))
|
||||
c.SetPayload(code.ErrSign)
|
||||
return
|
||||
}
|
||||
|
||||
res.Authorization = tokenString
|
||||
res.ExpireTime = claims.ExpiresAt
|
||||
|
||||
c.SetPayload(code.OK.WithData(res))
|
||||
}
|
||||
}
|
||||
|
||||
// 用户详情
|
||||
// @Summary 用户详情
|
||||
// @Description 用户详情
|
||||
// @Tags Demo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param username path string true "用户名"
|
||||
// @Success 200 {object} user_model.DetailResponse "返回信息"
|
||||
// @Router /user/info/{username} [get]
|
||||
func (u *userDemo) Detail() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
req := new(user_model.DetailRequest)
|
||||
res := new(user_model.DetailResponse)
|
||||
if err := c.ShouldBindURI(req); err != nil {
|
||||
u.logger.Error("should bind uri err", zap.Error(err))
|
||||
c.SetPayload(code.ErrParam)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := u.userService.GetUserByUserName(c, req.UserName)
|
||||
if err != nil {
|
||||
u.logger.Error("[user] GetUserByUserName err", zap.Error(err))
|
||||
c.SetPayload(code.ErrUserSearch)
|
||||
return
|
||||
}
|
||||
|
||||
res.Id = user.Id
|
||||
res.UserName = user.UserName
|
||||
res.NickName = user.NickName
|
||||
res.Mobile = user.Mobile
|
||||
c.SetPayload(code.OK.WithData(res))
|
||||
}
|
||||
}
|
||||
10
internal/api/model/README.md
Normal file
10
internal/api/model/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## model
|
||||
|
||||
实体层。
|
||||
|
||||
- 请求实体、返回实体。
|
||||
- 数据库实体。
|
||||
|
||||
命名规范:
|
||||
|
||||
- 包名以 `_model` 结尾。
|
||||
68
internal/api/model/user_model/user.go
Normal file
68
internal/api/model/user_model/user.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package user_model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 用户Demo表
|
||||
type UserDemo struct {
|
||||
Id uint `gorm:"column:id;primary_key;AUTO_INCREMENT"` // 主键
|
||||
UserName string `gorm:"column:user_name;NOT NULL"` // 用户名
|
||||
NickName string `gorm:"column:nick_name;NOT NULL"` // 昵称
|
||||
Mobile string `gorm:"column:mobile;NOT NULL"` // 手机号
|
||||
IsDeleted int `gorm:"column:is_deleted;default:-1;NOT NULL"` // 是否删除 1:是 -1:否
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 创建时间
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 更新时间
|
||||
}
|
||||
|
||||
func (m *UserDemo) TableName() string {
|
||||
return "user_demo"
|
||||
}
|
||||
|
||||
// user_handler Create Request
|
||||
type CreateRequest struct {
|
||||
UserName string `json:"user_name"` // 用户名
|
||||
NickName string `json:"nick_name"` // 昵称
|
||||
Mobile string `json:"mobile"` // 手机号
|
||||
}
|
||||
|
||||
// user_handler Create Response
|
||||
type CreateResponse struct {
|
||||
Id uint `json:"id"` //主键ID
|
||||
}
|
||||
|
||||
// user_handler UpdateNickNameByID Request
|
||||
type UpdateNickNameByIDRequest struct {
|
||||
Id uint `json:"id"` // 用户主键ID
|
||||
NickName string `json:"nick_name"` // 昵称
|
||||
}
|
||||
|
||||
// user_handler UpdateNickNameByID Response
|
||||
type UpdateNickNameByIDResponse struct {
|
||||
Id uint `json:"id"` // 用户主键ID
|
||||
}
|
||||
|
||||
// user_handler Login Request
|
||||
type LoginRequest struct {
|
||||
UserID int `json:"user_id" form:"user_id"` // 用户ID(>0)
|
||||
UserName string `json:"user_name" form:"user_name"` // 用户名
|
||||
}
|
||||
|
||||
// user_handler Login Response
|
||||
type LoginResponse struct {
|
||||
Authorization string `json:"authorization"` // 签名
|
||||
ExpireTime int64 `json:"expire_time"` // 过期时间
|
||||
}
|
||||
|
||||
// user_handler Detail Request
|
||||
type DetailRequest struct {
|
||||
UserName string `uri:"username"` // 用户名
|
||||
}
|
||||
|
||||
// user_handler Detail Response
|
||||
type DetailResponse struct {
|
||||
Id uint `json:"id"` // 用户主键ID
|
||||
UserName string `json:"user_name"` // 用户名
|
||||
NickName string `json:"nick_name"` // 昵称
|
||||
Mobile string `json:"mobile"` // 手机号
|
||||
}
|
||||
29
internal/api/repository/README.md
Normal file
29
internal/api/repository/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## repository
|
||||
|
||||
数据访问层。
|
||||
|
||||
- `./db_repo` 访问 DB 数据
|
||||
- `./cache_repo` 访问 Cache 数据
|
||||
- `./third_party_request` 访问外部 HTTP 接口数据。
|
||||
|
||||
SQL 建议:
|
||||
- 禁止使用 SQL k v 拼接,好处是避免 SQL 注入;
|
||||
- 禁止使用连表查询,好处是易扩展,比如分库分表;
|
||||
- 禁止使用万能方法,好处是便于后期维护,比如字段调整;
|
||||
- 禁止使用删除方法,好处是避免数据丢失;
|
||||
- 建议每张表需包含字段:主键(id)、标记删除(is_deteled)、创建时间(created_at)、更新时间(updated_at)
|
||||
|
||||
```mysql
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
```
|
||||
什么是万能方法?
|
||||
|
||||
指的是特别灵活的查询,比如通过非固定的参数返回全部字段,建议做到需要什么返回什么,不要返回大而全的数据,更新时也不能传递什么参数更新什么参数,更新字段要提前约定好。
|
||||
|
||||
命名规范:
|
||||
|
||||
- 包名应以 `_repo` 结尾;
|
||||
- `./db_repo` 目录下的包名以 `数据表名`+ `_repo` 命名;
|
||||
120
internal/api/repository/cache_repo/init.go
Normal file
120
internal/api/repository/cache_repo/init.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package cache_repo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
|
||||
"github.com/go-redis/redis/v7"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ Repo = (*cacheRepo)(nil)
|
||||
|
||||
type Repo interface {
|
||||
i()
|
||||
Set(key, value string, ttl time.Duration) error
|
||||
Get(key string) (string, error)
|
||||
TTL(key string) (time.Duration, error)
|
||||
Expire(key string, ttl time.Duration) bool
|
||||
ExpireAt(key string, ttl time.Time) bool
|
||||
Del(keys ...string) bool
|
||||
Incr(key string) int64
|
||||
Close()
|
||||
}
|
||||
|
||||
type cacheRepo struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func New() (Repo, error) {
|
||||
client, err := redisConnect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cacheRepo{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *cacheRepo) i() {}
|
||||
|
||||
func redisConnect() (*redis.Client, error) {
|
||||
cfg := configs.Get().Redis
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Addr,
|
||||
Password: cfg.Pass,
|
||||
DB: cfg.Db,
|
||||
MaxRetries: cfg.MaxRetries,
|
||||
PoolSize: cfg.PoolSize,
|
||||
MinIdleConns: cfg.MinIdleConns,
|
||||
})
|
||||
|
||||
if err := client.Ping().Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "ping redis err")
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Set set some <key,value> into redis
|
||||
func (c *cacheRepo) Set(key, value string, ttl time.Duration) error {
|
||||
if err := c.client.Set(key, value, ttl).Err(); err != nil {
|
||||
return errors.Wrapf(err, "redis set key: %s err", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get get some key from redis
|
||||
func (c *cacheRepo) Get(key string) (string, error) {
|
||||
value, err := c.client.Get(key).Result()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "redis get key: %s err", key)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// TTL get some key from redis
|
||||
func (c *cacheRepo) TTL(key string) (time.Duration, error) {
|
||||
ttl, err := c.client.TTL(key).Result()
|
||||
if err != nil {
|
||||
return -1, errors.Wrapf(err, "redis get key: %s err", key)
|
||||
}
|
||||
|
||||
return ttl, nil
|
||||
}
|
||||
|
||||
// Expire expire some key
|
||||
func (c *cacheRepo) Expire(key string, ttl time.Duration) bool {
|
||||
ok, _ := c.client.Expire(key, ttl).Result()
|
||||
return ok
|
||||
}
|
||||
|
||||
// ExpireAt expire some key at some time
|
||||
func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool {
|
||||
ok, _ := c.client.ExpireAt(key, ttl).Result()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Del del some key from redis
|
||||
func (c *cacheRepo) Del(keys ...string) bool {
|
||||
if len(keys) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
value, _ := c.client.Del(keys...).Result()
|
||||
return value > 0
|
||||
}
|
||||
|
||||
func (c *cacheRepo) Incr(key string) int64 {
|
||||
value, _ := c.client.Incr(key).Result()
|
||||
return value
|
||||
}
|
||||
|
||||
// Close close redis client
|
||||
func (c *cacheRepo) Close() {
|
||||
c.client.Close()
|
||||
}
|
||||
112
internal/api/repository/db_repo/init.go
Normal file
112
internal/api/repository/db_repo/init.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package db_repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ Repo = (*dbRepo)(nil)
|
||||
|
||||
type Repo interface {
|
||||
i()
|
||||
GetDbR() *gorm.DB
|
||||
GetDbW() *gorm.DB
|
||||
DbRClose() error
|
||||
DbWClose() error
|
||||
}
|
||||
|
||||
type dbRepo struct {
|
||||
DbR *gorm.DB
|
||||
DbW *gorm.DB
|
||||
}
|
||||
|
||||
func New() (Repo, error) {
|
||||
cfg := configs.Get().MySQL
|
||||
dbr, err := dbConnect(cfg.Read.User, cfg.Read.Pass, cfg.Read.Addr, cfg.Read.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbw, err := dbConnect(cfg.Write.User, cfg.Write.Pass, cfg.Write.Addr, cfg.Write.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dbRepo{
|
||||
DbR: dbr,
|
||||
DbW: dbw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dbRepo) i() {}
|
||||
|
||||
func (d *dbRepo) GetDbR() *gorm.DB {
|
||||
return d.DbR
|
||||
}
|
||||
|
||||
func (d *dbRepo) GetDbW() *gorm.DB {
|
||||
return d.DbW
|
||||
}
|
||||
|
||||
func (d *dbRepo) DbRClose() error {
|
||||
sqlDB, err := d.DbR.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
func (d *dbRepo) DbWClose() error {
|
||||
sqlDB, err := d.DbW.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
func dbConnect(user, pass, addr, dbName string) (*gorm.DB, error) {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
|
||||
user,
|
||||
pass,
|
||||
addr,
|
||||
dbName,
|
||||
true,
|
||||
"Local")
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
|
||||
}
|
||||
|
||||
db.Set("gorm:table_options", "CHARSET=utf8mb4")
|
||||
|
||||
cfg := configs.Get().MySQL.Base
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置连接池 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。
|
||||
sqlDB.SetMaxOpenConns(cfg.MaxOpenConn)
|
||||
|
||||
// 设置最大连接数 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。
|
||||
sqlDB.SetMaxIdleConns(cfg.MaxIdleConn)
|
||||
|
||||
// 设置最大连接超时
|
||||
sqlDB.SetConnMaxLifetime(time.Minute * cfg.ConnMaxLifeTime)
|
||||
|
||||
// 使用插件
|
||||
db.Use(&TracePlugin{})
|
||||
|
||||
return db, nil
|
||||
}
|
||||
86
internal/api/repository/db_repo/plugin.go
Normal file
86
internal/api/repository/db_repo/plugin.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package db_repo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/trace"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
callBackBeforeName = "core:before"
|
||||
callBackAfterName = "core:after"
|
||||
coreContext = "_core_context"
|
||||
startTime = "_start_time"
|
||||
)
|
||||
|
||||
type TracePlugin struct{}
|
||||
|
||||
func (op *TracePlugin) Name() string {
|
||||
return "tracePlugin"
|
||||
}
|
||||
|
||||
func before(db *gorm.DB) {
|
||||
db.InstanceSet(coreContext, db.Statement.Context)
|
||||
db.InstanceSet(startTime, time.Now())
|
||||
return
|
||||
}
|
||||
|
||||
func after(db *gorm.DB) {
|
||||
_ctx, isExist := db.InstanceGet(coreContext)
|
||||
if !isExist {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, ok := _ctx.(core.Context)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
_ts, isExist := db.InstanceGet(startTime)
|
||||
if !isExist {
|
||||
return
|
||||
}
|
||||
|
||||
ts, ok := _ts.(time.Time)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
sql := db.Dialector.Explain(db.Statement.SQL.String(), db.Statement.Vars...)
|
||||
|
||||
sqlInfo := new(trace.SQL)
|
||||
sqlInfo.Time = time_parse.CSTLayoutString()
|
||||
sqlInfo.SQL = sql
|
||||
sqlInfo.Src = utils.FileWithLineNum()
|
||||
sqlInfo.Rows = db.Statement.RowsAffected
|
||||
sqlInfo.Duration = time.Since(ts).Seconds()
|
||||
ctx.Trace().AppendSQL(sqlInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (op *TracePlugin) Initialize(db *gorm.DB) (err error) {
|
||||
// 开始前
|
||||
_ = db.Callback().Create().Before("gorm:before_create").Register(callBackBeforeName, before)
|
||||
_ = db.Callback().Query().Before("gorm:query").Register(callBackBeforeName, before)
|
||||
_ = db.Callback().Delete().Before("gorm:before_delete").Register(callBackBeforeName, before)
|
||||
_ = db.Callback().Update().Before("gorm:setup_reflect_value").Register(callBackBeforeName, before)
|
||||
_ = db.Callback().Row().Before("gorm:row").Register(callBackBeforeName, before)
|
||||
_ = db.Callback().Raw().Before("gorm:raw").Register(callBackBeforeName, before)
|
||||
|
||||
// 结束后
|
||||
_ = db.Callback().Create().After("gorm:after_create").Register(callBackAfterName, after)
|
||||
_ = db.Callback().Query().After("gorm:after_query").Register(callBackAfterName, after)
|
||||
_ = db.Callback().Delete().After("gorm:after_delete").Register(callBackAfterName, after)
|
||||
_ = db.Callback().Update().After("gorm:after_update").Register(callBackAfterName, after)
|
||||
_ = db.Callback().Row().After("gorm:row").Register(callBackAfterName, after)
|
||||
_ = db.Callback().Raw().After("gorm:raw").Register(callBackAfterName, after)
|
||||
return
|
||||
}
|
||||
|
||||
var _ gorm.Plugin = &TracePlugin{}
|
||||
71
internal/api/repository/db_repo/user_demo_repo/user_demo.go
Normal file
71
internal/api/repository/db_repo/user_demo_repo/user_demo.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package user_demo_repo
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/model/user_model"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ UserRepo = (*userRepo)(nil)
|
||||
|
||||
type UserRepo interface {
|
||||
// i 为了避免被其他包实现
|
||||
i()
|
||||
Create(ctx core.Context, user user_model.UserDemo) (id uint, err error)
|
||||
UpdateNickNameByID(ctx core.Context, id uint, username string) (err error)
|
||||
GetUserByUserName(ctx core.Context, username string) (*user_model.UserDemo, error)
|
||||
getUserByID(ctx core.Context, id uint) (*user_model.UserDemo, error)
|
||||
}
|
||||
|
||||
type userRepo struct {
|
||||
db db_repo.Repo
|
||||
}
|
||||
|
||||
func NewUserRepo(db db_repo.Repo) UserRepo {
|
||||
return &userRepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *userRepo) i() {}
|
||||
|
||||
func (u *userRepo) Create(ctx core.Context, user user_model.UserDemo) (id uint, err error) {
|
||||
err = u.db.GetDbW().WithContext(ctx).Create(&user).Error
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "[user_repo] create user err")
|
||||
}
|
||||
return user.Id, nil
|
||||
}
|
||||
|
||||
func (u *userRepo) getUserByID(ctx core.Context, id uint) (*user_model.UserDemo, error) {
|
||||
data := new(user_model.UserDemo)
|
||||
err := u.db.GetDbR().WithContext(ctx).First(data, id).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, errors.Wrap(err, "[user_demo] get user data err")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (u *userRepo) UpdateNickNameByID(ctx core.Context, id uint, nickname string) (err error) {
|
||||
user, err := u.getUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "[user_demo] update user data err")
|
||||
}
|
||||
return u.db.GetDbW().WithContext(ctx).Model(user).Update("nick_name", nickname).Error
|
||||
}
|
||||
|
||||
func (u *userRepo) GetUserByUserName(ctx core.Context, username string) (*user_model.UserDemo, error) {
|
||||
data := new(user_model.UserDemo)
|
||||
err := u.db.GetDbR().
|
||||
WithContext(ctx).
|
||||
Select([]string{"id", "user_name", "nick_name", "mobile"}).
|
||||
Where("user_name = ?", username).
|
||||
First(data).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, errors.Wrap(err, "[user_demo] get user data err")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/code"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/token"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/errno"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/token"
|
||||
)
|
||||
|
||||
func AuthHandler(ctx core.Context) (userId int, userName string, err errno.Error) {
|
||||
auth := ctx.GetHeader("Authorization")
|
||||
if auth == "" {
|
||||
err = errno.ErrSignParam
|
||||
err = code.ErrSignParam
|
||||
return
|
||||
}
|
||||
|
||||
claims, errParse := token.Parse(auth)
|
||||
cfg := configs.Get().JWT
|
||||
claims, errParse := token.New(cfg.Secret).Parse(auth)
|
||||
if errParse != nil {
|
||||
err = errno.ErrSignParam
|
||||
err = code.ErrSignParam
|
||||
return
|
||||
}
|
||||
|
||||
userId = claims.UserID
|
||||
if userId <= 0 {
|
||||
err = errno.ErrSignParam
|
||||
err = code.ErrSignParam
|
||||
return
|
||||
}
|
||||
userName = claims.UserName
|
||||
55
internal/api/router/router.go
Normal file
55
internal/api/router/router.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
|
||||
auth "github.com/xinliangnote/go-gin-api/internal/api/router/middleware"
|
||||
"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/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func NewHTTPMux(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) (core.Mux, error) {
|
||||
|
||||
if logger == nil {
|
||||
return nil, errors.New("logger required")
|
||||
}
|
||||
|
||||
mux, err := core.New(logger,
|
||||
core.WithEnableCors(),
|
||||
core.WithEnableRate(),
|
||||
core.WithPanicNotify(notify.OnPanicNotify),
|
||||
core.WithRecordMetrics(metrics.RecordMetrics),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
demoHandler := demo.NewDemo(logger)
|
||||
userHandler := user_handler.NewUserDemo(logger, db, cache)
|
||||
|
||||
u := mux.Group("/user")
|
||||
{
|
||||
u.POST("/login", userHandler.Login())
|
||||
u.POST("/create", userHandler.Create())
|
||||
u.GET("/info/:username", userHandler.Detail())
|
||||
u.POST("/update", userHandler.UpdateNickNameByID())
|
||||
}
|
||||
|
||||
d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证
|
||||
{
|
||||
d.GET("user/:name", demoHandler.User())
|
||||
|
||||
// 模拟数据
|
||||
d.GET("get/:name", demoHandler.Get(), core.DisableTrace)
|
||||
d.POST("post", demoHandler.Post(), core.DisableTrace)
|
||||
}
|
||||
|
||||
return mux, nil
|
||||
}
|
||||
10
internal/api/service/README.md
Normal file
10
internal/api/service/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## service
|
||||
|
||||
业务逻辑层。
|
||||
|
||||
处于 `controller` 层和 `repository` 层之间,依赖接口开发。
|
||||
|
||||
命令规范:
|
||||
|
||||
- 包名以 `_service` 结尾。
|
||||
|
||||
67
internal/api/service/user_service/user.go
Normal file
67
internal/api/service/user_service/user.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package user_service
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/model/user_model"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/cache_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
var _ UserService = (*userSer)(nil)
|
||||
|
||||
type UserService interface {
|
||||
// i 为了避免被其他包实现
|
||||
i()
|
||||
|
||||
Create(ctx core.Context, user *user_model.CreateRequest) (id uint, err error)
|
||||
UpdateNickNameByID(ctx core.Context, id uint, username string) (err error)
|
||||
GetUserByUserName(ctx core.Context, username string) (user *user_model.UserDemo, err error)
|
||||
}
|
||||
|
||||
type userSer struct {
|
||||
db db_repo.Repo
|
||||
cache cache_repo.Repo
|
||||
userRepo user_demo_repo.UserRepo
|
||||
}
|
||||
|
||||
func NewUserService(db db_repo.Repo, cache cache_repo.Repo) UserService {
|
||||
userRepo := user_demo_repo.NewUserRepo(db)
|
||||
return &userSer{
|
||||
db: db,
|
||||
cache: cache,
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *userSer) i() {}
|
||||
|
||||
func (u *userSer) Create(ctx core.Context, user *user_model.CreateRequest) (id uint, err error) {
|
||||
create := user_model.UserDemo{
|
||||
UserName: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Mobile: user.Mobile,
|
||||
}
|
||||
|
||||
id, err = u.userRepo.Create(ctx, create)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *userSer) UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) {
|
||||
err = u.userRepo.UpdateNickNameByID(ctx, id, username)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_model.UserDemo, err error) {
|
||||
user, err = u.userRepo.GetUserByUserName(ctx, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/pkg/env"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var config = new(Config)
|
||||
|
||||
type Config struct {
|
||||
Mail struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Pass string `mapstructure:"pass"`
|
||||
To string `mapstructure:"to"`
|
||||
} `mapstructure:"mail"`
|
||||
|
||||
JWT struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
} `mapstructure:"jwt"`
|
||||
|
||||
Aes struct {
|
||||
Key string `mapstructure:"key"`
|
||||
Iv string `mapstructure:"iv"`
|
||||
} `mapstructure:"aes"`
|
||||
|
||||
Rsa struct {
|
||||
Private string `mapstructure:"private"`
|
||||
Public string `mapstructure:"public"`
|
||||
} `mapstructure:"rsa"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
viper.SetConfigName(env.Active().Value() + "_configs")
|
||||
viper.SetConfigType("toml")
|
||||
viper.AddConfigPath("./configs")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Get() Config {
|
||||
return *config
|
||||
}
|
||||
|
||||
func ProjectName() string {
|
||||
return "go-gin-api"
|
||||
}
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/journal"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/trace"
|
||||
"github.com/xinliangnote/go-gin-api/pkg/errno"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
@@ -18,11 +19,11 @@ import (
|
||||
|
||||
type HandlerFunc func(c Context)
|
||||
|
||||
type Journal = journal.T
|
||||
type Trace = trace.T
|
||||
|
||||
const (
|
||||
_Alias = "_alias_"
|
||||
_JournalName = "_journal_"
|
||||
_TraceName = "_trace_"
|
||||
_LoggerName = "_logger_"
|
||||
_BodyName = "_body_"
|
||||
_PayloadName = "_payload_"
|
||||
@@ -78,9 +79,9 @@ type Context interface {
|
||||
// Redirect 重定向
|
||||
Redirect(code int, location string)
|
||||
|
||||
Journal() Journal
|
||||
setJournal(journal Journal)
|
||||
disableJournal()
|
||||
Trace() Trace
|
||||
setTrace(trace Trace)
|
||||
disableTrace()
|
||||
|
||||
Logger() *zap.Logger
|
||||
setLogger(logger *zap.Logger)
|
||||
@@ -109,6 +110,11 @@ type Context interface {
|
||||
Host() string
|
||||
Path() string
|
||||
URI() string
|
||||
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
Value(key interface{}) interface{}
|
||||
}
|
||||
|
||||
type context struct {
|
||||
@@ -121,7 +127,7 @@ func (c *context) init() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.ctx.Set(_BodyName, body) // cache body是为了journal使用
|
||||
c.ctx.Set(_BodyName, body) // cache body是为了trace使用
|
||||
c.ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // re-construct req body
|
||||
}
|
||||
|
||||
@@ -161,21 +167,21 @@ func (c *context) Redirect(code int, location string) {
|
||||
c.ctx.Redirect(code, location)
|
||||
}
|
||||
|
||||
func (c *context) Journal() Journal {
|
||||
j, ok := c.ctx.Get(_JournalName)
|
||||
if !ok || j == nil {
|
||||
func (c *context) Trace() Trace {
|
||||
t, ok := c.ctx.Get(_TraceName)
|
||||
if !ok || t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return j.(Journal)
|
||||
return t.(Trace)
|
||||
}
|
||||
|
||||
func (c *context) setJournal(journal Journal) {
|
||||
c.ctx.Set(_JournalName, journal)
|
||||
func (c *context) setTrace(trace Trace) {
|
||||
c.ctx.Set(_TraceName, trace)
|
||||
}
|
||||
|
||||
func (c *context) disableJournal() {
|
||||
c.setJournal(nil)
|
||||
func (c *context) disableTrace() {
|
||||
c.setTrace(nil)
|
||||
}
|
||||
|
||||
func (c *context) Logger() *zap.Logger {
|
||||
@@ -303,3 +309,26 @@ func (c *context) URI() string {
|
||||
uri, _ := url.QueryUnescape(c.ctx.Request.URL.RequestURI())
|
||||
return uri
|
||||
}
|
||||
|
||||
func (c *context) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Value(key interface{}) interface{} {
|
||||
if key == 0 {
|
||||
return c.ctx.Request
|
||||
}
|
||||
if keyAsString, ok := key.(string); ok {
|
||||
val, _ := c.ctx.Get(keyAsString)
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"time"
|
||||
|
||||
_ "github.com/xinliangnote/go-gin-api/docs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/journal"
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/code"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/trace"
|
||||
"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/gin-contrib/pprof"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -105,9 +107,8 @@ func WithEnableRate() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// DisableJournal 标识某些请求不记录journal
|
||||
func DisableJournal(ctx Context) {
|
||||
ctx.disableJournal()
|
||||
func DisableTrace(ctx Context) {
|
||||
ctx.disableTrace()
|
||||
}
|
||||
|
||||
// AliasForRecordMetrics 对请求uri起个别名,用于prometheus记录指标。
|
||||
@@ -242,8 +243,10 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
|
||||
fmt.Println(color.Blue(_UI))
|
||||
|
||||
fmt.Println(color.Green(fmt.Sprintf("* [register -env %s]", env.Active().Value())))
|
||||
|
||||
// withoutLogPaths 这些请求,默认不记录日志
|
||||
withoutJournalPaths := map[string]bool{
|
||||
withoutTracePaths := map[string]bool{
|
||||
"/metrics": true,
|
||||
|
||||
"/debug/pprof/": true,
|
||||
@@ -259,6 +262,9 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
"/debug/pprof/threadcreate": true,
|
||||
|
||||
"/favicon.ico": true,
|
||||
|
||||
"/h/ping": true,
|
||||
"/h/info": true,
|
||||
}
|
||||
|
||||
opt := new(option)
|
||||
@@ -318,11 +324,11 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
context.init()
|
||||
context.setLogger(logger)
|
||||
|
||||
if !withoutJournalPaths[ctx.Request.URL.Path] {
|
||||
if journalID := context.GetHeader(journal.JournalHeader); journalID != "" {
|
||||
context.setJournal(journal.NewJournal(journalID))
|
||||
if !withoutTracePaths[ctx.Request.URL.Path] {
|
||||
if traceId := context.GetHeader(trace.Header); traceId != "" {
|
||||
context.setTrace(trace.New(traceId))
|
||||
} else {
|
||||
context.setJournal(journal.NewJournal(""))
|
||||
context.setTrace(trace.New(""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +336,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
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.SetPayload(errno.ErrServer)
|
||||
context.SetPayload(code.ErrServer)
|
||||
|
||||
if notify := opt.panicNotify; notify != nil {
|
||||
notify(context, err, stackInfo)
|
||||
@@ -342,8 +348,10 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
}
|
||||
|
||||
var (
|
||||
response errno.Error
|
||||
abortErr error
|
||||
response errno.Error
|
||||
businessCode int
|
||||
businessCodeMsg string
|
||||
abortErr error
|
||||
)
|
||||
|
||||
if ctx.IsAborted() {
|
||||
@@ -360,12 +368,14 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
if x := context.Journal(); x != nil {
|
||||
context.SetHeader(journal.JournalHeader, x.ID())
|
||||
if x := context.Trace(); x != nil {
|
||||
context.SetHeader(trace.Header, x.ID())
|
||||
response.WithID(x.ID())
|
||||
} else {
|
||||
response.WithID("")
|
||||
}
|
||||
businessCode = response.GetCode()
|
||||
businessCodeMsg = response.GetMsg()
|
||||
ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
@@ -375,23 +385,18 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
uri = alias
|
||||
}
|
||||
|
||||
businessCode := 0
|
||||
if response != nil {
|
||||
businessCode = response.GetCode()
|
||||
}
|
||||
|
||||
opt.recordMetrics(context.Method(), uri, !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK, ctx.Writer.Status(), businessCode, time.Since(ts).Seconds())
|
||||
}
|
||||
|
||||
var j *journal.Journal
|
||||
if x := context.Journal(); x != nil {
|
||||
j = x.(*journal.Journal)
|
||||
var t *trace.Trace
|
||||
if x := context.Trace(); x != nil {
|
||||
t = x.(*trace.Trace)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
decodedURL, _ := url.QueryUnescape(ctx.Request.URL.RequestURI())
|
||||
j.WithRequest(&journal.Request{
|
||||
t.WithRequest(&trace.Request{
|
||||
TTL: "un-limit",
|
||||
Method: ctx.Request.Method,
|
||||
DecodedURL: decodedURL,
|
||||
@@ -399,17 +404,19 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
Body: string(context.RawData()),
|
||||
})
|
||||
|
||||
j.WithResponse(&journal.Response{
|
||||
Header: ctx.Writer.Header(),
|
||||
StatusCode: ctx.Writer.Status(),
|
||||
Status: http.StatusText(ctx.Writer.Status()),
|
||||
Body: response,
|
||||
t.WithResponse(&trace.Response{
|
||||
Header: ctx.Writer.Header(),
|
||||
HttpCode: ctx.Writer.Status(),
|
||||
HttpCodeMsg: http.StatusText(ctx.Writer.Status()),
|
||||
BusinessCode: businessCode,
|
||||
BusinessCodeMsg: businessCodeMsg,
|
||||
Body: response,
|
||||
})
|
||||
|
||||
j.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
|
||||
j.CostSeconds = time.Since(ts).Seconds()
|
||||
t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
|
||||
t.CostSeconds = time.Since(ts).Seconds()
|
||||
|
||||
logger.Info("interceptor", zap.Any("journal", j))
|
||||
logger.Info("interceptor", zap.Any("trace", t))
|
||||
}()
|
||||
|
||||
ctx.Next()
|
||||
@@ -422,7 +429,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
defer releaseContext(context)
|
||||
|
||||
if !limiter.Allow() {
|
||||
context.SetPayload(errno.ErrManyRequest)
|
||||
context.SetPayload(code.ErrManyRequest)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
@@ -430,24 +437,26 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
|
||||
})
|
||||
}
|
||||
|
||||
mux.engine.NoMethod(wrapHandlers(DisableJournal)...)
|
||||
mux.engine.NoRoute(wrapHandlers(DisableJournal)...)
|
||||
mux.engine.NoMethod(wrapHandlers(DisableTrace)...)
|
||||
mux.engine.NoRoute(wrapHandlers(DisableTrace)...)
|
||||
|
||||
h := mux.Group("/h", DisableJournal)
|
||||
h := mux.Group("/h")
|
||||
{
|
||||
h.GET("/ping", func(ctx Context) {
|
||||
ctx.SetPayload(errno.OK.WithData("pong"))
|
||||
ctx.SetPayload(code.OK.WithData("pong"))
|
||||
})
|
||||
|
||||
h.GET("/info", func(ctx Context) {
|
||||
resp := &struct {
|
||||
Header interface{} `json:"header"`
|
||||
Ts time.Time `json:"ts"`
|
||||
Env string `json:"env"`
|
||||
}{
|
||||
Header: ctx.Header(),
|
||||
Ts: time.Now(),
|
||||
Env: env.Active().Value(),
|
||||
}
|
||||
ctx.SetPayload(errno.OK.WithData(resp))
|
||||
ctx.SetPayload(code.OK.WithData(resp))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package errno
|
||||
|
||||
import "net/http"
|
||||
|
||||
var (
|
||||
// OK
|
||||
OK = NewError(0, "OK")
|
||||
|
||||
// 服务级错误码
|
||||
ErrServer = NewError(10101, http.StatusText(http.StatusInternalServerError))
|
||||
Err404 = NewError(10102, http.StatusText(http.StatusNotFound))
|
||||
ErrManyRequest = NewError(10103, "Too many requests")
|
||||
|
||||
ErrParam = NewError(10002, "参数有误")
|
||||
ErrSignParam = NewError(10003, "签名参数有误")
|
||||
|
||||
// 模块级错误码 - 用户模块
|
||||
ErrUser = NewError(20101, "非法用户")
|
||||
ErrUserCaptcha = NewError(20102, "用户验证码有误")
|
||||
|
||||
// ...
|
||||
)
|
||||
@@ -1,75 +0,0 @@
|
||||
package errno
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var _ Error = (*err)(nil)
|
||||
|
||||
type Error interface {
|
||||
// i 为了避免被其他包实现
|
||||
i()
|
||||
// WithData 设置成功时返回的数据
|
||||
WithData(data interface{}) Error
|
||||
// WithID 设置当前请求的唯一ID
|
||||
WithID(id string) Error
|
||||
// GetCode 获取 Code
|
||||
GetCode() int
|
||||
// GetMsg 获取 Msg
|
||||
GetMsg() string
|
||||
// ToString 返回 JSON 格式的错误详情
|
||||
ToString() string
|
||||
}
|
||||
|
||||
type err struct {
|
||||
Code int `json:"code"` // 业务编码
|
||||
Msg string `json:"msg"` // 错误描述
|
||||
Data interface{} `json:"data"` // 成功时返回的数据
|
||||
ID string `json:"id,omitempty"` // 当前请求的唯一ID,便于问题定位,忽略也可以
|
||||
}
|
||||
|
||||
func NewError(code int, msg string) Error {
|
||||
return &err{
|
||||
Code: code,
|
||||
Msg: msg,
|
||||
Data: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *err) i() {}
|
||||
|
||||
func (e *err) WithData(data interface{}) Error {
|
||||
e.Data = data
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *err) WithID(id string) Error {
|
||||
e.ID = id
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *err) GetCode() int {
|
||||
return e.Code
|
||||
}
|
||||
|
||||
func (e *err) GetMsg() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
// ToString 返回 JSON 格式的错误详情
|
||||
func (e *err) ToString() string {
|
||||
err := &struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
ID string `json:"id,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Msg: e.Msg,
|
||||
Data: e.Data,
|
||||
ID: e.ID,
|
||||
}
|
||||
|
||||
raw, _ := json.Marshal(err)
|
||||
return string(raw)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package journal
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// JournalHeader http/rpc header中的名字
|
||||
const JournalHeader = "Journal-ID"
|
||||
|
||||
var _ T = (*Journal)(nil)
|
||||
var _ T = (*Dialog)(nil)
|
||||
|
||||
// T 约束
|
||||
type T interface {
|
||||
ID() string
|
||||
t()
|
||||
}
|
||||
|
||||
// Journal 包含一次rpc请求的全部参数和内部调用其它方接口的过程
|
||||
type Journal struct {
|
||||
mux sync.Mutex
|
||||
Identifier string `json:"id"`
|
||||
Request *Request `json:"request"`
|
||||
Response *Response `json:"response"`
|
||||
Dialogs []*Dialog `json:"dialogs"`
|
||||
Success bool `json:"success"`
|
||||
CostSeconds float64 `json:"cost_seconds"`
|
||||
}
|
||||
|
||||
// NewJournal 创建Journal
|
||||
func NewJournal(id string) *Journal {
|
||||
if id == "" {
|
||||
buf := make([]byte, 10)
|
||||
io.ReadFull(rand.Reader, buf)
|
||||
id = string(hex.EncodeToString(buf))
|
||||
}
|
||||
|
||||
return &Journal{
|
||||
Identifier: id,
|
||||
}
|
||||
}
|
||||
|
||||
// ID 唯一标识符
|
||||
func (j *Journal) ID() string {
|
||||
return j.Identifier
|
||||
}
|
||||
|
||||
// WithRequest 设置request
|
||||
func (j *Journal) WithRequest(req *Request) *Journal {
|
||||
j.Request = req
|
||||
return j
|
||||
}
|
||||
|
||||
// WithResponse 设置response
|
||||
func (j *Journal) WithResponse(resp *Response) *Journal {
|
||||
j.Response = resp
|
||||
return j
|
||||
}
|
||||
|
||||
// AppendDialog 安全的追加内部调用过程dialog
|
||||
func (j *Journal) AppendDialog(dialog *Dialog) *Journal {
|
||||
if dialog == nil {
|
||||
return j
|
||||
}
|
||||
|
||||
j.mux.Lock()
|
||||
defer j.mux.Unlock()
|
||||
|
||||
j.Dialogs = append(j.Dialogs, dialog)
|
||||
return j
|
||||
}
|
||||
|
||||
func (j *Journal) t() {}
|
||||
|
||||
// Request 请求信息
|
||||
type Request struct {
|
||||
TTL string `json:"ttl"`
|
||||
Method string `json:"method"`
|
||||
DecodedURL string `json:"decoded_url"`
|
||||
Header interface{} `json:"header"`
|
||||
Body interface{} `json:"body"`
|
||||
}
|
||||
|
||||
// Response 响应信息
|
||||
type Response struct {
|
||||
Header interface{} `json:"header"`
|
||||
StatusCode int `json:"status_code"`
|
||||
Status string `json:"status"`
|
||||
Body interface{} `json:"body"`
|
||||
CostSeconds float64 `json:"cost_seconds"`
|
||||
}
|
||||
|
||||
// Dialog 内部调用其它方接口的会话信息;失败时会有retry操作,所以response会有多次。
|
||||
type Dialog struct {
|
||||
mux sync.Mutex
|
||||
Request *Request `json:"request"`
|
||||
Responses []*Response `json:"responses"`
|
||||
Success bool `json:"success"`
|
||||
CostSeconds float64 `json:"cost_seconds"`
|
||||
}
|
||||
|
||||
// ID ...
|
||||
func (d *Dialog) ID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// AppendResponse 按转的追加response信息
|
||||
func (d *Dialog) AppendResponse(resp *Response) {
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.mux.Lock()
|
||||
d.Responses = append(d.Responses, resp)
|
||||
d.mux.Unlock()
|
||||
}
|
||||
|
||||
func (d *Dialog) t() {}
|
||||
@@ -1,7 +1,7 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/configs"
|
||||
"github.com/xinliangnote/go-gin-api/configs"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/notify/mail"
|
||||
|
||||
@@ -16,7 +16,7 @@ func OnPanicNotify(ctx core.Context, err interface{}, stackInfo string) {
|
||||
return
|
||||
}
|
||||
|
||||
subject, body, htmlErr := mail.NewPanicHTMLEmail(ctx.Method(), ctx.Host(), ctx.URI(), ctx.Journal().ID(), err, stackInfo)
|
||||
subject, body, htmlErr := mail.NewPanicHTMLEmail(ctx.Method(), ctx.Host(), ctx.URI(), ctx.Trace().ID(), err, stackInfo)
|
||||
if htmlErr != nil {
|
||||
ctx.Logger().Error("NewPanicHTMLEmail error", zap.Error(htmlErr))
|
||||
return
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
//var secret = configs.Get().JWT.Secret
|
||||
var secret = "i1ydX9RtHyuJTrw7frcu"
|
||||
|
||||
type claims struct {
|
||||
UserID int
|
||||
UserName string
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func Sign(userId int, userName string) (tokenString string, err error) {
|
||||
// The token content.
|
||||
// iss: (Issuer)签发者
|
||||
// iat: (Issued At)签发时间,用Unix时间戳表示
|
||||
// exp: (Expiration Time)过期时间,用Unix时间戳表示
|
||||
// aud: (Audience)接收该JWT的一方
|
||||
// sub: (Subject)该JWT的主题
|
||||
// nbf: (Not Before)不要早于这个时间
|
||||
// jti: (JWT ID)用于标识JWT的唯一ID
|
||||
claims := claims{
|
||||
userId,
|
||||
userName,
|
||||
jwt.StandardClaims{
|
||||
NotBefore: time.Now().Unix(),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
||||
Issuer: "go-gin-api",
|
||||
},
|
||||
}
|
||||
tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
||||
return
|
||||
}
|
||||
|
||||
func Parse(tokenString string) (*claims, error) {
|
||||
tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
if tokenClaims != nil {
|
||||
if claims, ok := tokenClaims.Claims.(*claims); ok && tokenClaims.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 执行 Test 时,先将 token.secret 设置值
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
tokenString, err := Sign(123456789, "xinliangnote")
|
||||
if err != nil {
|
||||
t.Error("sign error", err)
|
||||
return
|
||||
}
|
||||
t.Log(tokenString)
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1Njc4OSwidXNlcm5hbWUiOiJ4aW5saWFuZyIsImV4cCI6MTYwOTQ2NzcwNCwiaWF0IjoxNjA5MzgxMzA0LCJpc3MiOiJnby1naW4tYXBpIiwibmJmIjoxNjA5MzgxMzA0fQ.hccv8F713WpKcwiSldBrFLZz_2SZzOTPedPi-8ps7M4"
|
||||
user, err := Parse(tokenString)
|
||||
if err != nil {
|
||||
t.Error("parse error", err)
|
||||
return
|
||||
}
|
||||
t.Log(user)
|
||||
}
|
||||
81
internal/pkg/trace/README.md
Normal file
81
internal/pkg/trace/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
## trace
|
||||
|
||||
一个用于开发调试的辅助工具。
|
||||
|
||||
可以实时显示当前页面的操作的请求信息、运行情况、SQL执行、错误提示等。
|
||||
|
||||
- `trace.go` 主入口文件;
|
||||
- `dialog.go` 处理 third_party_requests 记录;
|
||||
- `debug.go` 处理 debug 记录;
|
||||
|
||||
#### 数据格式
|
||||
|
||||
##### trace_id
|
||||
|
||||
当前 trace 的 ID,例如:938ff86be98439c6c1a7,便于搜索使用。
|
||||
|
||||
##### request
|
||||
|
||||
请求信息,会包括:
|
||||
|
||||
- ttl 请求超时时间,例如:2s 或 un-limit
|
||||
- method 请求方式,例如:GET 或 POST
|
||||
- decoded_url 请求地址
|
||||
- header 请求头信息
|
||||
- body 请求体信息
|
||||
|
||||
##### response
|
||||
|
||||
- header 响应头信息
|
||||
- body 响应提信息
|
||||
- business_code 业务码,例如:10010
|
||||
- business_code_msg 业务码信息,例如:签名错误
|
||||
- http_code HTTP 状态码,例如:200
|
||||
- http_code_msg HTTP 状态码信息,例如:OK
|
||||
- cost_seconds 耗费时长:单位秒,比如 0.001105661
|
||||
|
||||
##### third_party_requests
|
||||
|
||||
每一个第三方 http 请求都会生成如下的一组数据,多个请求会生成多组数据。
|
||||
|
||||
- request,同上 request 结构一致
|
||||
- response,同上 response 结构一致
|
||||
- success,是否成功,true 或 false
|
||||
- cost_seconds,耗费时长:单位秒
|
||||
|
||||
注意:response 中的 business_code、business_code_msg 为空,因为各个第三方返回结构不同,这两个字段为空。
|
||||
|
||||
##### sqls
|
||||
|
||||
执行的 SQL 信息,多个 SQL 会记录多组数据。
|
||||
|
||||
- time,时间,格式:2006-01-02 15:04:05
|
||||
- src,文件地址和行号
|
||||
- duration,执行时长,单位:秒
|
||||
- sql,SQL 语句
|
||||
- rows_affected,影响行数
|
||||
|
||||
##### debugs
|
||||
|
||||
- key 打印的标示
|
||||
- value 打印的值
|
||||
|
||||
```cassandraql
|
||||
// 调试时,使用这个方法:
|
||||
p.Print("key", "value", p.WithTrace(c.Trace()))
|
||||
```
|
||||
|
||||
只有参数中增加了 `p.WithTrace(c.Trace())`,才会记录到 `debugs` 中。
|
||||
|
||||
##### success
|
||||
|
||||
是否成功,true 或 false
|
||||
|
||||
```cassandraql
|
||||
success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
|
||||
```
|
||||
|
||||
##### cost_seconds
|
||||
|
||||
耗费时长:单位秒,比如 0.001105661
|
||||
|
||||
6
internal/pkg/trace/debug.go
Normal file
6
internal/pkg/trace/debug.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package trace
|
||||
|
||||
type Debug struct {
|
||||
Key string `json:"key"` //标示
|
||||
Value interface{} `json:"value"` //值
|
||||
}
|
||||
32
internal/pkg/trace/dialog.go
Normal file
32
internal/pkg/trace/dialog.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package trace
|
||||
|
||||
import "sync"
|
||||
|
||||
var _ D = (*Dialog)(nil)
|
||||
|
||||
type D interface {
|
||||
i()
|
||||
AppendResponse(resp *Response)
|
||||
}
|
||||
|
||||
// Dialog 内部调用其它方接口的会话信息;失败时会有retry操作,所以response会有多次。
|
||||
type Dialog struct {
|
||||
mux sync.Mutex
|
||||
Request *Request `json:"request"` // 请求信息
|
||||
Responses []*Response `json:"responses"` // 返回信息
|
||||
Success bool `json:"success"` // 是否成功,true 或 false
|
||||
CostSeconds float64 `json:"cost_seconds"` // 执行时长,单位:秒
|
||||
}
|
||||
|
||||
func (d *Dialog) i() {}
|
||||
|
||||
// AppendResponse 按转的追加response信息
|
||||
func (d *Dialog) AppendResponse(resp *Response) {
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.mux.Lock()
|
||||
d.Responses = append(d.Responses, resp)
|
||||
d.mux.Unlock()
|
||||
}
|
||||
9
internal/pkg/trace/sql.go
Normal file
9
internal/pkg/trace/sql.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package trace
|
||||
|
||||
type SQL struct {
|
||||
Time string `json:"time"` // 时间,格式:2006-01-02 15:04:05
|
||||
Src string `json:"src"` // 文件地址和行号
|
||||
Duration float64 `json:"duration"` // 执行时长,单位:秒
|
||||
SQL string `json:"sql"` // SQL 语句
|
||||
Rows int64 `json:"rows_affected"` // 影响行数
|
||||
}
|
||||
124
internal/pkg/trace/trace.go
Normal file
124
internal/pkg/trace/trace.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const Header = "TRACE-ID"
|
||||
|
||||
var _ T = (*Trace)(nil)
|
||||
|
||||
type T interface {
|
||||
i()
|
||||
ID() string
|
||||
WithRequest(req *Request) *Trace
|
||||
WithResponse(resp *Response) *Trace
|
||||
AppendDialog(dialog *Dialog) *Trace
|
||||
AppendSQL(sql *SQL) *Trace
|
||||
}
|
||||
|
||||
// Trace 记录的参数
|
||||
type Trace struct {
|
||||
mux sync.Mutex
|
||||
Identifier string `json:"trace_id"`
|
||||
Request *Request `json:"request"`
|
||||
Response *Response `json:"response"`
|
||||
ThirdPartyRequests []*Dialog `json:"third_party_requests"`
|
||||
Debugs []*Debug `json:"debugs"`
|
||||
SQLs []*SQL `json:"sqls"`
|
||||
Success bool `json:"success"`
|
||||
CostSeconds float64 `json:"cost_seconds"`
|
||||
}
|
||||
|
||||
// Request 请求信息
|
||||
type Request struct {
|
||||
TTL string `json:"ttl"`
|
||||
Method string `json:"method"`
|
||||
DecodedURL string `json:"decoded_url"`
|
||||
Header interface{} `json:"header"`
|
||||
Body interface{} `json:"body"`
|
||||
}
|
||||
|
||||
// Response 响应信息
|
||||
type Response struct {
|
||||
Header interface{} `json:"header"`
|
||||
Body interface{} `json:"body"`
|
||||
BusinessCode int `json:"business_code,omitempty"`
|
||||
BusinessCodeMsg string `json:"business_code_msg,omitempty"`
|
||||
HttpCode int `json:"http_code"`
|
||||
HttpCodeMsg string `json:"http_code_msg"`
|
||||
CostSeconds float64 `json:"cost_seconds"`
|
||||
}
|
||||
|
||||
func New(id string) *Trace {
|
||||
if id == "" {
|
||||
buf := make([]byte, 10)
|
||||
io.ReadFull(rand.Reader, buf)
|
||||
id = string(hex.EncodeToString(buf))
|
||||
}
|
||||
|
||||
return &Trace{
|
||||
Identifier: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trace) i() {}
|
||||
|
||||
// ID 唯一标识符
|
||||
func (t *Trace) ID() string {
|
||||
return t.Identifier
|
||||
}
|
||||
|
||||
// WithRequest 设置request
|
||||
func (t *Trace) WithRequest(req *Request) *Trace {
|
||||
t.Request = req
|
||||
return t
|
||||
}
|
||||
|
||||
// WithResponse 设置response
|
||||
func (t *Trace) WithResponse(resp *Response) *Trace {
|
||||
t.Response = resp
|
||||
return t
|
||||
}
|
||||
|
||||
// AppendDialog 安全的追加内部调用过程dialog
|
||||
func (t *Trace) AppendDialog(dialog *Dialog) *Trace {
|
||||
if dialog == nil {
|
||||
return t
|
||||
}
|
||||
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
t.ThirdPartyRequests = append(t.ThirdPartyRequests, dialog)
|
||||
return t
|
||||
}
|
||||
|
||||
// AppendDebug 追加 debug
|
||||
func (t *Trace) AppendDebug(debug *Debug) *Trace {
|
||||
if debug == nil {
|
||||
return t
|
||||
}
|
||||
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
t.Debugs = append(t.Debugs, debug)
|
||||
return t
|
||||
}
|
||||
|
||||
// AppendSQL 追加 SQL
|
||||
func (t *Trace) AppendSQL(sql *SQL) *Trace {
|
||||
if sql == nil {
|
||||
return t
|
||||
}
|
||||
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
|
||||
t.SQLs = append(t.SQLs, sql)
|
||||
return t
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo"
|
||||
"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/router/middleware"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func NewHTTPMux(logger *zap.Logger) (core.Mux, error) {
|
||||
|
||||
if logger == nil {
|
||||
return nil, errors.New("logger required")
|
||||
}
|
||||
|
||||
mux, err := core.New(logger,
|
||||
core.WithEnableCors(),
|
||||
core.WithEnableRate(),
|
||||
core.WithPanicNotify(notify.OnPanicNotify),
|
||||
core.WithRecordMetrics(metrics.RecordMetrics),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
demoHandler := demo.NewDemo(logger)
|
||||
|
||||
u := mux.Group("/user")
|
||||
{
|
||||
u.POST("/login", demoHandler.Login())
|
||||
}
|
||||
|
||||
d := mux.Group("/demo", core.WrapAuthHandler(middleware.AuthHandler)) //使用 auth 验证
|
||||
{
|
||||
d.GET("user/:name", demoHandler.User())
|
||||
|
||||
// 模拟数据
|
||||
d.GET("get/:name", demoHandler.Get(), core.DisableJournal)
|
||||
d.POST("post", demoHandler.Post(), core.DisableJournal)
|
||||
|
||||
// 测试加密性能
|
||||
d.GET("/rsa/test", demoHandler.RsaTest())
|
||||
d.GET("/aes/test", demoHandler.AesTest())
|
||||
d.GET("/md5/test", demoHandler.MD5Test())
|
||||
}
|
||||
|
||||
return mux, nil
|
||||
}
|
||||
Reference in New Issue
Block a user