This commit is contained in:
新亮
2021-01-23 16:46:01 +08:00
parent d17c58ffe1
commit 106d5b65d7
35 changed files with 811 additions and 502 deletions
+5 -4
View File
@@ -18,10 +18,11 @@ var (
// 模块级错误码 - 用户模块
ErrUser = errno.NewError(http.StatusBadRequest, 20101, "非法用户")
ErrUserCreate = errno.NewError(http.StatusBadRequest, 20102, "创建用户失败")
ErrUserUpdate = errno.NewError(http.StatusBadRequest, 20103, "更新用户失败")
ErrUserSearch = errno.NewError(http.StatusBadRequest, 20104, "查询用户失败")
ErrUserHTTP = errno.NewError(http.StatusBadRequest, 20105, "调用他方接口失败")
ErrUserName = errno.NewError(http.StatusBadRequest, 20102, "账号不能为空")
ErrUserCreate = errno.NewError(http.StatusBadRequest, 20103, "创建用户失败")
ErrUserUpdate = errno.NewError(http.StatusBadRequest, 20104, "更新用户失败")
ErrUserSearch = errno.NewError(http.StatusBadRequest, 20105, "查询用户失败")
ErrUserHTTP = errno.NewError(http.StatusBadRequest, 20106, "调用他方接口失败")
// ...
)
+72 -32
View File
@@ -3,21 +3,32 @@ package demo
import (
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/api/repository/third_party_request/go_gin_api_repo"
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/xinliangnote/go-gin-api/pkg/p"
"github.com/xinliangnote/go-gin-api/pkg/token"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type Demo struct {
logger *zap.Logger
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func NewDemo(logger *zap.Logger) *Demo {
func NewDemo(logger *zap.Logger, db db.Repo, cache cache.Repo) *Demo {
return &Demo{
logger: logger,
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
@@ -35,16 +46,16 @@ func (d *Demo) Get() core.HandlerFunc {
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(code.ErrParamBind)
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
if req.Name != "Tom" {
c.AbortWithError(code.ErrUser)
c.AbortWithError(code.ErrUser.WithErr(errors.New("req.Name != Tom")))
return
}
c.SetPayload(code.OK.WithData(&response{
c.Payload(code.OK.WithData(&response{
Name: "Tom",
Job: "Student",
}))
@@ -64,56 +75,70 @@ func (d *Demo) Post() core.HandlerFunc {
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindPostForm(req); err != nil {
c.AbortWithError(code.ErrParamBind)
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
if req.Name != "Jack" {
c.AbortWithError(code.ErrUser)
c.AbortWithError(code.ErrUser.WithErr(errors.New("req.Name != Jack")))
return
}
c.SetPayload(code.OK.WithData(&response{
c.Payload(code.OK.WithData(&response{
Name: "Jack",
Job: "Teacher",
}))
}
}
type request struct {
Name string `uri:"name"`
type authResponse struct {
Authorization string `json:"authorization"` // 签名
ExpireTime int64 `json:"expire_time"` // 过期时间
}
type response []struct {
type traceResponse []struct {
Name string `json:"name"` //用户名
Job string `json:"job"` //工作
}
// Get 获取用户信息
// @Summary 获取用户信息
// @Description 获取用户信息
// 获取授权信息
// @Summary 获取授权信息
// @Description 获取授权信息
// @Tags Demo
// @Accept json
// @Produce json
// @Param name path string true "用户名(Tom)"
// @Param Authorization header string true "签名"
// @Success 200 {object} response "用户信息"
// @Router /demo/user/{name} [get]
func (d *Demo) User() core.HandlerFunc {
// @Success 200 {object} authResponse "返回信息"
// @Router /auth/get [post]
func (d *Demo) Auth() core.HandlerFunc {
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(code.ErrParamBind)
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(code.ErrAuthorization.WithErr(err))
return
}
if req.Name != "Tom" {
c.AbortWithError(code.ErrUser)
return
}
res := new(authResponse)
res.Authorization = tokenString
res.ExpireTime = time.Now().Add(time.Hour * cfg.ExpireDuration).Unix()
c.Payload(code.OK.WithData(res))
}
}
res1, err := go_gin_api_repo.DemoGet(req.Name,
// Trace 示例
// @Summary Trace 示例
// @Description Trace 示例
// @Tags Demo
// @Accept json
// @Produce json
// @Param Authorization header string true "签名"
// @Success 200 {object} traceResponse "用户信息"
// @Router /demo/trace [get]
func (d *Demo) Trace() core.HandlerFunc {
return func(c core.Context) {
// 三方请求信息
res1, err := go_gin_api_repo.DemoGet("Tom",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
@@ -123,10 +148,14 @@ func (d *Demo) User() core.HandlerFunc {
if err != nil {
d.logger.Error("get [demo/get] err", zap.Error(err))
c.AbortWithError(code.ErrUserHTTP)
c.AbortWithError(code.ErrUserHTTP.WithErr(err))
return
}
// 调试信息
p.Println("res1.Data.Name", res1.Data.Name, p.WithTrace(c.Trace()))
// 三方请求信息
res2, err := go_gin_api_repo.DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
@@ -137,11 +166,22 @@ func (d *Demo) User() core.HandlerFunc {
if err != nil {
d.logger.Error("post [demo/post] err", zap.Error(err))
c.AbortWithError(code.ErrUserHTTP)
c.AbortWithError(code.ErrUserHTTP.WithErr(err))
return
}
data := &response{
// 调试信息
p.Println("res2.Data.Name", res2.Data.Name, p.WithTrace(c.Trace()))
// 执行 SQL 信息
d.userService.GetUserByUserName(c, "test_user")
// 执行 Redis 信息
_ = d.cache.Set("name", "tom", time.Minute*10, cache.WithTrace(c.Trace()))
val, _ := d.cache.Get("name", cache.WithTrace(c.Trace()))
p.Println("redis-name", val, p.WithTrace(c.Trace()))
data := &traceResponse{
{
Name: res1.Data.Name,
Job: res1.Data.Job,
@@ -151,6 +191,6 @@ func (d *Demo) User() core.HandlerFunc {
Job: res2.Data.Job,
},
}
c.SetPayload(code.OK.WithData(data))
c.Payload(code.OK.WithData(data))
}
}
+55 -60
View File
@@ -1,14 +1,14 @@
package user_handler
import (
"github.com/xinliangnote/go-gin-api/configs"
"errors"
"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/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/token"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"go.uber.org/zap"
)
@@ -18,24 +18,30 @@ var _ UserDemo = (*userDemo)(nil)
type UserDemo interface {
// i 为了避免被其他包实现
i()
// 创建用户
// Create 创建用户
Create() core.HandlerFunc
// 通过用户主键ID更新用户昵称
// UpdateNickNameByID 编辑用户 - 通过主键ID更新用户昵称
UpdateNickNameByID() core.HandlerFunc
// 用户登录
Login() core.HandlerFunc
// 用户详情
// Delete 删除用户 - 通过主键ID更新 is_deleted = 1
Delete() core.HandlerFunc
// Detail 用户详情
Detail() core.HandlerFunc
}
type userDemo struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func NewUserDemo(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) UserDemo {
func NewUserDemo(logger *zap.Logger, db db.Repo, cache cache.Repo) UserDemo {
return &userDemo{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
@@ -45,10 +51,11 @@ func (u *userDemo) i() {}
// 创建用户
// @Summary 创建用户
// @Description 创建用户
// @Tags Demo
// @Tags User
// @Accept json
// @Produce json
// @Param RequestInfo body user_model.CreateRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} user_model.CreateResponse "返回信息"
// @Router /user/create [post]
func (u *userDemo) Create() core.HandlerFunc {
@@ -56,102 +63,92 @@ func (u *userDemo) Create() core.HandlerFunc {
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.AbortWithError(code.ErrParamBind)
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
if req.UserName == "" {
c.AbortWithError(code.ErrUserName.WithErr(errors.New("req.UserName = ''")))
return
}
id, err := u.userService.Create(c, req)
if err != nil {
u.logger.Error("[user] Create err", zap.Error(err))
c.AbortWithError(code.ErrUserCreate)
c.AbortWithError(code.ErrUserCreate.WithErr(err))
return
}
res.Id = id
c.SetPayload(code.OK.WithData(res))
c.Payload(code.OK.WithData(res))
}
}
// 更新用户
// @Summary 更新用户
// @Description 更新用户
// @Tags Demo
// 编辑用户 - 通过用户主键ID更新用户
// @Summary 编辑用户 - 通过用户主键ID更新用户
// @Description 编辑用户 - 通过用户主键ID更新用户
// @Tags User
// @Accept json
// @Produce json
// @Param RequestInfo body user_model.UpdateNickNameByIDRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} user_model.UpdateNickNameByIDResponse "返回信息"
// @Router /user/update [post]
// @Router /user/update [put]
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.AbortWithError(code.ErrParamBind)
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
err := u.userService.UpdateNickNameByID(c, req.Id, req.NickName)
if err != nil {
u.logger.Error("[user] UpdateNickNameByID err", zap.Error(err))
c.AbortWithError(code.ErrUserUpdate)
c.AbortWithError(code.ErrUserUpdate.WithErr(err))
return
}
res.Id = req.Id
c.SetPayload(code.OK.WithData(res))
c.Payload(code.OK.WithData(res))
}
}
// 用户登录
// @Summary 用户登录
// @Description 用户登录
// @Tags Demo
// 删除用户 - 更新 is_deleted = 1
// @Summary 删除用户 - 更新 is_deleted = 1
// @Description 删除用户 - 更新 is_deleted = 1
// @Tags User
// @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 {
// @Param id path int true "用户ID"
// @Param Authorization header string true "签名"
// @Success 200 "返回信息"
// @Router /user/delete/{id} [patch]
func (u *userDemo) Delete() 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.AbortWithError(code.ErrParamBind)
req := new(user_model.DeleteRequest)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(req.UserID, req.UserName)
err := u.userService.Delete(c, req.Id)
if err != nil {
u.logger.Error("token sign err", zap.Error(err))
c.AbortWithError(code.ErrAuthorization)
c.AbortWithError(code.ErrUserUpdate.WithErr(err))
return
}
claims, err := token.New(cfg.Secret).Parse(tokenString)
if err != nil {
u.logger.Error("token parse err", zap.Error(err))
c.AbortWithError(code.ErrAuthorization)
return
}
res.Authorization = tokenString
res.ExpireTime = claims.ExpiresAt
c.SetPayload(code.OK.WithData(res))
c.Payload(code.OK.WithData(nil))
}
}
// 用户详情
// @Summary 用户详情
// @Description 用户详情
// @Tags Demo
// @Tags User
// @Accept json
// @Produce json
// @Param username path string true "用户名"
// @Param Authorization header string true "签名"
// @Success 200 {object} user_model.DetailResponse "返回信息"
// @Router /user/info/{username} [get]
func (u *userDemo) Detail() core.HandlerFunc {
@@ -159,15 +156,13 @@ func (u *userDemo) Detail() core.HandlerFunc {
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.AbortWithError(code.ErrParamBind)
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
user, err := u.userService.GetUserByUserName(c, req.UserName)
if err != nil {
u.logger.Error("[user] GetUserByUserName err", zap.Error(err))
c.AbortWithError(code.ErrUserSearch)
c.AbortWithError(code.ErrUserSearch.WithErr(err))
return
}
@@ -175,6 +170,6 @@ func (u *userDemo) Detail() core.HandlerFunc {
res.UserName = user.UserName
res.NickName = user.NickName
res.Mobile = user.Mobile
c.SetPayload(code.OK.WithData(res))
c.Payload(code.OK.WithData(res))
}
}
+4 -11
View File
@@ -28,7 +28,7 @@ type CreateRequest struct {
// user_handler Create Response
type CreateResponse struct {
Id uint `json:"id"` //主键ID
Id uint `json:"id"` // 主键ID
}
// user_handler UpdateNickNameByID Request
@@ -42,16 +42,9 @@ 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 Delete Request
type DeleteRequest struct {
Id uint `uri:"id"` // 用户ID
}
// user_handler Detail Request
@@ -2,8 +2,8 @@ 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/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/pkg/errors"
)
@@ -16,14 +16,15 @@ type UserRepo interface {
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)
Delete(ctx core.Context, id uint) (err error)
getUserByID(ctx core.Context, id uint) (*user_model.UserDemo, error)
}
type userRepo struct {
db db_repo.Repo
db db.Repo
}
func NewUserRepo(db db_repo.Repo) UserRepo {
func NewUserRepo(db db.Repo) UserRepo {
return &userRepo{
db: db,
}
@@ -32,7 +33,7 @@ func NewUserRepo(db db_repo.Repo) UserRepo {
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
err = u.db.GetDbW().WithContext(ctx.RequestContext()).Create(&user).Error
if err != nil {
return 0, errors.Wrap(err, "[user_repo] create user err")
}
@@ -41,7 +42,7 @@ func (u *userRepo) Create(ctx core.Context, user user_model.UserDemo) (id uint,
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
err := u.db.GetDbR().WithContext(ctx.RequestContext()).First(data, id).Where("is_deleted = ?", -1).Error
if err != nil {
return nil, errors.Wrap(err, "[user_demo] get user data err")
}
@@ -53,15 +54,23 @@ func (u *userRepo) UpdateNickNameByID(ctx core.Context, id uint, nickname string
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
return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("nick_name", nickname).Error
}
func (u *userRepo) Delete(ctx core.Context, id uint) (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.RequestContext()).Model(user).Update("is_deleted", 1).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).
WithContext(ctx.RequestContext()).
Select([]string{"id", "user_name", "nick_name", "mobile"}).
Where("user_name = ?", username).
Where("user_name = ? and is_deleted = ?", username, -1).
First(data).Error
if err != nil {
return nil, errors.Wrap(err, "[user_demo] get user data err")
@@ -1,4 +1,4 @@
package middleware
package auth
import (
"github.com/xinliangnote/go-gin-api/configs"
@@ -6,25 +6,27 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"
"github.com/pkg/errors"
)
func AuthHandler(ctx core.Context) (userId int, userName string, err errno.Error) {
func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Error) {
auth := ctx.GetHeader("Authorization")
if auth == "" {
err = code.ErrAuthorization
err = code.ErrAuthorization.WithErr(errors.New("Header 中缺少 Authorization 参数"))
return
}
cfg := configs.Get().JWT
claims, errParse := token.New(cfg.Secret).Parse(auth)
if errParse != nil {
err = code.ErrAuthorization
err = code.ErrAuthorization.WithErr(errParse)
return
}
userId = claims.UserID
if userId <= 0 {
err = code.ErrAuthorization
err = code.ErrAuthorization.WithErr(errors.New("claims.UserID <= 0 "))
return
}
userName = claims.UserName
+20 -11
View File
@@ -3,10 +3,10 @@ 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/api/router/middleware/auth"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
@@ -14,7 +14,7 @@ import (
"go.uber.org/zap"
)
func NewHTTPMux(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) (core.Mux, error) {
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo) (core.Mux, error) {
if logger == nil {
return nil, errors.New("logger required")
@@ -31,20 +31,29 @@ func NewHTTPMux(logger *zap.Logger, db db_repo.Repo, cache cache_repo.Repo) (cor
panic(err)
}
demoHandler := demo.NewDemo(logger)
demoHandler := demo.NewDemo(logger, db, cache)
userHandler := user_handler.NewUserDemo(logger, db, cache)
u := mux.Group("/user")
// user_demo CURD
user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler))
{
u.POST("/login", userHandler.Login())
u.POST("/create", userHandler.Create())
u.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
u.POST("/update", userHandler.UpdateNickNameByID())
user.POST("/create", userHandler.Create())
user.PUT("/update", userHandler.UpdateNickNameByID())
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}
// auth
a := mux.Group("/auth")
{
a.POST("/get", demoHandler.Auth())
}
// demo
d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证
{
d.GET("user/:name", core.AliasForRecordMetrics("/demo/user"), demoHandler.User())
// 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。
d.GET("/trace", demoHandler.Trace())
// 模拟数据
d.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get())
+14 -5
View File
@@ -2,10 +2,10 @@ 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/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
)
var _ UserService = (*userSer)(nil)
@@ -17,15 +17,16 @@ type UserService interface {
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)
Delete(ctx core.Context, id uint) (err error)
}
type userSer struct {
db db_repo.Repo
cache cache_repo.Repo
db db.Repo
cache cache.Repo
userRepo user_demo_repo.UserRepo
}
func NewUserService(db db_repo.Repo, cache cache_repo.Repo) UserService {
func NewUserService(db db.Repo, cache cache.Repo) UserService {
userRepo := user_demo_repo.NewUserRepo(db)
return &userSer{
db: db,
@@ -65,3 +66,11 @@ func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *us
}
return user, nil
}
func (u *userSer) Delete(ctx core.Context, id uint) (err error) {
err = u.userRepo.Delete(ctx, id)
if err != nil {
return nil
}
return nil
}
@@ -1,25 +1,40 @@
package cache_repo
package cache
import (
"time"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/trace"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/go-redis/redis/v7"
"github.com/pkg/errors"
)
type Option func(*option)
type Trace = trace.T
type option struct {
Trace *trace.Trace
Redis *trace.Redis
}
func newOption() *option {
return &option{}
}
var _ Repo = (*cacheRepo)(nil)
type Repo interface {
i()
Set(key, value string, ttl time.Duration) error
Get(key string) (string, error)
Set(key, value string, ttl time.Duration, options ...Option) error
Get(key string, options ...Option) (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
Incr(key string, options ...Option) int64
Close()
}
@@ -59,7 +74,23 @@ func redisConnect() (*redis.Client, error) {
}
// Set set some <key,value> into redis
func (c *cacheRepo) Set(key, value string, ttl time.Duration) error {
func (c *cacheRepo) Set(key, value string, ttl time.Duration, options ...Option) error {
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "set"
opt.Redis.Key = key
opt.Redis.Value = value
opt.Redis.TTL = ttl
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
if err := c.client.Set(key, value, ttl).Err(); err != nil {
return errors.Wrapf(err, "redis set key: %s err", key)
}
@@ -68,7 +99,21 @@ func (c *cacheRepo) Set(key, value string, ttl time.Duration) error {
}
// Get get some key from redis
func (c *cacheRepo) Get(key string) (string, error) {
func (c *cacheRepo) Get(key string, options ...Option) (string, error) {
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "get"
opt.Redis.Key = key
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
value, err := c.client.Get(key).Result()
if err != nil {
return "", errors.Wrapf(err, "redis get key: %s err", key)
@@ -109,7 +154,20 @@ func (c *cacheRepo) Del(keys ...string) bool {
return value > 0
}
func (c *cacheRepo) Incr(key string) int64 {
func (c *cacheRepo) Incr(key string, options ...Option) int64 {
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "incr"
opt.Redis.Key = key
opt.Trace.AppendRedis(opt.Redis)
}
}()
for _, f := range options {
f(opt)
}
value, _ := c.client.Incr(key).Result()
return value
}
@@ -118,3 +176,13 @@ func (c *cacheRepo) Incr(key string) int64 {
func (c *cacheRepo) Close() {
c.client.Close()
}
// WithTrace 设置trace信息
func WithTrace(t Trace) Option {
return func(opt *option) {
if t != nil {
opt.Trace = t.(*trace.Trace)
opt.Redis = new(trace.Redis)
}
}
}
+53 -49
View File
@@ -2,12 +2,12 @@ package core
import (
"bytes"
stdctx "context"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/xinliangnote/go-gin-api/internal/pkg/trace"
"github.com/xinliangnote/go-gin-api/pkg/errno"
@@ -55,72 +55,90 @@ var _ Context = (*context)(nil)
type Context interface {
init()
// ShouldBindQuery 反序列化querystring
// tag: `form:"xxx"` (注:不要写成query)
// ShouldBindQuery 反序列化 querystring
// tag: `form:"xxx"` (注:不要写成 query)
ShouldBindQuery(obj interface{}) error
// ShouldBindPostForm 反序列化postform(querystring会被忽略)
// ShouldBindPostForm 反序列化 postform (querystring会被忽略)
// tag: `form:"xxx"`
ShouldBindPostForm(obj interface{}) error
// ShouldBindForm 同时反序列化querystringpostform;
// 当querystringpostform存在相同字段时,postform优先使用。
// ShouldBindForm 同时反序列化 querystringpostform;
// 当 querystringpostform 存在相同字段时,postform 优先使用。
// tag: `form:"xxx"`
ShouldBindForm(obj interface{}) error
// ShouldBindJSON 反序列化postjson
// ShouldBindJSON 反序列化 postjson
// tag: `json:"xxx"`
ShouldBindJSON(obj interface{}) error
// ShouldBindURI 反序列化path参数(如路由路径为 /user/:name)
// ShouldBindURI 反序列化 path 参数(如路由路径为 /user/:name)
// tag: `uri:"xxx"`
ShouldBindURI(obj interface{}) error
// Redirect 重定向
Redirect(code int, location string)
// Trace 获取 Trace 对象
Trace() Trace
setTrace(trace Trace)
disableTrace()
// Logger 获取 Logger 对象
Logger() *zap.Logger
setLogger(logger *zap.Logger)
// Payload 正确返回
Payload(payload errno.Error)
getPayload() errno.Error
SetPayload(payload errno.Error)
Header() http.Header
GetHeader(key string) string
SetHeader(key, value string)
UserID() int
setUserID(userID int)
UserName() string
setUserName(userName string)
// AbortWithError 错误返回
AbortWithError(err errno.Error)
abortError() errno.Error
// Header 获取 Header 对象
Header() http.Header
// GetHeader 获取 Header
GetHeader(key string) string
// SetHeader 设置 Header
SetHeader(key, value string)
// UserID 获取 JWT 中 UserID
UserID() int64
setUserID(userID int64)
// UserName 获取 JWT 中 UserName
UserName() string
setUserName(userName string)
// Alias 设置路由别名 for metrics uri
Alias() string
setAlias(path string)
// RawData 获取 Request.Body
RawData() []byte
// Method 获取 Request.Method
Method() string
// Host 获取 Request.Host
Host() string
// Path 获取 请求的路径 Request.URL.Path (不附带 querystring)
Path() string
// URI 获取 unescape 后的 Request.URL.RequestURI()
URI() string
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
// RequestContext 获取请求的 context (当 client 关闭后,会自动 canceled)
RequestContext() StdContext
}
type context struct {
ctx *gin.Context
}
type StdContext struct {
stdctx.Context
Trace
*zap.Logger
}
func (c *context) init() {
body, err := c.ctx.GetRawData()
if err != nil {
@@ -137,7 +155,7 @@ func (c *context) ShouldBindQuery(obj interface{}) error {
return c.ctx.ShouldBindWith(obj, binding.Query)
}
// ShouldBindPostForm 反序列化postform(querystring会被忽略)
// ShouldBindPostForm 反序列化 postform (querystring 会被忽略)
// tag: `form:"xxx"`
func (c *context) ShouldBindPostForm(obj interface{}) error {
return c.ctx.ShouldBindWith(obj, binding.FormPost)
@@ -202,7 +220,7 @@ func (c *context) getPayload() errno.Error {
return payload.(errno.Error)
}
func (c *context) SetPayload(payload errno.Error) {
func (c *context) Payload(payload errno.Error) {
c.ctx.Set(_PayloadName, payload)
}
@@ -227,16 +245,16 @@ func (c *context) SetHeader(key, value string) {
c.ctx.Header(key, value)
}
func (c *context) UserID() int {
func (c *context) UserID() int64 {
val, ok := c.ctx.Get(_UserID)
if !ok {
return 0
}
return val.(int)
return val.(int64)
}
func (c *context) setUserID(userID int) {
func (c *context) setUserID(userID int64) {
c.ctx.Set(_UserID, userID)
}
@@ -315,25 +333,11 @@ func (c *context) URI() string {
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
// RequestContext 获取请求的 context (当client关闭后,会自动canceled)
func (c *context) RequestContext() StdContext {
return StdContext{
c.ctx.Request.Context(),
c.Trace(),
c.Logger(),
}
if keyAsString, ok := key.(string); ok {
val, _ := c.ctx.Get(keyAsString)
return val
}
return nil
}
+19 -6
View File
@@ -121,11 +121,10 @@ func AliasForRecordMetrics(path string) HandlerFunc {
}
// WrapAuthHandler 用来处理 Auth 的入口,在之后的handler中只需 ctx.UserID() ctx.UserName() 即可。
func WrapAuthHandler(handler func(Context) (userID int, userName string, err errno.Error)) HandlerFunc {
func WrapAuthHandler(handler func(Context) (userID int64, userName string, err errno.Error)) HandlerFunc {
return func(ctx Context) {
userID, userName, err := handler(ctx)
if err != nil {
ctx.Logger().Error("auth handler err", zap.Error(errors.WithStack(errors.New(err.GetMsg()))))
ctx.AbortWithError(err)
return
}
@@ -362,7 +361,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
if err := context.abortError(); err != nil { // customer err
multierr.AppendInto(&abortErr, errors.New(err.GetMsg()))
multierr.AppendInto(&abortErr, err.GetErr())
response = err
}
} else {
@@ -389,7 +388,15 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
uri = alias
}
opt.recordMetrics(context.Method(), uri, !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK, ctx.Writer.Status(), businessCode, time.Since(ts).Seconds(), traceId)
opt.recordMetrics(
context.Method(),
uri,
!ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK,
ctx.Writer.Status(),
businessCode,
time.Since(ts).Seconds(),
traceId,
)
}
var t *trace.Trace
@@ -420,7 +427,12 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
t.CostSeconds = time.Since(ts).Seconds()
logger.Info("core-interceptor", zap.Any("trace", t))
if abortErr == nil {
logger.Info("core-interceptor", zap.Any("trace", t))
} else {
logger.Info("core-interceptor", zap.Any("trace", t), zap.Error(abortErr))
}
}()
ctx.Next()
@@ -436,6 +448,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
context.AbortWithError(code.ErrManyRequest)
return
}
ctx.Next()
})
}
@@ -457,7 +470,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
Host: ctx.Host(),
Status: "ok",
}
ctx.SetPayload(code.OK.WithData(resp))
ctx.Payload(code.OK.WithData(resp))
})
}
@@ -1,4 +1,4 @@
package db_repo
package db
import (
"fmt"
@@ -1,4 +1,4 @@
package db_repo
package db
import (
"time"
@@ -51,7 +51,7 @@ func before(db *gorm.DB) {
func after(db *gorm.DB) {
_ctx := db.Statement.Context
ctx, ok := _ctx.(core.Context)
ctx, ok := _ctx.(core.StdContext)
if !ok {
return
}
@@ -74,7 +74,7 @@ func after(db *gorm.DB) {
sqlInfo.Stack = utils.FileWithLineNum()
sqlInfo.Rows = db.Statement.RowsAffected
sqlInfo.CostSeconds = time.Since(ts).Seconds()
ctx.Trace().AppendSQL(sqlInfo)
ctx.Trace.AppendSQL(sqlInfo)
return
}
+24 -14
View File
@@ -5,30 +5,45 @@ import (
"github.com/spf13/cast"
)
// requestsCounter 定义计数器(Counter
var requestsCounter = prometheus.NewCounterVec(
const (
namespace = "xinliangnote"
subsystem = "go_gin_api"
)
// metricsRequestsTotal metrics for request total 计数器(Counter
var metricsRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_requests_total",
Namespace: namespace,
Subsystem: subsystem,
Name: "requests_total",
Help: "request(s) total",
},
[]string{"method", "path"},
)
// httpDurationsHistogram 定义累积直方图(Histogram
var httpDurationsHistogram = prometheus.NewHistogramVec(
// metricsRequestsCost metrics for requests cost 累积直方图(Histogram
var metricsRequestsCost = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "api_http_durations_histogram_seconds",
Buckets: []float64{0.01, 0.02, 0.03},
Namespace: namespace,
Subsystem: subsystem,
Name: "requests_cost",
Help: "request(s) cost seconds",
},
[]string{"method", "path", "success", "http_code", "business_code", "cost_seconds", "trace_id"},
)
func init() {
prometheus.MustRegister(requestsCounter, httpDurationsHistogram)
prometheus.MustRegister(metricsRequestsTotal, metricsRequestsCost)
}
// RecordMetrics 记录指标
func RecordMetrics(method, uri string, success bool, httpCode, businessCode int, costSeconds float64, traceId string) {
httpDurationsHistogram.With(prometheus.Labels{
metricsRequestsTotal.With(prometheus.Labels{
"method": method,
"path": uri,
}).Inc()
metricsRequestsCost.With(prometheus.Labels{
"method": method,
"path": uri,
"success": cast.ToString(success),
@@ -37,9 +52,4 @@ func RecordMetrics(method, uri string, success bool, httpCode, businessCode int,
"cost_seconds": cast.ToString(costSeconds),
"trace_id": traceId,
}).Observe(costSeconds)
requestsCounter.With(prometheus.Labels{
"method": method,
"path": uri,
}).Add(1)
}
+11 -2
View File
@@ -8,7 +8,7 @@ import (
"go.uber.org/zap"
)
// OnPanicNotify
// OnPanicNotify 发生 panic 时进行通知
func OnPanicNotify(ctx core.Context, err interface{}, stackInfo string) {
cfg := configs.Get().Mail
if cfg.Host == "" || cfg.Port == 0 || cfg.User == "" || cfg.Pass == "" || cfg.To == "" {
@@ -16,11 +16,19 @@ func OnPanicNotify(ctx core.Context, err interface{}, stackInfo string) {
return
}
subject, body, htmlErr := NewPanicHTMLEmail(ctx.Method(), ctx.Host(), ctx.URI(), ctx.Trace().ID(), err, stackInfo)
subject, body, htmlErr := NewPanicHTMLEmail(
ctx.Method(),
ctx.Host(),
ctx.URI(),
ctx.Trace().ID(),
err,
stackInfo,
)
if htmlErr != nil {
ctx.Logger().Error("NewPanicHTMLEmail error", zap.Error(htmlErr))
return
}
options := &mail.Options{
MailHost: cfg.Host,
MailPort: cfg.Port,
@@ -34,5 +42,6 @@ func OnPanicNotify(ctx core.Context, err interface{}, stackInfo string) {
if sendErr != nil {
ctx.Logger().Error("Mail Send error", zap.Error(sendErr))
}
return
}
+2 -2
View File
@@ -9,13 +9,13 @@ type D interface {
AppendResponse(resp *Response)
}
// Dialog 内部调用其它方接口的会话信息;失败时会有retry操作,所以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"` // 执行时长单位
CostSeconds float64 `json:"cost_seconds"` // 执行时长(单位秒)
}
func (d *Dialog) i() {}
+11
View File
@@ -0,0 +1,11 @@
package trace
import "time"
type Redis struct {
Timestamp string `json:"timestamp"` // 时间,格式:2006-01-02 15:04:05
Handle string `json:"handle"` // 操作,function
Key string `json:"key"`
Value string `json:"value,omitempty"`
TTL time.Duration `json:"ttl,omitempty"`
}
+1 -1
View File
@@ -3,7 +3,7 @@ package trace
type SQL struct {
Timestamp string `json:"timestamp"` // 时间,格式:2006-01-02 15:04:05
Stack string `json:"stack"` // 文件地址和行号
CostSeconds float64 `json:"cost_seconds"` // 执行时长单位
CostSeconds float64 `json:"cost_seconds"` // 执行时长(单位秒)
SQL string `json:"sql"` // SQL 语句
Rows int64 `json:"rows_affected"` // 影响行数
}
+34 -20
View File
@@ -23,34 +23,35 @@ type T interface {
// 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"`
Identifier string `json:"trace_id"` // 链路ID
Request *Request `json:"request"` // 请求信息
Response *Response `json:"response"` // 返回信息
ThirdPartyRequests []*Dialog `json:"third_party_requests"` // 调用第三方接口的信息
Debugs []*Debug `json:"debugs"` // 调试信息
SQLs []*SQL `json:"sqls"` // 执行的 SQL 信息
Redis []*Redis `json:"redis"` // 执行的 Redis 信息
Success bool `json:"success"` // 请求结果 true or false
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"`
TTL string `json:"ttl"` // 请求超时时间
Method string `json:"method"` // 请求方式
DecodedURL string `json:"decoded_url"` // 请求地址
Header interface{} `json:"header"` // 请求 Header 信息
Body interface{} `json:"body"` // 请求 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"`
Header interface{} `json:"header"` // Header 信息
Body interface{} `json:"body"` // Body 信息
BusinessCode int `json:"business_code,omitempty"` // 业务码
BusinessCodeMsg string `json:"business_code_msg,omitempty"` // 提示信息
HttpCode int `json:"http_code"` // HTTP 状态码
HttpCodeMsg string `json:"http_code_msg"` // HTTP 状态码信息
CostSeconds float64 `json:"cost_seconds"` // 执行时间(单位秒)
}
func New(id string) *Trace {
@@ -122,3 +123,16 @@ func (t *Trace) AppendSQL(sql *SQL) *Trace {
t.SQLs = append(t.SQLs, sql)
return t
}
// AppendRedis 追加 Redis
func (t *Trace) AppendRedis(redis *Redis) *Trace {
if redis == nil {
return t
}
t.mux.Lock()
defer t.mux.Unlock()
t.Redis = append(t.Redis, redis)
return t
}