6 Commits
Author SHA1 Message Date
新亮 3823eee2ca #11 code generator 2021-02-27 09:29:34 +08:00
新亮 0b6b9dd69f upgrade 2021-02-20 20:43:39 +08:00
新亮 7327275b7a upgrade 2021-02-20 10:02:53 +08:00
新亮 9b5028e9de upgrade 2021-02-20 09:59:36 +08:00
新亮 cc8012b09d upgrade 2021-02-19 22:00:01 +08:00
新亮 334bfbb4aa upgrade 2021-02-09 09:52:50 +08:00
68 changed files with 2652 additions and 1014 deletions
+2 -1
View File
@@ -18,8 +18,9 @@
1. 支持 errno 统一定义错误码
1. 支持 [zap](https://go.uber.org/zap) 日志收集
1. 支持 [viper](https://github.com/spf13/viper) 配置文件解析
1. 支持 [grom](https://gorm.io/gorm) 数据库组件
1. 支持 [gorm](https://gorm.io/gorm) 数据库组件
1. 支持 [go-redis](https://github.com/go-redis/redis/v7) 组件
1. 支持 RESTful API 返回值规范
+6
View File
@@ -0,0 +1,6 @@
## 执行命令
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割;
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`
## 参考
- https://github.com/MohamedBassem/gormgen
+37
View File
@@ -0,0 +1,37 @@
package main
import (
"flag"
"log"
"os"
"strings"
"github.com/xinliangnote/go-gin-api/cmd/gormgen/pkg"
)
var (
input string
structs []string
)
func init() {
flagStructs := flag.String("structs", "", "[Required] The name of schema structs to generate structs for, comma seperated\n")
flagInput := flag.String("input", "", "[Required] The name of the input file dir\n")
flag.Parse()
if *flagStructs == "" || *flagInput == "" {
flag.Usage()
os.Exit(1)
}
structs = strings.Split(*flagStructs, ",")
input = *flagInput
}
func main() {
gen := pkg.NewGenerator(input)
p := pkg.NewParser(input)
if err := gen.ParserAST(p, structs).Generate().Format().Flush(); err != nil {
log.Fatalln(err)
}
}
+124
View File
@@ -0,0 +1,124 @@
package pkg
import (
"bytes"
"errors"
"go/format"
"io/ioutil"
"log"
"strings"
"github.com/jinzhu/gorm"
)
// fieldConfig
type fieldConfig struct {
FieldName string
ColumnName string
FieldType string
HumpName string
}
// structConfig
type structConfig struct {
config
StructName string
OnlyFields []fieldConfig
OptionFields []fieldConfig
}
type ImportPkg struct {
Pkg string
}
type structHelpers struct {
Titelize func(string) string
}
type config struct {
PkgName string
Helpers structHelpers
QueryBuilderName string
}
// The Generator is the one responsible for generating the code, adding the imports, formating, and writing it to the file.
type Generator struct {
buf map[string]*bytes.Buffer
inputFile string
config config
structConfigs []structConfig
}
// NewGenerator function creates an instance of the generator given the name of the output file as an argument.
func NewGenerator(outputFile string) *Generator {
return &Generator{
buf: map[string]*bytes.Buffer{},
inputFile: outputFile,
}
}
// ParserAST parse by go file
func (g *Generator) ParserAST(p *Parser, structs []string) (ret *Generator) {
for _, v := range structs {
g.buf[gorm.ToDBName(v)] = new(bytes.Buffer)
}
g.structConfigs = p.Parse()
g.config.PkgName = p.pkg.Name
g.config.Helpers = structHelpers{
Titelize: strings.Title,
}
g.config.QueryBuilderName = SQLColumnToHumpStyle(p.pkg.Name) + "QueryBuilder"
return g
}
func (g *Generator) checkConfig() (err error) {
if len(g.config.PkgName) == 0 {
err = errors.New("package name dose'n set")
return
}
for i := 0; i < len(g.structConfigs); i++ {
g.structConfigs[i].config = g.config
}
return
}
// Generate executes the template and store it in an internal buffer.
func (g *Generator) Generate() *Generator {
if err := g.checkConfig(); err != nil {
panic(err)
}
for _, v := range g.structConfigs {
if _, ok := g.buf[gorm.ToDBName(v.StructName)]; !ok {
continue
}
if err := outputTemplate.Execute(g.buf[gorm.ToDBName(v.StructName)], v); err != nil {
panic(err)
}
}
return g
}
// Format function formats the output of the generation.
func (g *Generator) Format() *Generator {
for k := range g.buf {
formattedOutput, err := format.Source(g.buf[k].Bytes())
if err != nil {
panic(err)
}
g.buf[k] = bytes.NewBuffer(formattedOutput)
}
return g
}
// Flush function writes the output to the output file.
func (g *Generator) Flush() error {
for k := range g.buf {
filename := g.inputFile + "/gen_" + strings.ToLower(k) + ".go"
if err := ioutil.WriteFile(filename, g.buf[k].Bytes(), 0777); err != nil {
log.Fatalln(err)
}
}
return nil
}
+120
View File
@@ -0,0 +1,120 @@
package pkg
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"log"
"strings"
"github.com/jinzhu/gorm"
)
// The Parser is used to parse a directory and expose information about the structs defined in the files of this directory.
type Parser struct {
dir string
pkg *build.Package
parsedFiles []*ast.File
}
// NewParser create a new parser instance.
func NewParser(dir string) *Parser {
return &Parser{
dir: dir,
}
}
// getPackage parse dir get go file and package
func (p *Parser) getPackage() {
pkg, err := build.Default.ImportDir(p.dir, build.ImportComment)
if err != nil {
log.Fatalf("cannot process directory %s: %s", p.dir, err)
}
p.pkg = pkg
}
// parseGoFiles parse go file
func (p *Parser) parseGoFiles() {
var parsedFiles []*ast.File
fs := token.NewFileSet()
for _, file := range p.pkg.GoFiles {
file = p.dir + "/" + file
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
}
parsedFiles = append(parsedFiles, parsedFile)
}
p.parsedFiles = parsedFiles
}
// parseTypes parse type of struct
func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
ast.Inspect(file, func(n ast.Node) bool {
decl, ok := n.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
var (
data structConfig
)
typeSpec, _ok := spec.(*ast.TypeSpec)
if !_ok {
continue
}
// We only care about struct declaration (for now)
var structType *ast.StructType
if structType, ok = typeSpec.Type.(*ast.StructType); !ok {
continue
}
data.StructName = typeSpec.Name.Name
for _, v := range structType.Fields.List {
var (
optionField fieldConfig
)
// type is ident, get onlyField type
if t, _ok := v.Type.(*ast.Ident); _ok {
optionField.FieldType = t.String()
} else {
if v.Tag != nil {
if strings.Contains(v.Tag.Value, "gorm") && strings.Contains(v.Tag.Value, "time") {
optionField.FieldType = "time.Time"
}
}
}
// get file name
if len(v.Names) > 0 {
optionField.FieldName = v.Names[0].String()
optionField.ColumnName = gorm.ToDBName(optionField.FieldName)
optionField.HumpName = SQLColumnToHumpStyle(optionField.ColumnName)
}
data.OptionFields = append(data.OptionFields, optionField)
}
ret = append(ret, data)
}
return true
})
return
}
// Parse should be called before any type querying for the parser. It takes the directory to be parsed and extracts all the structs defined in this directory.
func (p *Parser) Parse() (ret []structConfig) {
var (
data []structConfig
)
p.getPackage()
p.parseGoFiles()
for _, f := range p.parsedFiles {
data = append(data, p.parseTypes(f)...)
}
return data
}
+149
View File
@@ -0,0 +1,149 @@
package pkg
import "text/template"
// Make sure that the template compiles during package initialization
func parseTemplateOrPanic(t string) *template.Template {
tpl, err := template.New("output_template").Parse(t)
if err != nil {
panic(err)
}
return tpl
}
var outputTemplate = parseTemplateOrPanic(`
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package {{.PkgName}}
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *{{.StructName}} {
return new({{.StructName}})
}
func NewQueryBuilder() *{{.QueryBuilderName}} {
return new({{.QueryBuilderName}})
}
func (t *{{.StructName}}) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type {{.QueryBuilderName}} struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *{{.QueryBuilderName}}) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *{{.QueryBuilderName}}) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&{{.StructName}}{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *{{.QueryBuilderName}}) First(db *gorm.DB) (*{{.StructName}}, error) {
ret := &{{.StructName}}{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *{{.QueryBuilderName}}) QueryOne(db *gorm.DB) (*{{.StructName}}, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *{{.QueryBuilderName}}) QueryAll(db *gorm.DB) ([]*{{.StructName}}, error) {
var ret []*{{.StructName}}
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *{{.QueryBuilderName}}) Limit(limit int) *{{.QueryBuilderName}} {
qb.limit = limit
return qb
}
func (qb *{{.QueryBuilderName}}) Offset(offset int) *{{.QueryBuilderName}} {
qb.offset = offset
return qb
}
{{$queryBuilderName := .QueryBuilderName}}
{{range .OptionFields}}
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p db_repo.Predicate, value {{.FieldType}}) *{{$queryBuilderName}} {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", p),
value,
})
return qb
}
func (qb *{{$queryBuilderName}}) OrderBy{{call $.Helpers.Titelize .FieldName}}(asc bool) *{{$queryBuilderName}} {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "{{.ColumnName}} " + order)
return qb
}
{{end}}
`)
+18
View File
@@ -0,0 +1,18 @@
package pkg
import "strings"
// SQLColumnToHumpStyle sql转换成驼峰模式
func SQLColumnToHumpStyle(in string) (ret string) {
for i := 0; i < len(in); i++ {
if i > 0 && in[i-1] == '_' && in[i] != '_' {
s := strings.ToUpper(string(in[i]))
ret += s
} else if in[i] == '_' {
continue
} else {
ret += string(in[i])
}
}
return
}
+6
View File
@@ -0,0 +1,6 @@
## 执行命令
```$xslt
// test_handler 为 ./internal/api/controller/ 中的包名
./scripts/handlergen.sh test_handler
```
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"strings"
"unicode"
)
var handlerName string
func init() {
handler := flag.String("handler", "", "请输入需要生成的 handler 名称\n")
flag.Parse()
handlerName = strings.ToLower(*handler)
}
func main() {
fs := token.NewFileSet()
file := fmt.Sprintf("./internal/api/controller/%s/handler.go", handlerName)
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
}
ast.Inspect(parsedFile, func(n ast.Node) bool {
decl, ok := n.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
typeSpec, _ok := spec.(*ast.TypeSpec)
if !_ok {
continue
}
var interfaceType *ast.InterfaceType
if interfaceType, ok = typeSpec.Type.(*ast.InterfaceType); !ok {
continue
}
for _, v := range interfaceType.Methods.List {
if len(v.Names) > 0 {
if v.Names[0].String() == "i" {
continue
}
filepath := "./internal/api/controller/" + handlerName
filename := fmt.Sprintf("%s/func_%s.go", filepath, strings.ToLower(v.Names[0].String()))
funcFile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open func file error %v\n", err.Error())
}
funcContent := fmt.Sprintf("package %s\n\n", handlerName)
funcContent += "import (\n"
funcContent += `"github.com/xinliangnote/go-gin-api/internal/pkg/core"`
funcContent += "\n)\n\n"
funcContent += fmt.Sprintf("\n\ntype %sRequest struct {}\n\n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("type %sResponse struct {}\n\n", Lcfirst(v.Names[0].String()))
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String())
funcFile.WriteString(funcContent)
funcFile.Close()
}
}
}
return true
})
}
func Lcfirst(str string) string {
for i, v := range str {
return string(unicode.ToLower(v)) + str[i+1:]
}
return ""
}
+289
View File
@@ -0,0 +1,289 @@
package main
import (
"database/sql"
"fmt"
"os"
"regexp"
"strings"
"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/logger"
"go.uber.org/zap"
"gorm.io/gorm"
)
type tableInfo struct {
Name string `db:"table_name"` // name
Comment sql.NullString `db:"table_comment"` // comment
}
type tableColumn struct {
OrdinalPosition uint16 `db:"ORDINAL_POSITION"` // position
ColumnName string `db:"COLUMN_NAME"` // name
ColumnType string `db:"COLUMN_TYPE"` // column_type
DataType string `db:"DATA_TYPE"` // data_type
ColumnKey sql.NullString `db:"COLUMN_KEY"` // key
IsNullable string `db:"IS_NULLABLE"` // nullable
Extra sql.NullString `db:"EXTRA"` // extra
ColumnComment sql.NullString `db:"COLUMN_COMMENT"` // comment
ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value
}
func main() {
// 初始化 logger
loggers, err := logger.NewJSONLogger(
logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName(), env.Active().Value())),
logger.WithTimeLayout("2006-01-02 15:04:05"),
logger.WithFileP(configs.ProjectLogFile()),
)
if err != nil {
panic(err)
}
defer loggers.Sync()
// 初始化 DB
dbRepo, err := db.New()
if err != nil {
loggers.Fatal("new db err", zap.Error(err))
}
defer func() {
if err := dbRepo.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
}
if err := dbRepo.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
}
}()
dbName := configs.Get().MySQL.Read.Name
genTables := configs.Get().Cmd.GenTables
tables, err := queryTables(dbRepo.GetDbR(), dbName, genTables)
if err != nil {
loggers.Error("query tables of database err", zap.Error(err))
return
}
for _, table := range tables {
filepath := "./internal/api/repository/db_repo/" + table.Name + "_repo"
_ = os.Mkdir(filepath, 0766)
mdName := fmt.Sprintf("%s/gen_table.md", filepath)
mdFile, err := os.OpenFile(mdName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open markdown file error %v\n", err.Error())
return
}
modelName := fmt.Sprintf("%s/gen_model.go", filepath)
modelFile, err := os.OpenFile(modelName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
if err != nil {
fmt.Printf("create and open model file error %v\n", err.Error())
return
}
modelContent := fmt.Sprintf("package %s%s\n", table.Name, "_repo")
modelContent += fmt.Sprintf(`import "time"`)
modelContent += fmt.Sprintf("\n\n// %s \n", table.Comment.String)
modelContent += fmt.Sprintf("//go:generate gormgen -structs %s -input . \n", capitalize(table.Name))
modelContent += fmt.Sprintf("type %s struct {\n", capitalize(table.Name))
tableContent := fmt.Sprintf("#### %s.%s \n", dbName, table.Name)
if table.Comment.String != "" {
tableContent += table.Comment.String + "\n"
}
tableContent += "\n" +
"| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" +
"| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n"
columnInfo, columnInfoErr := queryTableColumn(dbRepo.GetDbR(), dbName, table.Name)
if columnInfoErr != nil {
continue
}
for _, info := range columnInfo {
tableContent += fmt.Sprintf(
"| %d | %s | %s | %s | %s | %s | %s | %s |\n",
info.OrdinalPosition,
info.ColumnName,
strings.ReplaceAll(strings.ReplaceAll(info.ColumnComment.String, "|", "\\|"), "\n", ""),
info.ColumnType,
info.ColumnKey.String,
info.IsNullable,
info.Extra.String,
info.ColumnDefault.String,
)
if textType(info.DataType) == "time.Time" {
modelContent += fmt.Sprintf("%s %s `%s` // %s\n", capitalize(info.ColumnName), textType(info.DataType), "gorm:\"time\"", info.ColumnComment.String)
} else {
modelContent += fmt.Sprintf("%s %s // %s\n", capitalize(info.ColumnName), textType(info.DataType), info.ColumnComment.String)
}
}
mdFile.WriteString(tableContent)
mdFile.Close()
modelContent += "}\n"
modelFile.WriteString(modelContent)
modelFile.Close()
}
}
func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, error) {
var tableCollect []tableInfo
var tableArray []string
var commentArray []sql.NullString
sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", dbName)
rows, err := db.Raw(sqlTables).Rows()
if err != nil {
return tableCollect, err
}
defer rows.Close()
for rows.Next() {
var info tableInfo
err = rows.Scan(&info.Name, &info.Comment)
if err != nil {
fmt.Printf("execute query tables action error,had ignored, detail is [%v]\n", err.Error())
continue
}
tableCollect = append(tableCollect, info)
tableArray = append(tableArray, info.Name)
commentArray = append(commentArray, info.Comment)
}
// filter tables when specified tables params
if tableName != "" {
tableCollect = nil
chooseTables := strings.Split(tableName, ",")
indexMap := make(map[int]int)
for _, item := range chooseTables {
subIndexMap := getTargetIndexMap(tableArray, item)
for k, v := range subIndexMap {
if _, ok := indexMap[k]; ok {
continue
}
indexMap[k] = v
}
}
if len(indexMap) != 0 {
for _, v := range indexMap {
var info tableInfo
info.Name = tableArray[v]
info.Comment = commentArray[v]
tableCollect = append(tableCollect, info)
}
}
}
return tableCollect, err
}
func queryTableColumn(db *gorm.DB, dbName string, tableName string) ([]tableColumn, error) {
// 定义承载列信息的切片
var columns []tableColumn
sqlTableColumn := fmt.Sprintf("SELECT `ORDINAL_POSITION`,`COLUMN_NAME`,`COLUMN_TYPE`,`DATA_TYPE`,`COLUMN_KEY`,`IS_NULLABLE`,`EXTRA`,`COLUMN_COMMENT`,`COLUMN_DEFAULT` FROM `information_schema`.`columns` WHERE `table_schema`= '%s' AND `table_name`= '%s' ORDER BY `ORDINAL_POSITION` ASC",
dbName, tableName)
rows, err := db.Raw(sqlTableColumn).Rows()
if err != nil {
fmt.Printf("execute query table column action error, detail is [%v]\n", err.Error())
return columns, err
}
defer rows.Close()
for rows.Next() {
var column tableColumn
err = rows.Scan(
&column.OrdinalPosition,
&column.ColumnName,
&column.ColumnType,
&column.DataType,
&column.ColumnKey,
&column.IsNullable,
&column.Extra,
&column.ColumnComment,
&column.ColumnDefault)
if err != nil {
fmt.Printf("query table column scan error, detail is [%v]\n", err.Error())
return columns, err
}
columns = append(columns, column)
}
return columns, err
}
func getTargetIndexMap(tableNameArr []string, item string) map[int]int {
indexMap := make(map[int]int)
for i := 0; i < len(tableNameArr); i++ {
if match, _ := regexp.MatchString(item, tableNameArr[i]); match {
if _, ok := indexMap[i]; ok {
continue
}
indexMap[i] = i
}
}
return indexMap
}
func capitalize(s string) string {
var upperStr string
chars := strings.Split(s, "_")
for _, val := range chars {
vv := []rune(val)
for i := 0; i < len(vv); i++ {
if i == 0 {
if vv[i] >= 97 && vv[i] <= 122 {
vv[i] -= 32
upperStr += string(vv[i])
}
} else {
upperStr += string(vv[i])
}
}
}
return upperStr
}
func textType(s string) string {
var mysqlTypeToGoType = map[string]string{
"tinyint": "int32",
"smallint": "int32",
"mediumint": "int32",
"int": "int32",
"integer": "int64",
"bigint": "int64",
"float": "float64",
"double": "float64",
"decimal": "float64",
"date": "string",
"time": "string",
"year": "string",
"datetime": "time.Time",
"timestamp": "time.Time",
"char": "string",
"varchar": "string",
"tinyblob": "string",
"tinytext": "string",
"blob": "string",
"text": "string",
"mediumblob": "string",
"mediumtext": "string",
"longblob": "string",
"longtext": "string",
}
return mysqlTypeToGoType[s]
}
+4
View File
@@ -63,6 +63,10 @@ type Config struct {
Private string `toml:"private"`
Public string `toml:"public"`
} `toml:"rsa"`
Cmd struct {
GenTables string `toml:"genTables"`
} `toml:"cmd"`
}
func init() {
+2
View File
@@ -77,3 +77,5 @@ xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz
owIDAQAB
-----END PUBLIC KEY-----'
[cmd]
genTables = 'user_demo'
+114 -23
View File
@@ -43,9 +43,15 @@ var doc = `{
"summary": "获取授权信息",
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/demo.authResponse"
"$ref": "#/definitions/demo_handler.authResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -75,7 +81,7 @@ var doc = `{
],
"responses": {
"200": {
"description": "用户信息",
"description": "OK",
"schema": {
"type": "array",
"items": {
@@ -92,6 +98,18 @@ var doc = `{
}
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
@@ -112,11 +130,11 @@ var doc = `{
"parameters": [
{
"description": "请求信息",
"name": "RequestInfo",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_model.CreateRequest"
"$ref": "#/definitions/user_handler.createRequest"
}
},
{
@@ -129,9 +147,21 @@ var doc = `{
],
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/user_model.CreateResponse"
"$ref": "#/definitions/user_handler.createResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -139,7 +169,7 @@ var doc = `{
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户 - 更新 is_deleted = 1",
"description": "删除用户",
"consumes": [
"application/json"
],
@@ -149,7 +179,7 @@ var doc = `{
"tags": [
"User"
],
"summary": "删除用户 - 更新 is_deleted = 1",
"summary": "删除用户",
"parameters": [
{
"type": "integer",
@@ -168,7 +198,22 @@ var doc = `{
],
"responses": {
"200": {
"description": "返回信息"
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.deleteResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
@@ -204,9 +249,21 @@ var doc = `{
],
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/user_model.DetailResponse"
"$ref": "#/definitions/user_handler.detailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -228,11 +285,11 @@ var doc = `{
"parameters": [
{
"description": "请求信息",
"name": "RequestInfo",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_model.UpdateNickNameByIDRequest"
"$ref": "#/definitions/user_handler.updateNickNameByIDRequest"
}
},
{
@@ -245,9 +302,21 @@ var doc = `{
],
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/user_model.UpdateNickNameByIDResponse"
"$ref": "#/definitions/user_handler.updateNickNameByIDResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -255,7 +324,20 @@ var doc = `{
}
},
"definitions": {
"demo.authResponse": {
"code.Failure": {
"type": "object",
"properties": {
"code": {
"description": "业务码",
"type": "integer"
},
"message": {
"description": "描述信息",
"type": "string"
}
}
},
"demo_handler.authResponse": {
"type": "object",
"properties": {
"authorization": {
@@ -268,7 +350,7 @@ var doc = `{
}
}
},
"user_model.CreateRequest": {
"user_handler.createRequest": {
"type": "object",
"properties": {
"mobile": {
@@ -285,7 +367,7 @@ var doc = `{
}
}
},
"user_model.CreateResponse": {
"user_handler.createResponse": {
"type": "object",
"properties": {
"id": {
@@ -294,7 +376,16 @@ var doc = `{
}
}
},
"user_model.DetailResponse": {
"user_handler.deleteResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
},
"user_handler.detailResponse": {
"type": "object",
"properties": {
"id": {
@@ -302,7 +393,7 @@ var doc = `{
"type": "integer"
},
"mobile": {
"description": "手机号",
"description": "手机号(脱敏)",
"type": "string"
},
"nick_name": {
@@ -315,7 +406,7 @@ var doc = `{
}
}
},
"user_model.UpdateNickNameByIDRequest": {
"user_handler.updateNickNameByIDRequest": {
"type": "object",
"properties": {
"id": {
@@ -328,7 +419,7 @@ var doc = `{
}
}
},
"user_model.UpdateNickNameByIDResponse": {
"user_handler.updateNickNameByIDResponse": {
"type": "object",
"properties": {
"id": {
+114 -23
View File
@@ -26,9 +26,15 @@
"summary": "获取授权信息",
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/demo.authResponse"
"$ref": "#/definitions/demo_handler.authResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -58,7 +64,7 @@
],
"responses": {
"200": {
"description": "用户信息",
"description": "OK",
"schema": {
"type": "array",
"items": {
@@ -75,6 +81,18 @@
}
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
@@ -95,11 +113,11 @@
"parameters": [
{
"description": "请求信息",
"name": "RequestInfo",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_model.CreateRequest"
"$ref": "#/definitions/user_handler.createRequest"
}
},
{
@@ -112,9 +130,21 @@
],
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/user_model.CreateResponse"
"$ref": "#/definitions/user_handler.createResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -122,7 +152,7 @@
},
"/user/delete/{id}": {
"patch": {
"description": "删除用户 - 更新 is_deleted = 1",
"description": "删除用户",
"consumes": [
"application/json"
],
@@ -132,7 +162,7 @@
"tags": [
"User"
],
"summary": "删除用户 - 更新 is_deleted = 1",
"summary": "删除用户",
"parameters": [
{
"type": "integer",
@@ -151,7 +181,22 @@
],
"responses": {
"200": {
"description": "返回信息"
"description": "OK",
"schema": {
"$ref": "#/definitions/user_handler.deleteResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
@@ -187,9 +232,21 @@
],
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/user_model.DetailResponse"
"$ref": "#/definitions/user_handler.detailResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -211,11 +268,11 @@
"parameters": [
{
"description": "请求信息",
"name": "RequestInfo",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/user_model.UpdateNickNameByIDRequest"
"$ref": "#/definitions/user_handler.updateNickNameByIDRequest"
}
},
{
@@ -228,9 +285,21 @@
],
"responses": {
"200": {
"description": "返回信息",
"description": "OK",
"schema": {
"$ref": "#/definitions/user_model.UpdateNickNameByIDResponse"
"$ref": "#/definitions/user_handler.updateNickNameByIDResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
@@ -238,7 +307,20 @@
}
},
"definitions": {
"demo.authResponse": {
"code.Failure": {
"type": "object",
"properties": {
"code": {
"description": "业务码",
"type": "integer"
},
"message": {
"description": "描述信息",
"type": "string"
}
}
},
"demo_handler.authResponse": {
"type": "object",
"properties": {
"authorization": {
@@ -251,7 +333,7 @@
}
}
},
"user_model.CreateRequest": {
"user_handler.createRequest": {
"type": "object",
"properties": {
"mobile": {
@@ -268,7 +350,7 @@
}
}
},
"user_model.CreateResponse": {
"user_handler.createResponse": {
"type": "object",
"properties": {
"id": {
@@ -277,7 +359,16 @@
}
}
},
"user_model.DetailResponse": {
"user_handler.deleteResponse": {
"type": "object",
"properties": {
"id": {
"description": "用户主键ID",
"type": "integer"
}
}
},
"user_handler.detailResponse": {
"type": "object",
"properties": {
"id": {
@@ -285,7 +376,7 @@
"type": "integer"
},
"mobile": {
"description": "手机号",
"description": "手机号(脱敏)",
"type": "string"
},
"nick_name": {
@@ -298,7 +389,7 @@
}
}
},
"user_model.UpdateNickNameByIDRequest": {
"user_handler.updateNickNameByIDRequest": {
"type": "object",
"properties": {
"id": {
@@ -311,7 +402,7 @@
}
}
},
"user_model.UpdateNickNameByIDResponse": {
"user_handler.updateNickNameByIDResponse": {
"type": "object",
"properties": {
"id": {
+84 -23
View File
@@ -1,5 +1,14 @@
definitions:
demo.authResponse:
code.Failure:
properties:
code:
description: 业务码
type: integer
message:
description: 描述信息
type: string
type: object
demo_handler.authResponse:
properties:
authorization:
description: 签名
@@ -8,7 +17,7 @@ definitions:
description: 过期时间
type: integer
type: object
user_model.CreateRequest:
user_handler.createRequest:
properties:
mobile:
description: 手机号
@@ -20,19 +29,25 @@ definitions:
description: 用户名
type: string
type: object
user_model.CreateResponse:
user_handler.createResponse:
properties:
id:
description: 主键ID
type: integer
type: object
user_model.DetailResponse:
user_handler.deleteResponse:
properties:
id:
description: 用户主键ID
type: integer
type: object
user_handler.detailResponse:
properties:
id:
description: 用户主键ID
type: integer
mobile:
description: 手机号
description: 手机号(脱敏)
type: string
nick_name:
description: 昵称
@@ -41,7 +56,7 @@ definitions:
description: 用户名
type: string
type: object
user_model.UpdateNickNameByIDRequest:
user_handler.updateNickNameByIDRequest:
properties:
id:
description: 用户主键ID
@@ -50,7 +65,7 @@ definitions:
description: 昵称
type: string
type: object
user_model.UpdateNickNameByIDResponse:
user_handler.updateNickNameByIDResponse:
properties:
id:
description: 用户主键ID
@@ -74,9 +89,13 @@ paths:
- application/json
responses:
"200":
description: 返回信息
description: OK
schema:
$ref: '#/definitions/demo.authResponse'
$ref: '#/definitions/demo_handler.authResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 获取授权信息
tags:
- Demo
@@ -95,7 +114,7 @@ paths:
- application/json
responses:
"200":
description: 用户信息
description: OK
schema:
items:
properties:
@@ -107,6 +126,14 @@ paths:
type: string
type: object
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: Trace 示例
tags:
- Demo
@@ -118,10 +145,10 @@ paths:
parameters:
- description: 请求信息
in: body
name: RequestInfo
name: Request
required: true
schema:
$ref: '#/definitions/user_model.CreateRequest'
$ref: '#/definitions/user_handler.createRequest'
- description: 签名
in: header
name: Authorization
@@ -131,9 +158,17 @@ paths:
- application/json
responses:
"200":
description: 返回信息
description: OK
schema:
$ref: '#/definitions/user_model.CreateResponse'
$ref: '#/definitions/user_handler.createResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 创建用户
tags:
- User
@@ -141,7 +176,7 @@ paths:
patch:
consumes:
- application/json
description: 删除用户 - 更新 is_deleted = 1
description: 删除用户
parameters:
- description: 用户ID
in: path
@@ -157,8 +192,18 @@ paths:
- application/json
responses:
"200":
description: 返回信息
summary: 删除用户 - 更新 is_deleted = 1
description: OK
schema:
$ref: '#/definitions/user_handler.deleteResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 删除用户
tags:
- User
/user/info/{username}:
@@ -181,9 +226,17 @@ paths:
- application/json
responses:
"200":
description: 返回信息
description: OK
schema:
$ref: '#/definitions/user_model.DetailResponse'
$ref: '#/definitions/user_handler.detailResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 用户详情
tags:
- User
@@ -195,10 +248,10 @@ paths:
parameters:
- description: 请求信息
in: body
name: RequestInfo
name: Request
required: true
schema:
$ref: '#/definitions/user_model.UpdateNickNameByIDRequest'
$ref: '#/definitions/user_handler.updateNickNameByIDRequest'
- description: 签名
in: header
name: Authorization
@@ -208,9 +261,17 @@ paths:
- application/json
responses:
"200":
description: 返回信息
description: OK
schema:
$ref: '#/definitions/user_model.UpdateNickNameByIDResponse'
$ref: '#/definitions/user_handler.updateNickNameByIDResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/code.Failure'
summary: 编辑用户 - 通过用户主键ID更新用户昵称
tags:
- User
+1
View File
@@ -12,6 +12,7 @@ require (
github.com/go-redis/redis/v7 v7.4.0
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.4 // indirect
github.com/jinzhu/gorm v1.9.16
github.com/onsi/ginkgo v1.14.2 // indirect
github.com/onsi/gomega v1.10.4 // indirect
github.com/pkg/errors v0.9.1
+20
View File
@@ -19,6 +19,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -33,6 +34,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -58,6 +60,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
@@ -65,6 +69,8 @@ github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMN
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -123,6 +129,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -192,8 +200,11 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -220,6 +231,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -237,6 +250,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -381,9 +396,11 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -409,6 +426,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -428,6 +446,8 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+3 -4
View File
@@ -1,7 +1,6 @@
## 错误码规则
- 错误码需在 `code.go` 文件中定义。
- 错误码需为 > 1 的数,反之表示正确。
- 错误码需在 `code` 包中进行定义。
#### 错误码为 5 位数
@@ -9,6 +8,6 @@
| :------ | :------ | :------ |
| 服务级错误码 | 模块级错误码 | 具体错误码 |
- 服务级错误码:1 位数进行表示,比如 1 为系统级错误;2 为普通错误,通常是由用户非法操作引起。
- 服务级错误码:1 位数进行表示,比如 1 为系统级错误;2 为普通错误,通常是由用户非法操作引起。
- 模块级错误码:2 位数进行表示,比如 01 为用户模块;02 为订单模块。
- 具体错误码:2 位数进行表示,比如 01 为手机号不合法;02 为验证码输入错误。
- 具体错误码:2 位数进行表示,比如 01 为手机号不合法;02 为验证码输入错误。
+32 -19
View File
@@ -1,28 +1,41 @@
package code
import (
"net/http"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
var (
// OK
OK = errno.NewError(http.StatusOK, 1, "OK")
// 错误时返回结构
type Failure struct {
Code int `json:"code"` // 业务码
Message string `json:"message"` // 描述信息
}
const (
// 服务级错误码
ErrServer = errno.NewError(http.StatusInternalServerError, 10101, http.StatusText(http.StatusInternalServerError))
ErrManyRequest = errno.NewError(http.StatusTooManyRequests, 10102, http.StatusText(http.StatusTooManyRequests))
ErrParamBind = errno.NewError(http.StatusBadRequest, 10103, "参数信息有误")
ErrAuthorization = errno.NewError(http.StatusUnauthorized, 10104, "签名信息有误")
ServerError = 10101
TooManyRequests = 10102
ParamBindError = 10103
AuthorizationError = 10104
CallHTTPError = 10105
// 模块级错误码 - 用户模块
ErrUser = errno.NewError(http.StatusBadRequest, 20101, "非法用户")
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, "调用他方接口失败")
IllegalUserName = 20101
UserCreateError = 20102
UserUpdateError = 20103
UserSearchError = 20104
// ...
)
var codeText = map[int]string{
ServerError: "Internal Server Error",
TooManyRequests: "Too Many Requests",
ParamBindError: "参数信息有误",
AuthorizationError: "签名信息有误",
CallHTTPError: "调用第三方 HTTP 接口失败",
IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
UserUpdateError: "更新用户失败",
UserSearchError: "查询用户失败",
}
func Text(code int) string {
return codeText[code]
}
-223
View File
@@ -1,223 +0,0 @@
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
cache cache.Repo
userService user_service.UserService
}
func NewDemo(logger *zap.Logger, db db.Repo, cache cache.Repo) *Demo {
return &Demo{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
func (d *Demo) Get() core.HandlerFunc {
type request struct {
Name string `uri:"name"`
}
type response struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
if req.Name != "Tom" {
c.AbortWithError(code.ErrUser.WithErr(errors.New("req.Name != Tom")))
return
}
c.Payload(code.OK.WithData(&response{
Name: "Tom",
Job: "Student",
}))
}
}
func (d *Demo) Post() core.HandlerFunc {
type request struct {
Name string `form:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindPostForm(req); err != nil {
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
if req.Name != "Jack" {
c.AbortWithError(code.ErrUser.WithErr(errors.New("req.Name != Jack")))
return
}
c.Payload(code.OK.WithData(&response{
Name: "Jack",
Job: "Teacher",
}))
}
}
type authResponse struct {
Authorization string `json:"authorization"` // 签名
ExpireTime int64 `json:"expire_time"` // 过期时间
}
type traceResponse []struct {
Name string `json:"name"` //用户名
Job string `json:"job"` //工作
}
// 获取授权信息
// @Summary 获取授权信息
// @Description 获取授权信息
// @Tags Demo
// @Accept json
// @Produce json
// @Success 200 {object} authResponse "返回信息"
// @Router /auth/get [post]
func (d *Demo) Auth() core.HandlerFunc {
return func(c core.Context) {
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
}
res := new(authResponse)
res.Authorization = tokenString
res.ExpireTime = time.Now().Add(time.Hour * cfg.ExpireDuration).Unix()
c.Payload(code.OK.WithData(res))
}
}
// 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()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoGetRetryVerify),
)
if err != nil {
d.logger.Error("get [demo/get] err", zap.Error(err))
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()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoPostRetryVerify),
)
if err != nil {
d.logger.Error("post [demo/post] err", zap.Error(err))
c.AbortWithError(code.ErrUserHTTP.WithErr(err))
return
}
// 调试信息
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()))
//// 执行 gRPC 信息
//conn, err := grpclient.New("127.0.0.1:9001",
// grpclient.WithDialTimeout(time.Second*5),
// grpclient.WithTrace(c.Trace()),
// grpclient.WithSign(func(message []byte) (authorization string, err error) {
// return grpclient.GenerateSign("abcdef", message)
// }),
//)
//if err != nil {
// d.logger.Error("grpc conn err", zap.Error(err))
//}
//defer conn.Close()
//
//// 初始化客户端
//client := hello.NewHelloClient(conn)
//
//// 调用 SayHello 方法
//ctx := context.Background()
//// 设置 SayHello 超时间
//ctx, cancel := context.WithTimeout(ctx, time.Second*3)
//defer cancel()
//
//client.SayHello(ctx, &hello.HelloRequest{Name: "Hello World"})
data := &traceResponse{
{
Name: res1.Data.Name,
Job: res1.Data.Job,
},
{
Name: res2.Data.Name,
Job: res2.Data.Job,
},
}
c.Payload(code.OK.WithData(data))
}
}
@@ -0,0 +1,47 @@
package demo_handler
import (
"net/http"
"time"
"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/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"
)
type authResponse struct {
Authorization string `json:"authorization"` // 签名
ExpireTime int64 `json:"expire_time"` // 过期时间
}
// 获取授权信息
// @Summary 获取授权信息
// @Description 获取授权信息
// @Tags Demo
// @Accept json
// @Produce json
// @Success 200 {object} authResponse
// @Failure 400 {object} code.Failure
// @Router /auth/get [post]
func (h *handler) Auth() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(err),
)
return
}
res := new(authResponse)
res.Authorization = tokenString
res.ExpireTime = time.Now().Add(time.Hour * cfg.ExpireDuration).Unix()
c.Payload(res)
}
}
@@ -0,0 +1,48 @@
package demo_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (h *handler) Get() core.HandlerFunc {
type request struct {
Name string `uri:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.Name != "Tom" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.Name != Tom")),
)
return
}
c.Payload(&response{
Name: "Tom",
Job: "Student",
})
}
}
@@ -0,0 +1,48 @@
package demo_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/pkg/errors"
)
func (h *handler) Post() core.HandlerFunc {
type request struct {
Name string `form:"name"`
}
type response struct {
Name string `json:"name"`
Job string `json:"job"`
}
return func(c core.Context) {
req := new(request)
if err := c.ShouldBindPostForm(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.Name != "Jack" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.Name != Jack")),
)
return
}
c.Payload(&response{
Name: "Jack",
Job: "Teacher",
})
}
}
@@ -0,0 +1,107 @@
package demo_handler
import (
"net/http"
"time"
"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/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
"github.com/xinliangnote/go-gin-api/pkg/p"
"go.uber.org/zap"
)
type traceResponse []struct {
Name string `json:"name"` //用户名
Job string `json:"job"` //工作
}
// Trace 示例
// @Summary Trace 示例
// @Description Trace 示例
// @Tags Demo
// @Accept json
// @Produce json
// @Param Authorization header string true "签名"
// @Success 200 {object} traceResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /demo/trace [get]
func (h *handler) 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()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoGetRetryVerify),
)
if err != nil {
h.logger.Error("get [demo/get] err", zap.Error(err))
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.CallHTTPError,
code.Text(code.CallHTTPError)).WithErr(err),
)
return
}
// 调试信息
p.Println("res1.Name", res1.Name, p.WithTrace(c.Trace()))
// 三方请求信息
res2, err := go_gin_api_repo.DemoPost("Jack",
httpclient.WithTTL(time.Second*5),
httpclient.WithTrace(c.Trace()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
httpclient.WithOnFailedRetry(3, time.Second*1, go_gin_api_repo.DemoPostRetryVerify),
)
if err != nil {
h.logger.Error("post [demo/post] err", zap.Error(err))
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.CallHTTPError,
code.Text(code.CallHTTPError)).WithErr(err),
)
return
}
// 调试信息
p.Println("res2.Name",
res2.Name,
p.WithTrace(c.Trace()),
)
// 执行 SQL 信息
h.userService.GetUserByUserName(c, "test_user")
// 执行 Redis 信息
_ = h.cache.Set("name", "tom", time.Minute*10, cache.WithTrace(c.Trace()))
val, _ := h.cache.Get("name", cache.WithTrace(c.Trace()))
p.Println("redis-name", val, p.WithTrace(c.Trace()))
// 初始化客户端
// client := hello.NewHelloClient(d.grpConn.Conn())
// client.SayHello(grpc.ContextWithValueAndTimeout(c, time.Second*3), &hello.HelloRequest{Name: "Hello World"})
data := &traceResponse{
{
Name: res1.Name,
Job: res1.Job,
},
{
Name: res2.Name,
Job: res2.Job,
},
}
c.Payload(data)
}
}
@@ -0,0 +1,44 @@
package demo_handler
import (
"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/internal/pkg/grpc"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// 示例:支持 get 请求的方法
Get() core.HandlerFunc
// 示例:支持 post 请求的方法
Post() core.HandlerFunc
// 获取授权信息
Auth() core.HandlerFunc
// Trace 示例
Trace() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
grpConn grpc.ClientConn
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) Handler {
return &handler{
logger: logger,
cache: cache,
grpConn: grpConn,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}
@@ -0,0 +1,77 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"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/errno"
"github.com/pkg/errors"
)
type createRequest struct {
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
type createResponse struct {
Id int32 `json:"id"` // 主键ID
}
// 创建用户
// @Summary 创建用户
// @Description 创建用户
// @Tags User
// @Accept json
// @Produce json
// @Param Request body createRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} createResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/create [post]
func (h *handler) Create() core.HandlerFunc {
return func(c core.Context) {
req := new(createRequest)
res := new(createResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
if req.UserName == "" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.IllegalUserName,
code.Text(code.IllegalUserName)).WithErr(errors.New("req.UserName = ''")),
)
return
}
createUserData := new(user_service.CreateUserInfo)
createUserData.Mobile = req.Mobile
createUserData.NickName = req.NickName
createUserData.UserName = req.UserName
id, err := h.userService.Create(c, createUserData)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserCreateError,
code.Text(code.UserCreateError)).WithErr(err),
)
return
}
res.Id = id
c.Payload(res)
}
}
@@ -0,0 +1,57 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type deleteRequest struct {
Id int32 `uri:"id"` // 用户ID
}
type deleteResponse struct {
Id int32 `json:"id"` // 用户主键ID
}
// 删除用户
// @Summary 删除用户
// @Description 删除用户
// @Tags User
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param Authorization header string true "签名"
// @Success 200 {object} deleteResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/delete/{id} [patch]
func (h *handler) Delete() core.HandlerFunc {
return func(c core.Context) {
req := new(deleteRequest)
res := new(deleteResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
err := h.userService.Delete(c, req.Id)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserUpdateError,
code.Text(code.UserUpdateError)).WithErr(err),
)
return
}
res.Id = req.Id
c.Payload(res)
}
}
@@ -0,0 +1,65 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/ddm"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type detailRequest struct {
UserName string `uri:"username"` // 用户名
}
type detailResponse struct {
Id int32 `json:"id"` // 用户主键ID
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile ddm.Mobile `json:"mobile"` // 手机号(脱敏)
}
// 用户详情
// @Summary 用户详情
// @Description 用户详情
// @Tags User
// @Accept json
// @Produce json
// @Param username path string true "用户名"
// @Param Authorization header string true "签名"
// @Success 200 {object} detailResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/info/{username} [get]
func (h *handler) Detail() core.HandlerFunc {
return func(c core.Context) {
req := new(detailRequest)
res := new(detailResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
user, err := h.userService.GetUserByUserName(c, req.UserName)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserSearchError,
code.Text(code.UserSearchError)).WithErr(err),
)
return
}
res.Id = user.Id
res.UserName = user.UserName
res.NickName = user.NickName
res.Mobile = ddm.Mobile(user.Mobile)
c.Payload(res)
}
}
@@ -0,0 +1,59 @@
package user_handler
import (
"net/http"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
)
type updateNickNameByIDRequest struct {
Id int32 `json:"id"` // 用户主键ID
NickName string `json:"nick_name"` // 昵称
}
type updateNickNameByIDResponse struct {
Id int32 `json:"id"` // 用户主键ID
}
// 编辑用户 - 通过用户主键ID更新用户昵称
// @Summary 编辑用户 - 通过用户主键ID更新用户昵称
// @Description 编辑用户 - 通过用户主键ID更新用户昵称
// @Tags User
// @Accept json
// @Produce json
// @Param Request body updateNickNameByIDRequest true "请求信息"
// @Param Authorization header string true "签名"
// @Success 200 {object} updateNickNameByIDResponse
// @Failure 400 {object} code.Failure
// @Failure 401 {object} code.Failure
// @Router /user/update [put]
func (h *handler) UpdateNickNameByID() core.HandlerFunc {
return func(c core.Context) {
req := new(updateNickNameByIDRequest)
res := new(updateNickNameByIDResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ParamBindError,
code.Text(code.ParamBindError)).WithErr(err),
)
return
}
err := h.userService.UpdateNickNameByID(c, req.Id, req.NickName)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.UserUpdateError,
code.Text(code.UserUpdateError)).WithErr(err),
)
return
}
res.Id = req.Id
c.Payload(res)
}
}
@@ -0,0 +1,41 @@
package user_handler
import (
"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"
"go.uber.org/zap"
)
var _ Handler = (*handler)(nil)
type Handler interface {
// i 为了避免被其他包实现
i()
// Create 创建用户
Create() core.HandlerFunc
// UpdateNickNameByID 编辑用户 - 通过主键ID更新用户昵称
UpdateNickNameByID() core.HandlerFunc
// Delete 删除用户
Delete() core.HandlerFunc
// Detail 用户详情
Detail() core.HandlerFunc
}
type handler struct {
logger *zap.Logger
cache cache.Repo
userService user_service.UserService
}
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
return &handler{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
func (h *handler) i() {}
@@ -1,176 +0,0 @@
package user_handler
import (
"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/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/ddm"
"go.uber.org/zap"
)
var _ UserDemo = (*userDemo)(nil)
type UserDemo interface {
// i 为了避免被其他包实现
i()
// Create 创建用户
Create() core.HandlerFunc
// UpdateNickNameByID 编辑用户 - 通过主键ID更新用户昵称
UpdateNickNameByID() 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, cache cache.Repo) UserDemo {
return &userDemo{
logger: logger,
cache: cache,
userService: user_service.NewUserService(db, cache),
}
}
func (u *userDemo) i() {}
// 创建用户
// @Summary 创建用户
// @Description 创建用户
// @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 {
return func(c core.Context) {
req := new(user_model.CreateRequest)
res := new(user_model.CreateResponse)
if err := c.ShouldBindJSON(req); err != nil {
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 {
c.AbortWithError(code.ErrUserCreate.WithErr(err))
return
}
res.Id = id
c.Payload(code.OK.WithData(res))
}
}
// 编辑用户 - 通过用户主键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 [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 {
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
err := u.userService.UpdateNickNameByID(c, req.Id, req.NickName)
if err != nil {
c.AbortWithError(code.ErrUserUpdate.WithErr(err))
return
}
res.Id = req.Id
c.Payload(code.OK.WithData(res))
}
}
// 删除用户 - 更新 is_deleted = 1
// @Summary 删除用户 - 更新 is_deleted = 1
// @Description 删除用户 - 更新 is_deleted = 1
// @Tags User
// @Accept json
// @Produce json
// @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.DeleteRequest)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
err := u.userService.Delete(c, req.Id)
if err != nil {
c.AbortWithError(code.ErrUserUpdate.WithErr(err))
return
}
c.Payload(code.OK.WithData(nil))
}
}
// 用户详情
// @Summary 用户详情
// @Description 用户详情
// @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 {
return func(c core.Context) {
req := new(user_model.DetailRequest)
res := new(user_model.DetailResponse)
if err := c.ShouldBindURI(req); err != nil {
c.AbortWithError(code.ErrParamBind.WithErr(err))
return
}
user, err := u.userService.GetUserByUserName(c, req.UserName)
if err != nil {
c.AbortWithError(code.ErrUserSearch.WithErr(err))
return
}
res.Id = user.Id
res.UserName = user.UserName
res.NickName = user.NickName
res.Mobile = ddm.Mobile(user.Mobile)
c.Payload(code.OK.WithData(res))
}
}
-10
View File
@@ -1,10 +0,0 @@
## model
实体层。
- 请求实体、返回实体。
- 数据库实体。
命名规范:
- 包名以 `_model` 结尾。
-63
View File
@@ -1,63 +0,0 @@
package user_model
import (
"time"
"github.com/xinliangnote/go-gin-api/pkg/ddm"
)
// 用户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 Delete Request
type DeleteRequest struct {
Id uint `uri:"id"` // 用户ID
}
// 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 ddm.Mobile `json:"mobile"` // 手机号(脱敏)
}
+13 -10
View File
@@ -1,16 +1,12 @@
## repository
数据访问层。
#### 数据访问层。
- `./db_repo` 访问 DB 数据
- `./cache_repo` 访问 Cache 数据
- `./third_party_request` 访问外部 HTTP 接口数据。
SQL 建议:
- 禁止使用 SQL k v 拼接,好处是避免 SQL 注入;
- 禁止使用连表查询,好处是易扩展,比如分库分表;
- 禁止使用万能方法,好处是便于后期维护,比如字段调整;
- 禁止使用删除方法,好处是避免数据丢失;
#### SQL 建议:
- 建议每张表需包含字段:主键(id)、标记删除(is_deteled)、创建时间(created_at)、更新时间(updated_at)
```mysql
@@ -19,11 +15,18 @@ SQL 建议:
`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` 命名;
#### 脚本生成 MySQL CURD
1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割;
1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`
以用户表(user_demo)为例:
- 结构体文件:user_demo_repo/gen_model.go
- CURD 方法文件:user_demo_repo/gen_user_demo.go
- 表结构 MD 文件:user_demo_repo/gen_table.md
+14
View File
@@ -0,0 +1,14 @@
package db_repo
// Predicate is a string that acts as a condition in the where clause
type Predicate string
var (
EqualPredicate = Predicate("=")
NotEqualPredicate = Predicate("<>")
GreaterThanPredicate = Predicate(">")
GreaterThanOrEqualPredicate = Predicate(">=")
SmallerThanPredicate = Predicate("<")
SmallerThanOrEqualPredicate = Predicate("<=")
LikePredicate = Predicate("LIKE")
)
@@ -0,0 +1,15 @@
package user_demo_repo
import "time"
// 用户Demo表
//go:generate gormgen -structs UserDemo -input .
type UserDemo struct {
Id int32 // 主键
UserName string // 用户名
NickName string // 昵称
Mobile string // 手机号
IsDeleted int32 // 是否删除 1:是 -1:否
CreatedAt time.Time `gorm:"time"` // 创建时间
UpdatedAt time.Time `gorm:"time"` // 更新时间
}
@@ -0,0 +1,12 @@
#### xin_ceshi.user_demo
用户Demo表
| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | |
| 2 | user_name | 用户名 | varchar(32) | | NO | | |
| 3 | nick_name | 昵称 | varchar(100) | | NO | | |
| 4 | mobile | 手机号 | varchar(20) | | NO | | |
| 5 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 |
| 6 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP |
| 7 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP |
@@ -0,0 +1,257 @@
///////////////////////////////////////////////////////////
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
// ANY CHANGES DONE HERE WILL BE LOST //
///////////////////////////////////////////////////////////
package user_demo_repo
import (
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
"github.com/pkg/errors"
"gorm.io/gorm"
)
func NewModel() *UserDemo {
return new(UserDemo)
}
func NewQueryBuilder() *userDemoRepoQueryBuilder {
return new(userDemoRepoQueryBuilder)
}
func (t *UserDemo) Create(db *gorm.DB) (id int32, err error) {
if err = db.Create(t).Error; err != nil {
return 0, errors.Wrap(err, "create err")
}
return t.Id, nil
}
func (t *UserDemo) Delete(db *gorm.DB) (err error) {
if err = db.Delete(t).Error; err != nil {
return errors.Wrap(err, "delete err")
}
return nil
}
func (t *UserDemo) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
return errors.Wrap(err, "updates err")
}
return nil
}
type userDemoRepoQueryBuilder struct {
order []string
where []struct {
prefix string
value interface{}
}
limit int
offset int
}
func (qb *userDemoRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB {
ret := db
for _, where := range qb.where {
ret = ret.Where(where.prefix, where.value)
}
for _, order := range qb.order {
ret = ret.Order(order)
}
ret = ret.Limit(qb.limit).Offset(qb.offset)
return ret
}
func (qb *userDemoRepoQueryBuilder) Count(db *gorm.DB) (int64, error) {
var c int64
res := qb.buildQuery(db).Model(&UserDemo{}).Count(&c)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
c = 0
}
return c, res.Error
}
func (qb *userDemoRepoQueryBuilder) First(db *gorm.DB) (*UserDemo, error) {
ret := &UserDemo{}
res := qb.buildQuery(db).First(ret)
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
ret = nil
}
return ret, res.Error
}
func (qb *userDemoRepoQueryBuilder) QueryOne(db *gorm.DB) (*UserDemo, error) {
qb.limit = 1
ret, err := qb.QueryAll(db)
if len(ret) > 0 {
return ret[0], err
}
return nil, err
}
func (qb *userDemoRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*UserDemo, error) {
var ret []*UserDemo
err := qb.buildQuery(db).Find(&ret).Error
return ret, err
}
func (qb *userDemoRepoQueryBuilder) Limit(limit int) *userDemoRepoQueryBuilder {
qb.limit = limit
return qb
}
func (qb *userDemoRepoQueryBuilder) Offset(offset int) *userDemoRepoQueryBuilder {
qb.offset = offset
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "id", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderById(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "id "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUserName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "user_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUserName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "user_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereNickName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "nick_name", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByNickName(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "nick_name "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "mobile", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByMobile(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "mobile "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "is_deleted", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByIsDeleted(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "is_deleted "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "created_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByCreatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "created_at "+order)
return qb
}
func (qb *userDemoRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder {
qb.where = append(qb.where, struct {
prefix string
value interface{}
}{
fmt.Sprintf("%v %v ?", "updated_at", p),
value,
})
return qb
}
func (qb *userDemoRepoQueryBuilder) OrderByUpdatedAt(asc bool) *userDemoRepoQueryBuilder {
order := "DESC"
if asc {
order = "ASC"
}
qb.order = append(qb.order, "updated_at "+order)
return qb
}
@@ -1,81 +0,0 @@
package user_demo_repo
import (
"github.com/xinliangnote/go-gin-api/internal/api/model/user_model"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/pkg/errors"
"gorm.io/gorm"
)
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)
Delete(ctx core.Context, id uint) (err error)
getUserByID(ctx core.Context, id uint) (*user_model.UserDemo, error)
}
type userRepo struct {
db db.Repo
}
func NewUserRepo(db db.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.RequestContext()).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.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
}
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] get user data err")
}
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] get 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.RequestContext()).
Select([]string{"id", "user_name", "nick_name", "mobile"}).
Where("user_name = ? and is_deleted = ?", username, -1).
First(data).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errors.Wrap(err, "[user_demo] get user data err")
}
return data, nil
}
@@ -2,7 +2,6 @@ package go_gin_api_repo
import (
"encoding/json"
"fmt"
"net/url"
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
@@ -11,23 +10,13 @@ import (
)
type demoGetResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Name string `json:"name"`
Job string `json:"job"`
} `json:"data"`
ID string `json:"id"`
Name string `json:"name"`
Job string `json:"job"`
}
type demoPostResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Name string `json:"name"`
Job string `json:"job"`
} `json:"data"`
ID string `json:"id"`
Name string `json:"name"`
Job string `json:"job"`
}
func DemoGet(name string, opts ...httpclient.Option) (res *demoGetResponse, err error) {
@@ -43,10 +32,6 @@ func DemoGet(name string, opts ...httpclient.Option) (res *demoGetResponse, err
return nil, errors.Wrap(err, "DemoGet json unmarshal error")
}
if res.Code != 1 {
return nil, errors.New(fmt.Sprintf("code err: %d-%s", res.Code, res.Msg))
}
return res, nil
}
@@ -55,17 +40,7 @@ func DemoGetRetryVerify(body []byte) (shouldRetry bool) {
return true
}
type Response struct {
Code int `json:"code"`
}
resp := new(Response)
if err := json.Unmarshal(body, resp); err != nil {
return true
}
// 例如 无需重试的 code 码,code !=1 需要重试
successCode := 1
return resp.Code != successCode
return false
}
func DemoPost(name string, opts ...httpclient.Option) (res *demoPostResponse, err error) {
@@ -83,10 +58,6 @@ func DemoPost(name string, opts ...httpclient.Option) (res *demoPostResponse, er
return nil, errors.Wrap(err, "DemoPost json unmarshal error")
}
if res.Code != 1 {
return nil, errors.New(fmt.Sprintf("code err: %d-%s", res.Code, res.Msg))
}
return res, nil
}
@@ -95,15 +66,5 @@ func DemoPostRetryVerify(body []byte) (shouldRetry bool) {
return true
}
type Response struct {
Code int `json:"code"`
}
resp := new(Response)
if err := json.Unmarshal(body, resp); err != nil {
return true
}
// 例如 无需重试的 code 码,code !=1 需要重试
successCode := 1
return resp.Code != successCode
return false
}
@@ -4,10 +4,8 @@ import "encoding/json"
func MockDemoGet() (body []byte) {
res := new(demoGetResponse)
res.Code = 1
res.Msg = "ok"
res.Data.Name = "AA"
res.Data.Job = "AA_JOB"
res.Name = "AA"
res.Job = "AA_JOB"
body, _ = json.Marshal(res)
return body
@@ -15,10 +13,8 @@ func MockDemoGet() (body []byte) {
func MockDemoPost() (body []byte) {
res := new(demoPostResponse)
res.Code = 1
res.Msg = "ok"
res.Data.Name = "BB"
res.Data.Job = "BB_JOB"
res.Name = "BB"
res.Job = "BB_JOB"
body, _ = json.Marshal(res)
return body
@@ -9,7 +9,7 @@ import (
"github.com/xinliangnote/go-gin-api/pkg/httpclient"
)
var authorization = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOjEwLCJVc2VyTmFtZSI6IjEyMyIsImV4cCI6MTYxMDcxODA3NCwiaWF0IjoxNjEwNjMxNjc0LCJpc3MiOiJnby1naW4tYXBpIiwibmJmIjoxNjEwNjMxNjc0fQ.S3T4MaIaz3XjkbJ-xkMDkwzuZ_jfZ8ZRf4cPMz0oXBE"
var authorization = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOjEsIlVzZXJOYW1lIjoieGlubGlhbmdub3RlIiwiZXhwIjoxNjEzODI3MTEzLCJpYXQiOjE2MTM3NDA3MTMsIm5iZiI6MTYxMzc0MDcxM30.SnooP1ikO33ryGPdohsmOKqISa-bWzMkMvUNb5f2zc0"
func TestDemoGet(t *testing.T) {
res, err := DemoGet("Tom",
+17 -3
View File
@@ -1,6 +1,8 @@
package auth
import (
"net/http"
"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"
@@ -13,20 +15,32 @@ import (
func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Error) {
auth := ctx.GetHeader("Authorization")
if auth == "" {
err = code.ErrAuthorization.WithErr(errors.New("Header 中缺少 Authorization 参数"))
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("Header 中缺少 Authorization 参数"))
return
}
cfg := configs.Get().JWT
claims, errParse := token.New(cfg.Secret).Parse(auth)
if errParse != nil {
err = code.ErrAuthorization.WithErr(errParse)
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errParse)
return
}
userId = claims.UserID
if userId <= 0 {
err = code.ErrAuthorization.WithErr(errors.New("claims.UserID <= 0 "))
err = errno.NewError(
http.StatusUnauthorized,
code.AuthorizationError,
code.Text(code.AuthorizationError)).WithErr(errors.New("claims.UserID <= 0 "))
return
}
userName = claims.UserName
+6 -4
View File
@@ -1,13 +1,14 @@
package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo"
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"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"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
@@ -15,7 +16,7 @@ import (
"go.uber.org/zap"
)
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo) (core.Mux, error) {
func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.ClientConn) (core.Mux, error) {
if logger == nil {
return nil, errors.New("logger required")
@@ -32,8 +33,6 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo) (core.Mux, err
panic(err)
}
demoHandler := demo.NewDemo(logger, db, cache)
userHandler := user_handler.NewUserDemo(logger, db, cache)
gqlHandler := handler.New(logger, db, cache)
gql := mux.Group("/graphql")
@@ -42,6 +41,9 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo) (core.Mux, err
gql.POST("/query", gqlHandler.Query())
}
demoHandler := demo_handler.New(logger, db, cache, grpConn)
userHandler := user_handler.New(logger, db, cache)
// user_demo CURD
user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler))
{
@@ -0,0 +1,25 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
type CreateUserInfo struct {
UserName string `json:"user_name"` // 用户名
NickName string `json:"nick_name"` // 昵称
Mobile string `json:"mobile"` // 手机号
}
func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id int32, err error) {
model := user_demo_repo.NewModel()
model.UserName = user.UserName
model.NickName = user.NickName
model.Mobile = user.Mobile
id, err = model.Create(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return 0, err
}
return
}
@@ -0,0 +1,16 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) Delete(ctx core.Context, id int32) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
err = model.Delete(u.db.GetDbW().WithContext(ctx.RequestContext()))
if err != nil {
return nil
}
return nil
}
@@ -0,0 +1,23 @@
package user_service
import (
"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"
)
func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error) {
user, err = user_demo_repo.NewQueryBuilder().
WhereUserName(db_repo.EqualPredicate, username).
QueryOne(u.db.GetDbR().WithContext(ctx.RequestContext()))
if err != nil {
return user, err
}
if user == nil {
user = user_demo_repo.NewModel()
}
return user, nil
}
@@ -0,0 +1,22 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
)
func (u *userSer) UpdateNickNameByID(ctx core.Context, id int32, nickname string) (err error) {
model := user_demo_repo.NewModel()
model.Id = id
data := map[string]interface{}{
"nick_name": nickname,
}
err = model.Updates(u.db.GetDbW().WithContext(ctx.RequestContext()), data)
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,34 @@
package user_service
import (
"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)
type UserService interface {
// i 为了避免被其他包实现
i()
Create(ctx core.Context, user *CreateUserInfo) (id int32, err error)
UpdateNickNameByID(ctx core.Context, id int32, username string) (err error)
GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error)
Delete(ctx core.Context, id int32) (err error)
}
type userSer struct {
db db.Repo
cache cache.Repo
}
func NewUserService(db db.Repo, cache cache.Repo) UserService {
return &userSer{
db: db,
cache: cache,
}
}
func (u *userSer) i() {}
-76
View File
@@ -1,76 +0,0 @@
package user_service
import (
"github.com/xinliangnote/go-gin-api/internal/api/model/user_model"
"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)
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)
Delete(ctx core.Context, id uint) (err error)
}
type userSer struct {
db db.Repo
cache cache.Repo
userRepo user_demo_repo.UserRepo
}
func NewUserService(db db.Repo, cache cache.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
}
func (u *userSer) Delete(ctx core.Context, id uint) (err error) {
err = u.userRepo.Delete(ctx, id)
if err != nil {
return nil
}
return nil
}
+3 -3
View File
@@ -35,7 +35,7 @@ type Repo interface {
ExpireAt(key string, ttl time.Time) bool
Del(keys ...string) bool
Incr(key string, options ...Option) int64
Close()
Close() error
}
type cacheRepo struct {
@@ -179,8 +179,8 @@ func (c *cacheRepo) Incr(key string, options ...Option) int64 {
}
// Close close redis client
func (c *cacheRepo) Close() {
c.client.Close()
func (c *cacheRepo) Close() error {
return c.client.Close()
}
// WithTrace 设置trace信息
+5 -5
View File
@@ -90,8 +90,8 @@ type Context interface {
setLogger(logger *zap.Logger)
// Payload 正确返回
Payload(payload errno.Error)
getPayload() errno.Error
Payload(payload interface{})
getPayload() interface{}
// GraphPayload GraphQL返回值 与 api 返回结构不同
GraphPayload(payload interface{})
@@ -225,14 +225,14 @@ func (c *context) setLogger(logger *zap.Logger) {
c.ctx.Set(_LoggerName, logger)
}
func (c *context) getPayload() errno.Error {
func (c *context) getPayload() interface{} {
if payload, ok := c.ctx.Get(_PayloadName); ok != false {
return payload.(errno.Error)
return payload
}
return nil
}
func (c *context) Payload(payload errno.Error) {
func (c *context) Payload(payload interface{}) {
c.ctx.Set(_PayloadName, payload)
}
+30 -17
View File
@@ -336,7 +336,11 @@ 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.AbortWithError(code.ErrServer)
context.AbortWithError(errno.NewError(
http.StatusInternalServerError,
code.ServerError,
code.Text(code.ServerError)),
)
if notify := opt.panicNotify; notify != nil {
notify(context, err, stackInfo)
@@ -348,7 +352,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
var (
response errno.Error
response interface{}
businessCode int
businessCodeMsg string
abortErr error
@@ -364,23 +368,28 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
if err := context.abortError(); err != nil { // customer err
multierr.AppendInto(&abortErr, err.GetErr())
response = err
businessCode = err.GetBusinessCode()
businessCodeMsg = err.GetMsg()
if x := context.Trace(); x != nil {
context.SetHeader(trace.Header, x.ID())
traceId = x.ID()
}
ctx.JSON(err.GetHttpCode(), &code.Failure{
Code: businessCode,
Message: businessCodeMsg,
})
}
} else {
response = context.getPayload()
}
if response != nil {
if x := context.Trace(); x != nil {
context.SetHeader(trace.Header, x.ID())
response.WithID(x.ID())
traceId = x.ID()
} else {
response.WithID("")
if response != nil {
if x := context.Trace(); x != nil {
context.SetHeader(trace.Header, x.ID())
traceId = x.ID()
}
ctx.JSON(http.StatusOK, response)
}
businessCode = response.GetBusinessCode()
businessCodeMsg = response.GetMsg()
ctx.JSON(response.GetHttpCode(), response)
}
graphResponse = context.getGraphPayload()
@@ -464,7 +473,11 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
defer releaseContext(context)
if !limiter.Allow() {
context.AbortWithError(code.ErrManyRequest)
context.AbortWithError(errno.NewError(
http.StatusTooManyRequests,
code.TooManyRequests,
code.Text(code.TooManyRequests)),
)
return
}
@@ -489,7 +502,7 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
Host: ctx.Host(),
Status: "ok",
}
ctx.Payload(code.OK.WithData(resp))
ctx.Payload(resp)
})
}
+4
View File
@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
var _ Repo = (*dbRepo)(nil)
@@ -80,6 +81,9 @@ func dbConnect(user, pass, addr, dbName string) (*gorm.DB, error) {
"Local")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
})
+53
View File
@@ -0,0 +1,53 @@
package grpc
import (
"context"
"time"
"google.golang.org/grpc"
)
var _ ClientConn = (*clientConn)(nil)
type ClientConn interface {
i()
Conn() *grpc.ClientConn
}
type clientConn struct {
conn *grpc.ClientConn
}
func New() (ClientConn, error) {
// TODO 需从配置文件中获取
//target := "127.0.0.1:9988"
//secret := "abcdef"
//
//clientInterceptor := NewClientInterceptor(func(message []byte) (authorization string, err error) {
// return GenerateSign(secret, message)
//})
//
//conn, err := grpclient.New(target,
// grpclient.WithKeepAlive(keepAlive),
// grpclient.WithDialTimeout(time.Second*5),
// grpclient.WithUnaryInterceptor(clientInterceptor.UnaryInterceptor),
//)
//
//return &clientConn{
// conn: conn,
//}, err
return nil, nil
}
func (c *clientConn) i() {}
func (c *clientConn) Conn() *grpc.ClientConn {
return c.conn
}
func ContextWithValueAndTimeout(value interface{}, duration time.Duration) context.Context {
ctx, _ := context.WithTimeout(context.Background(), duration)
return context.WithValue(ctx, ClientWithContextKey, value)
}
+152
View File
@@ -0,0 +1,152 @@
package grpc
import (
"context"
"fmt"
"net/http"
"runtime/debug"
"time"
"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/notify"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/p"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
const (
// Trace ID
TraceID = "trace-id"
)
type clientWithContextKeyType struct{ name string }
var ClientWithContextKey = clientWithContextKeyType{"_client_with_context"}
// ClientInterceptor the client's interceptor
type ClientInterceptor struct {
sign Sign
}
// NewClientInterceptor create a client interceptor
func NewClientInterceptor(sign Sign) *ClientInterceptor {
return &ClientInterceptor{
sign: sign,
}
}
// UnaryInterceptor a interceptor for client unary operations
func (c *ClientInterceptor) UnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
var (
invokerErr error
ts = time.Now()
coreContext = ctx.Value(ClientWithContextKey).(core.Context)
)
defer func() { // double recover for safety
if err := recover(); err != nil {
stackInfo := string(debug.Stack())
coreContext.Logger().Error("UnaryInterceptor got double panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo))
coreContext.AbortWithError(errno.NewError(
http.StatusInternalServerError,
code.ServerError,
code.Text(code.ServerError)),
)
notify.OnPanicNotify(coreContext, err, stackInfo)
}
}()
defer func() {
if err := recover(); err != nil {
stackInfo := string(debug.Stack())
coreContext.Logger().Error("UnaryInterceptor got panic", zap.String("panic", fmt.Sprintf("%+v", err)), zap.String("stack", stackInfo))
coreContext.AbortWithError(errno.NewError(
http.StatusInternalServerError,
code.ServerError,
code.Text(code.ServerError)),
)
notify.OnPanicNotify(coreContext, err, stackInfo)
}
if coreContext.Trace() != nil {
var mapReq, mapReply map[string]interface{}
mapReq, err := ProtoMessage2Map(req.(proto.Message))
if err != nil {
p.Println("req ProtoMessage2Map err", err, p.WithTrace(coreContext.Trace()))
}
mapReply, err = ProtoMessage2Map(reply.(proto.Message))
if err != nil {
p.Println("reply ProtoMessage2Map err", err, p.WithTrace(coreContext.Trace()))
}
meta, _ := metadata.FromOutgoingContext(ctx)
gRPCTrace := new(trace.Grpc)
gRPCTrace.Timestamp = time_parse.CSTLayoutString()
gRPCTrace.Addr = cc.Target()
gRPCTrace.Method = method
gRPCTrace.Meta = meta
gRPCTrace.Request = mapReq
gRPCTrace.Response = mapReply
gRPCTrace.CostSeconds = time.Since(ts).Seconds()
if invokerErr != nil {
statusErr, ok := status.FromError(invokerErr)
if ok {
gRPCTrace.Code = statusErr.Code().String()
gRPCTrace.Message = statusErr.Message()
}
}
coreContext.Trace().AppendGRPC(gRPCTrace)
}
}()
if c.sign != nil {
var (
raw string
signature string
err error
)
if req != nil {
if raw, err = ProtoMessage2JSON(req.(proto.Message)); err != nil {
return err
}
}
if signature, err = c.sign([]byte(raw)); err != nil {
return err
}
meta, _ := metadata.FromOutgoingContext(ctx)
if meta == nil {
meta = make(metadata.MD)
}
meta.Set(ProxyAuthorization, signature)
ctx = metadata.NewOutgoingContext(ctx, meta)
}
if coreContext.Trace() != nil {
meta, _ := metadata.FromOutgoingContext(ctx)
if meta == nil {
meta = make(metadata.MD)
}
meta.Set(TraceID, coreContext.Trace().ID())
ctx = metadata.NewOutgoingContext(ctx, meta)
}
invokerErr = invoker(ctx, method, req, reply, cc, opts...)
return invokerErr
}
+15
View File
@@ -0,0 +1,15 @@
package grpc
import (
"time"
"google.golang.org/grpc/keepalive"
)
var (
keepAlive = &keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: time.Second,
PermitWithoutStream: true,
}
)
@@ -1,4 +1,4 @@
package grpclient
package grpc
import (
"bytes"
@@ -1,4 +1,4 @@
package grpclient
package grpc
import (
"bytes"
+26 -2
View File
@@ -10,6 +10,7 @@ import (
"github.com/xinliangnote/go-gin-api/internal/api/router"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"github.com/xinliangnote/go-gin-api/pkg/env"
"github.com/xinliangnote/go-gin-api/pkg/logger"
"github.com/xinliangnote/go-gin-api/pkg/shutdown"
@@ -54,8 +55,14 @@ func main() {
loggers.Fatal("new cache err", zap.Error(err))
}
// 初始化 gRPC client
gRPCRepo, err := grpc.New()
if err != nil {
loggers.Fatal("new grpc err", zap.Error(err))
}
// 初始化 HTTP 服务
mux, err := router.NewHTTPMux(loggers, dbRepo, cacheRepo)
mux, err := router.NewHTTPMux(loggers, dbRepo, cacheRepo, gRPCRepo)
if err != nil {
panic(err)
}
@@ -89,16 +96,33 @@ func main() {
func() {
if err := dbRepo.DbWClose(); err != nil {
loggers.Error("dbw close err", zap.Error(err))
} else {
loggers.Info("dbw close success")
}
if err := dbRepo.DbRClose(); err != nil {
loggers.Error("dbr close err", zap.Error(err))
} else {
loggers.Info("dbr close success")
}
},
// 关闭 cache
func() {
cacheRepo.Close()
if err := cacheRepo.Close(); err != nil {
loggers.Error("cache close err", zap.Error(err))
} else {
loggers.Info("cache close success")
}
},
// 关闭 gRPC client
//func() {
// if err := gRPCRepo.Conn().Close(); err != nil {
// loggers.Error("gRPC client close err", zap.Error(err))
// } else {
// loggers.Info("gRPC client close success")
// }
//},
)
}
+10 -32
View File
@@ -11,10 +11,6 @@ var _ Error = (*err)(nil)
type Error interface {
// i 为了避免被其他包实现
i()
// WithData 设置成功时返回的数据
WithData(data interface{}) Error
// WithID 设置当前请求的唯一ID
WithID(id string) Error
// WithErr 设置错误信息
WithErr(err error) Error
// GetBusinessCode 获取 Business Code
@@ -30,36 +26,22 @@ type Error interface {
}
type err struct {
HttpCode int `json:"-"` // HTTP Code
BusinessCode int `json:"code"` // Business Code
Msg string `json:"msg"` // 描述信息
Data interface{} `json:"data"` // 接口数据
Err error `json:"-"` // 错误信息
ID string `json:"id,omitempty"` // 当前请求的唯一ID,便于问题定位,忽略也可以
HttpCode int // HTTP Code
BusinessCode int // Business Code
Message string // 描述信息
Err error // 错误信息
}
func NewError(httpCode, businessCode int, msg string) Error {
return &err{
HttpCode: httpCode,
BusinessCode: businessCode,
Msg: msg,
Data: nil,
Err: nil,
Message: msg,
}
}
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) WithErr(err error) Error {
e.Err = errors.WithStack(err)
return e
@@ -74,7 +56,7 @@ func (e *err) GetBusinessCode() int {
}
func (e *err) GetMsg() string {
return e.Msg
return e.Message
}
func (e *err) GetErr() error {
@@ -84,17 +66,13 @@ func (e *err) GetErr() error {
// ToString 返回 JSON 格式的错误详情
func (e *err) ToString() string {
err := &struct {
HttpCode int `json:"http_code"`
BusinessCode int `json:"business_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
ID string `json:"id,omitempty"`
HttpCode int `json:"http_code"`
BusinessCode int `json:"business_code"`
Message string `json:"message"`
}{
HttpCode: e.HttpCode,
BusinessCode: e.BusinessCode,
Msg: e.Msg,
Data: e.Data,
ID: e.ID,
Message: e.Message,
}
raw, _ := json.Marshal(err)
+10 -28
View File
@@ -4,31 +4,23 @@ import (
"context"
"time"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/resolver"
)
var (
defaultDialTimeout = time.Second * 2
)
type Trace = trace.T
type Option func(*option)
type option struct {
credential credentials.TransportCredentials
keepalive *keepalive.ClientParameters
resolverBuilder resolver.Builder
dialTimeout time.Duration
sign Sign
trace *trace.Trace
grpc *trace.Grpc
credential credentials.TransportCredentials
keepalive *keepalive.ClientParameters
dialTimeout time.Duration
unaryInterceptor grpc.UnaryClientInterceptor
}
// WithCredential setup credential for tls
@@ -52,20 +44,9 @@ func WithDialTimeout(timeout time.Duration) Option {
}
}
// WithSign setup the signature handler
func WithSign(sign Sign) Option {
func WithUnaryInterceptor(unaryInterceptor grpc.UnaryClientInterceptor) Option {
return func(opt *option) {
opt.sign = sign
}
}
// WithTrace setup trace info
func WithTrace(t Trace) Option {
return func(opt *option) {
if t != nil {
opt.trace = t.(*trace.Trace)
opt.grpc = new(trace.Grpc)
}
opt.unaryInterceptor = unaryInterceptor
}
}
@@ -89,12 +70,13 @@ func New(target string, options ...Option) (*grpc.ClientConn, error) {
dialTimeout = opt.dialTimeout
}
clientInterceptor := NewClientInterceptor(opt.sign, opt.trace, opt.grpc)
dialOptions := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithKeepaliveParams(*kacp),
grpc.WithUnaryInterceptor(clientInterceptor.UnaryInterceptor),
}
if opt.unaryInterceptor != nil {
dialOptions = append(dialOptions, grpc.WithUnaryInterceptor(opt.unaryInterceptor))
}
if opt.credential == nil {
-131
View File
@@ -1,131 +0,0 @@
package grpclient
import (
"context"
"runtime/debug"
"time"
"github.com/xinliangnote/go-gin-api/pkg/time_parse"
"github.com/xinliangnote/go-gin-api/pkg/trace"
"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
const (
// Trace ID
TraceID = "trace-id"
)
// ClientInterceptor the client's interceptor
type ClientInterceptor struct {
sign Sign
trace *trace.Trace
grpc *trace.Grpc
}
// NewClientInterceptor create a client interceptor
func NewClientInterceptor(sign Sign, trace *trace.Trace, grpc *trace.Grpc) *ClientInterceptor {
return &ClientInterceptor{
sign: sign,
trace: trace,
grpc: grpc,
}
}
// UnaryInterceptor a interceptor for client unary operations
func (c *ClientInterceptor) UnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
ts := time.Now()
var invokerErr error
defer func() { // double recover for safety
if p := recover(); p != nil {
debug.PrintStack()
err = status.Errorf(codes.Internal, "got double panic err: %+v,Stack: %s", p, debug.Stack())
return
}
}()
defer func() {
if p := recover(); p != nil {
debug.PrintStack()
err = status.Errorf(codes.Internal, "got panic err: %+v,Stack: %s", p, debug.Stack())
return
}
var mapReq, mapReply map[string]interface{}
mapReq, err = ProtoMessage2Map(req.(proto.Message))
if err != nil {
return
}
mapReply, err = ProtoMessage2Map(reply.(proto.Message))
if err != nil {
return
}
meta, _ := metadata.FromOutgoingContext(ctx)
if c.trace != nil {
c.grpc.Timestamp = time_parse.CSTLayoutString()
c.grpc.Addr = cc.Target()
c.grpc.Method = method
c.grpc.Meta = meta
c.grpc.Request = mapReq
c.grpc.Response = mapReply
c.grpc.CostSeconds = time.Since(ts).Seconds()
if invokerErr != nil {
statusErr, ok := status.FromError(invokerErr)
if ok {
c.grpc.Code = statusErr.Code().String()
c.grpc.Message = statusErr.Message()
}
}
c.trace.AppendGRPC(c.grpc)
}
}()
if c.sign != nil {
var raw string
if req != nil {
if raw, err = ProtoMessage2JSON(req.(proto.Message)); err != nil {
return
}
}
var signature string
if signature, err = c.sign([]byte(raw)); err != nil {
return
}
meta, _ := metadata.FromOutgoingContext(ctx)
if meta == nil {
meta = make(metadata.MD)
}
meta.Set(ProxyAuthorization, signature)
ctx = metadata.NewOutgoingContext(ctx, meta)
}
if c.trace != nil {
meta, _ := metadata.FromOutgoingContext(ctx)
if meta == nil {
meta = make(metadata.MD)
}
meta.Set(TraceID, c.trace.ID())
ctx = metadata.NewOutgoingContext(ctx, meta)
}
invokerErr = invoker(ctx, method, req, reply, cc, opts...)
if invokerErr != nil {
return invokerErr
}
return nil
}
+46
View File
@@ -0,0 +1,46 @@
package httpclient
var _ ReplyErr = (*replyErr)(nil)
// ReplyErr 错误响应,当 resp.StatusCode != http.StatusOK 时用来包装返回的 httpcode 和 body 。
type ReplyErr interface {
error
StatusCode() int
Body() []byte
}
type replyErr struct {
err error
statusCode int
body []byte
}
func (r *replyErr) Error() string {
return r.err.Error()
}
func (r *replyErr) StatusCode() int {
return r.statusCode
}
func (r *replyErr) Body() []byte {
return r.body
}
func newReplyErr(statusCode int, body []byte, err error) ReplyErr {
return &replyErr{
statusCode: statusCode,
body: body,
err: err,
}
}
// ToReplyErr 尝试将 err 转换为 ReplyErr
func ToReplyErr(err error) (ReplyErr, bool) {
if err == nil {
return nil, false
}
e, ok := err.(ReplyErr)
return e, ok
}
+5 -1
View File
@@ -105,7 +105,11 @@ func doHTTP(ctx context.Context, method, url string, payload []byte, opt *option
}()
if resp.StatusCode != http.StatusOK {
return body, resp.StatusCode, errors.Errorf("do [%s %s] return code: %d message: %s", method, url, resp.StatusCode, string(body))
return nil, resp.StatusCode, newReplyErr(
resp.StatusCode,
body,
errors.Errorf("do [%s %s] return code: %d message: %s", method, url, resp.StatusCode, string(body)),
)
}
return body, http.StatusOK, nil
+2
View File
@@ -18,6 +18,8 @@ type T interface {
WithResponse(resp *Response) *Trace
AppendDialog(dialog *Dialog) *Trace
AppendSQL(sql *SQL) *Trace
AppendRedis(redis *Redis) *Trace
AppendGRPC(grpc *Grpc) *Trace
}
// Trace 记录的参数
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
printf "\nRegenerating mysql file\n\n"
time go run -v ./cmd/mysqlmd/main.go -env fat
printf "\nRegenerating code\n\n"
time go build -o gormgen ./cmd/gormgen/main.go
mv gormgen $GOPATH/bin
go generate ./...
printf "\nFormatting code\n\n"
time go run -v github.com/koketama/mfmt
printf "\nDone.\n\n"
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
printf "\nRegenerating handler file\n\n"
time go run -v ./cmd/handlergen/main.go -handler $1
printf "\nFormatting code\n\n"
time go run -v github.com/koketama/mfmt
printf "\nDone.\n\n"