This commit is contained in:
新亮
2021-01-31 14:51:41 +08:00
parent 106d5b65d7
commit 4ed8b81c57
32 changed files with 3255 additions and 81 deletions
+4 -1
View File
@@ -171,7 +171,10 @@ func (d *Demo) Trace() core.HandlerFunc {
}
// 调试信息
p.Println("res2.Data.Name", res2.Data.Name, p.WithTrace(c.Trace()))
p.Println("res2.Data.Name",
res2.Data.Name,
p.WithTrace(c.Trace()),
)
// 执行 SQL 信息
d.userService.GetUserByUserName(c, "test_user")
@@ -6,6 +6,7 @@ import (
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/pkg/errors"
"gorm.io/gorm"
)
var _ UserRepo = (*userRepo)(nil)
@@ -42,8 +43,9 @@ 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.RequestContext()).First(data, id).Where("is_deleted = ?", -1).Error
if err != nil {
err := u.db.GetDbR().
WithContext(ctx.RequestContext()).First(data, id).Where("is_deleted = ?", -1).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(err, "[user_demo] get user data err")
}
return data, nil
@@ -52,7 +54,7 @@ func (u *userRepo) getUserByID(ctx core.Context, id uint) (*user_model.UserDemo,
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 errors.Wrap(err, "[user_demo] get user data err")
}
return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("nick_name", nickname).Error
}
@@ -60,7 +62,7 @@ func (u *userRepo) UpdateNickNameByID(ctx core.Context, id uint, nickname string
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 errors.Wrap(err, "[user_demo] get user data err")
}
return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("is_deleted", 1).Error
}
@@ -72,7 +74,7 @@ func (u *userRepo) GetUserByUserName(ctx core.Context, username string) (*user_m
Select([]string{"id", "user_name", "nick_name", "mobile"}).
Where("user_name = ? and is_deleted = ?", username, -1).
First(data).Error
if err != nil {
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(err, "[user_demo] get user data err")
}
return data, nil
+8
View File
@@ -4,6 +4,7 @@ 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/router/middleware/auth"
"github.com/xinliangnote/go-gin-api/internal/graph/handler"
"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"
@@ -33,6 +34,13 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo) (core.Mux, err
demoHandler := demo.NewDemo(logger, db, cache)
userHandler := user_handler.NewUserDemo(logger, db, cache)
gqlHandler := handler.New(logger, db, cache)
gql := mux.Group("/graphql")
{
gql.GET("", gqlHandler.Playground())
gql.POST("/query", gqlHandler.Query())
}
// user_demo CURD
user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler))
+24
View File
@@ -0,0 +1,24 @@
## example
```cassandraql
1.
query {
bySex(sex: "男") {
id
name
sex
mobile
}
}
2.
mutation {
updateUserMobile(data: {id: "1", mobile: "13299999999"}) {
id
name
sex
mobile
}
}
```
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
package handler
import (
"context"
"time"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"github.com/xinliangnote/go-gin-api/internal/graph/resolvers"
"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/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/lru"
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"go.uber.org/zap"
)
var _ Gql = (*gql)(nil)
type Gql interface {
i()
Playground() core.HandlerFunc
Query() core.HandlerFunc
}
type gql struct {
logger *zap.Logger
db db.Repo
cache cache.Repo
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Gql {
return &gql{
logger: logger,
cache: cache,
db: db,
}
}
func (g *gql) i() {}
func (g *gql) Query() core.HandlerFunc {
// 定义扩展字段
extensions := make(map[string]interface{})
h := handler.New(generated.NewExecutableSchema(
resolvers.NewRootResolvers(g.logger, g.db, g.cache)),
)
h.AddTransport(transport.Websocket{
KeepAlivePingInterval: 10 * time.Second,
})
// 设置 transport
h.AddTransport(transport.Options{})
h.AddTransport(transport.GET{})
h.AddTransport(transport.POST{})
h.SetQueryCache(lru.New(1000))
// 启用侧边栏文档
h.Use(extension.Introspection{})
h.Use(extension.AutomaticPersistedQuery{
Cache: lru.New(100),
})
return func(c core.Context) {
var responses interface{}
defer func() {
// 设置 core log
c.GraphPayload(responses)
}()
// 设置 core trace_id
extensions["trace_id"] = c.Trace().ID()
h.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
resp := next(ctx)
resp.Extensions = extensions
responses = resp
return resp
})
// 设置 core context
coreContext := context.WithValue(c.Request().Context(), resolvers.CoreContextKey, c)
h.ServeHTTP(c.ResponseWriter(), c.Request().WithContext(coreContext))
}
}
func (g *gql) Playground() core.HandlerFunc {
h := playground.Handler("GraphQL", "/graphql/query")
return func(c core.Context) {
h.ServeHTTP(c.ResponseWriter(), c.Request())
}
}
+15
View File
@@ -0,0 +1,15 @@
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package model
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Sex string `json:"sex"`
Mobile string `json:"mobile"`
}
type UpdateUserMobileInput struct {
ID string `json:"id"`
Mobile string `json:"mobile"`
}
@@ -0,0 +1,29 @@
package resolvers
// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"github.com/xinliangnote/go-gin-api/internal/graph/model"
)
type Resolver struct{}
func (r *mutationResolver) UpdateUserMobile(ctx context.Context, data model.UpdateUserMobileInput) (*model.User, error) {
panic("not implemented")
}
func (r *queryResolver) BySex(ctx context.Context, sex string) ([]*model.User, error) {
panic("not implemented")
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
+51
View File
@@ -0,0 +1,51 @@
package resolvers
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/graph/generated"
"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"
"go.uber.org/zap"
)
type coreCtxKeyType struct{ name string }
var CoreContextKey = coreCtxKeyType{"_core_context"}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type Resolver struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func NewRootResolvers(logger *zap.Logger, db db.Repo, cache cache.Repo) generated.Config {
c := generated.Config{
Resolvers: &Resolver{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
},
}
return c
}
func (r *Resolver) Mutation() generated.MutationResolver {
return &mutationResolver{r}
}
func (r *Resolver) Query() generated.QueryResolver {
return &queryResolver{r}
}
// getCoreContextByCtx 获取 core context
func (r *Resolver) getCoreContextByCtx(ctx context.Context) core.Context {
return ctx.Value(CoreContextKey).(core.Context)
}
+47
View File
@@ -0,0 +1,47 @@
package resolvers
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/graph/model"
"github.com/pkg/errors"
)
func (r *queryResolver) BySex(ctx context.Context, sex string) ([]*model.User, error) {
if sex == "" {
return nil, errors.New("sex required")
}
//模拟数据
var users []*model.User
users = append(users, &model.User{ID: "1", Name: "Tom", Sex: sex, Mobile: "13266666666"})
users = append(users, &model.User{ID: "1", Name: "Jack", Sex: sex, Mobile: "13288888888"})
return users, nil
}
func (r *mutationResolver) UpdateUserMobile(ctx context.Context, data model.UpdateUserMobileInput) (*model.User, error) {
if data.ID == "" {
return nil, errors.New("id required")
}
if data.Mobile == "" {
return nil, errors.New("mobile required")
}
//模拟数据
user := new(model.User)
user.ID = data.ID
user.Mobile = data.Mobile
user.Sex = "男"
user.Name = "Jack"
//操作数据库
//userData, err := r.userService.GetUserByUserName(r.getCoreContextByCtx(ctx), "test_user")
//if err != nil {
// return nil, err
//}
return user, nil
}
+22
View File
@@ -0,0 +1,22 @@
type User {
id: String!
name: String!
sex: String!
mobile: String!
}
# 查询 集合
type Query {
bySex(sex: String!): [User!]
}
# 输入类型: 一般用户更改资源中的输入是列表对象,完成复杂任务
input updateUserMobileInput {
id:ID!
mobile: String!
}
# 更改或者创建 集合
type Mutation {
updateUserMobile(data: updateUserMobileInput!): User
}
+7 -1
View File
@@ -75,6 +75,7 @@ func redisConnect() (*redis.Client, error) {
// Set set some <key,value> into redis
func (c *cacheRepo) Set(key, value string, ttl time.Duration, options ...Option) error {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
@@ -82,7 +83,8 @@ func (c *cacheRepo) Set(key, value string, ttl time.Duration, options ...Option)
opt.Redis.Handle = "set"
opt.Redis.Key = key
opt.Redis.Value = value
opt.Redis.TTL = ttl
opt.Redis.TTL = ttl.Minutes()
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
@@ -100,12 +102,14 @@ func (c *cacheRepo) Set(key, value string, ttl time.Duration, options ...Option)
// Get get some key from redis
func (c *cacheRepo) Get(key string, options ...Option) (string, error) {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "get"
opt.Redis.Key = key
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
@@ -155,12 +159,14 @@ func (c *cacheRepo) Del(keys ...string) bool {
}
func (c *cacheRepo) Incr(key string, options ...Option) int64 {
ts := time.Now()
opt := newOption()
defer func() {
if opt.Trace != nil {
opt.Redis.Timestamp = time_parse.CSTLayoutString()
opt.Redis.Handle = "incr"
opt.Redis.Key = key
opt.Redis.CostSeconds = time.Since(ts).Seconds()
opt.Trace.AppendRedis(opt.Redis)
}
}()
+43 -10
View File
@@ -22,14 +22,15 @@ type HandlerFunc func(c Context)
type Trace = trace.T
const (
_Alias = "_alias_"
_TraceName = "_trace_"
_LoggerName = "_logger_"
_BodyName = "_body_"
_PayloadName = "_payload_"
_UserID = "_user_id_"
_UserName = "_user_name_"
_AbortErrorName = "_abort_error_"
_Alias = "_alias_"
_TraceName = "_trace_"
_LoggerName = "_logger_"
_BodyName = "_body_"
_PayloadName = "_payload_"
_GraphPayloadName = "_graph_payload_"
_UserID = "_user_id_"
_UserName = "_user_name_"
_AbortErrorName = "_abort_error_"
)
var contextPool = &sync.Pool{
@@ -92,6 +93,10 @@ type Context interface {
Payload(payload errno.Error)
getPayload() errno.Error
// GraphPayload GraphQL返回值 与 api 返回结构不同
GraphPayload(payload interface{})
getGraphPayload() interface{}
// AbortWithError 错误返回
AbortWithError(err errno.Error)
abortError() errno.Error
@@ -115,6 +120,8 @@ type Context interface {
Alias() string
setAlias(path string)
// Request 获取 Request 对象
Request() *http.Request
// RawData 获取 Request.Body
RawData() []byte
// Method 获取 Request.Method
@@ -127,6 +134,9 @@ type Context interface {
URI() string
// RequestContext 获取请求的 context (当 client 关闭后,会自动 canceled)
RequestContext() StdContext
// ResponseWriter 获取 ResponseWriter 对象
ResponseWriter() gin.ResponseWriter
}
type context struct {
@@ -216,14 +226,27 @@ func (c *context) setLogger(logger *zap.Logger) {
}
func (c *context) getPayload() errno.Error {
payload, _ := c.ctx.Get(_PayloadName)
return payload.(errno.Error)
if payload, ok := c.ctx.Get(_PayloadName); ok != false {
return payload.(errno.Error)
}
return nil
}
func (c *context) Payload(payload errno.Error) {
c.ctx.Set(_PayloadName, payload)
}
func (c *context) getGraphPayload() interface{} {
if payload, ok := c.ctx.Get(_GraphPayloadName); ok != false {
return payload
}
return nil
}
func (c *context) GraphPayload(payload interface{}) {
c.ctx.Set(_GraphPayloadName, payload)
}
func (c *context) Header() http.Header {
header := c.ctx.Request.Header
@@ -303,6 +326,11 @@ func (c *context) setAlias(path string) {
}
}
// Request 获取 Request
func (c *context) Request() *http.Request {
return c.ctx.Request
}
func (c *context) RawData() []byte {
body, ok := c.ctx.Get(_BodyName)
if !ok {
@@ -341,3 +369,8 @@ func (c *context) RequestContext() StdContext {
c.Logger(),
}
}
// ResponseWriter 获取 ResponseWriter
func (c *context) ResponseWriter() gin.ResponseWriter {
return c.ctx.Writer
}
+26 -7
View File
@@ -353,6 +353,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
businessCodeMsg string
abortErr error
traceId string
graphResponse interface{}
)
if ctx.IsAborted() {
@@ -382,6 +383,8 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
ctx.JSON(response.GetHttpCode(), response)
}
graphResponse = context.getGraphPayload()
if opt.recordMetrics != nil {
uri := context.URI()
if alias := context.Alias(); alias != "" {
@@ -415,24 +418,40 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
Body: string(context.RawData()),
})
var responseBody interface{}
if response != nil {
responseBody = response
}
if graphResponse != nil {
responseBody = graphResponse
}
t.WithResponse(&trace.Response{
Header: ctx.Writer.Header(),
HttpCode: ctx.Writer.Status(),
HttpCodeMsg: http.StatusText(ctx.Writer.Status()),
BusinessCode: businessCode,
BusinessCodeMsg: businessCodeMsg,
Body: response,
Body: responseBody,
CostSeconds: time.Since(ts).Seconds(),
})
t.Success = !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK
t.CostSeconds = time.Since(ts).Seconds()
if abortErr == nil {
logger.Info("core-interceptor", zap.Any("trace", t))
} else {
logger.Info("core-interceptor", zap.Any("trace", t), zap.Error(abortErr))
}
logger.Info("core-interceptor",
zap.Any("method", ctx.Request.Method),
zap.Any("path", decodedURL),
zap.Any("http_code", ctx.Writer.Status()),
zap.Any("business_code", businessCode),
zap.Any("success", t.Success),
zap.Any("cost_seconds", t.CostSeconds),
zap.Any("trace_id", t.Identifier),
zap.Any("trace_info", t),
zap.Error(abortErr),
)
}()
ctx.Next()
+3 -2
View File
@@ -1,6 +1,7 @@
package trace
type Debug struct {
Key string `json:"key"` //标示
Value interface{} `json:"value"` //值
Key string `json:"key"` // 标示
Value interface{} `json:"value"` // 值
CostSeconds float64 `json:"cost_seconds"` // 执行时间(单位秒)
}
+6 -7
View File
@@ -1,11 +1,10 @@
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"`
Timestamp string `json:"timestamp"` // 时间,格式:2006-01-02 15:04:05
Handle string `json:"handle"` // 操作,SET/GET 等
Key string `json:"key"` // Key
Value string `json:"value,omitempty"` // Value
TTL float64 `json:"ttl,omitempty"` // 超时时长(单位分)
CostSeconds float64 `json:"cost_seconds"` // 执行时间(单位秒)
}
+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"` // 执行时长(单位秒)
SQL string `json:"sql"` // SQL 语句
Rows int64 `json:"rows_affected"` // 影响行数
CostSeconds float64 `json:"cost_seconds"` // 执行时长(单位秒)
}