代码重构-基于gin,gorm

This commit is contained in:
meilin.huang
2021-04-16 15:10:07 +08:00
parent a1141a405a
commit ec1001d88b
84 changed files with 1445 additions and 1424 deletions

View File

@@ -2,6 +2,7 @@ package biz
import (
"fmt"
"mayfly-go/base/utils"
"reflect"
)
@@ -42,6 +43,18 @@ func NotNil(data interface{}, msg string) {
}
}
func NotBlank(data interface{}, msg string) {
if utils.IsBlank(reflect.ValueOf(data)) {
panic(NewBizErr(msg))
}
}
func IsEquals(data interface{}, data1 interface{}, msg string) {
if data != data1 {
panic(NewBizErr(msg))
}
}
func Nil(data interface{}, msg string) {
if !reflect.ValueOf(data).IsNil() {
panic(NewBizErr(msg))

12
base/config/app.go Normal file
View File

@@ -0,0 +1,12 @@
package config
import "fmt"
type App struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
}
func (a *App) GetAppInfo() string {
return fmt.Sprintf("[%s:%s]", a.Name, a.Version)
}

51
base/config/config.go Normal file
View File

@@ -0,0 +1,51 @@
package config
import (
"flag"
"fmt"
"mayfly-go/base/utils"
"path/filepath"
)
func init() {
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
flag.Parse()
// 获取启动参数中,配置文件的绝对路径
path, _ := filepath.Abs(*configFilePath)
startConfigParam = &CmdConfigParam{ConfigFilePath: path}
// 读取配置文件信息
yc := &Config{}
if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
panic(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))
}
Conf = yc
}
// 启动配置参数
type CmdConfigParam struct {
ConfigFilePath string // -e 配置文件路径
}
// 启动可执行文件时的参数
var startConfigParam *CmdConfigParam
// yaml配置文件映射对象
type Config struct {
App *App `yaml:"app"`
Server *Server `yaml:"server"`
Redis *Redis `yaml:"redis"`
Mysql *Mysql `yaml:"mysql"`
}
// 配置文件映射对象
var Conf *Config
// 获取执行可执行文件时,指定的启动参数
func getStartConfig() *CmdConfigParam {
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
flag.Parse()
// 获取配置文件绝对路径
path, _ := filepath.Abs(*configFilePath)
sc := &CmdConfigParam{ConfigFilePath: path}
return sc
}

17
base/config/mysql.go Normal file
View File

@@ -0,0 +1,17 @@
package config
type Mysql struct {
Host string `mapstructure:"path" json:"host" yaml:"host"`
Config string `mapstructure:"config" json:"config" yaml:"config"`
Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`
Username string `mapstructure:"username" json:"username" yaml:"username"`
Password string `mapstructure:"password" json:"password" yaml:"password"`
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"`
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"`
LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
}
func (m *Mysql) Dsn() string {
return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config
}

8
base/config/redis.go Normal file
View File

@@ -0,0 +1,8 @@
package config
type Redis struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Password string `yaml:"password"`
Db int `yaml:"db"`
}

25
base/config/server.go Normal file
View File

@@ -0,0 +1,25 @@
package config
import "fmt"
type Server struct {
Port int `yaml:"port"`
Model string `yaml:"model"`
Cors bool `yaml:"cors"`
Static *[]*Static `yaml:"static"`
StaticFile *[]*StaticFile `yaml:"static-file"`
}
func (s *Server) GetPort() string {
return fmt.Sprintf(":%d", s.Port)
}
type Static struct {
RelativePath string `yaml:"relative-path"`
Root string `yaml:"root"`
}
type StaticFile struct {
RelativePath string `yaml:"relative-path"`
Filepath string `yaml:"filepath"`
}

View File

@@ -1,153 +0,0 @@
package base
import (
"encoding/json"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/mlog"
"mayfly-go/base/model"
"github.com/beego/beego/v2/core/validation"
"github.com/beego/beego/v2/server/web"
)
type Controller struct {
web.Controller
}
// 获取数据函数
type getDataFunc func(loginAccount *ctx.LoginAccount) interface{}
// 操作函数,无返回数据
type operationFunc func(loginAccount *ctx.LoginAccount)
// 将请求体的json赋值给指定的结构体
func (c *Controller) UnmarshalBody(data interface{}) {
err := json.Unmarshal(c.Ctx.Input.RequestBody, data)
biz.BizErrIsNil(err, "request body解析错误")
}
// 校验表单数据
func (c *Controller) validForm(form interface{}) {
valid := validation.Validation{}
b, err := valid.Valid(form)
if err != nil {
panic(err)
}
if !b {
e := valid.Errors[0]
panic(biz.NewBizErr(e.Field + " " + e.Message))
}
}
// 将请求体的json赋值给指定的结构体并校验表单数据
func (c *Controller) UnmarshalBodyAndValid(data interface{}) {
c.UnmarshalBody(data)
c.validForm(data)
}
// 返回数据
// @param reqCtx 请求上下文
// @param getData 获取数据的回调函数
func (c *Controller) ReturnData(reqCtx *ctx.ReqCtx, getData getDataFunc) {
defer func() {
if err := recover(); err != nil {
ctx.ApplyAfterHandler(reqCtx, err.(error))
c.parseErr(err)
} else {
// 应用所有请求后置处理器
ctx.ApplyAfterHandler(reqCtx, nil)
}
}()
reqCtx.Req = c.Ctx.Request
// 调用请求前所有处理器
err := ctx.ApplyBeforeHandler(reqCtx)
if err != nil {
panic(err)
}
resp := getData(reqCtx.LoginAccount)
c.Success(resp)
reqCtx.RespObj = resp
}
// 无返回数据的操作,如新增修改等无需返回数据的操作
// @param reqCtx 请求上下文
func (c *Controller) Operation(reqCtx *ctx.ReqCtx, operation operationFunc) {
defer func() {
if err := recover(); err != nil {
ctx.ApplyAfterHandler(reqCtx, err.(error))
c.parseErr(err)
} else {
ctx.ApplyAfterHandler(reqCtx, nil)
}
}()
reqCtx.Req = c.Ctx.Request
// 调用请求前所有处理器
err := ctx.ApplyBeforeHandler(reqCtx)
if err != nil {
panic(err)
}
operation(reqCtx.LoginAccount)
c.SuccessNoData()
// 不记录返回结果
reqCtx.RespObj = 0
}
// 获取分页参数
func (c *Controller) GetPageParam() *model.PageParam {
pn, err := c.GetInt("pageNum", 1)
biz.BizErrIsNil(err, "pageNum参数错误")
ps, serr := c.GetInt("pageSize", 10)
biz.BizErrIsNil(serr, "pageSize参数错误")
return &model.PageParam{PageNum: pn, PageSize: ps}
}
// 统一返回Result json对象
func (c *Controller) Result(result *model.Result) {
c.Data["json"] = result
c.ServeJSON()
}
// 返回成功结果
func (c *Controller) Success(data interface{}) {
c.Result(model.Success(data))
}
// 返回成功结果
func (c *Controller) SuccessNoData() {
c.Result(model.SuccessNoData())
}
// 返回业务错误
func (c *Controller) BizError(bizError *biz.BizError) {
c.Result(model.Error(bizError.Code(), bizError.Error()))
}
// 返回服务器错误结果
func (c *Controller) ServerError() {
c.Result(model.ServerError())
}
// 解析error并对不同error返回不同result
func (c *Controller) parseErr(err interface{}) {
switch t := err.(type) {
case *biz.BizError:
c.BizError(t)
break
case error:
c.ServerError()
mlog.Log.Error(t)
panic(err)
//break
case string:
c.ServerError()
mlog.Log.Error(t)
panic(err)
//break
default:
mlog.Log.Error(t)
}
}

96
base/ctx/log_handler.go Normal file
View File

@@ -0,0 +1,96 @@
package ctx
import (
"encoding/json"
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/mlog"
"mayfly-go/base/utils"
"reflect"
"runtime/debug"
log "github.com/sirupsen/logrus"
)
func init() {
// customFormatter := new(log.TextFormatter)
// customFormatter.TimestampFormat = "2006-01-02 15:04:05.000"
// customFormatter.FullTimestamp = true
log.SetFormatter(new(mlog.LogFormatter))
log.SetReportCaller(true)
AfterHandlers = append(AfterHandlers, new(LogInfo))
}
type LogInfo struct {
NeedLog bool // 是否需要记录日志
LogResp bool // 是否记录返回结果
Description string // 请求描述
}
func NewLogInfo(description string) *LogInfo {
return &LogInfo{NeedLog: true, Description: description, LogResp: false}
}
func (i *LogInfo) WithLogResp(logResp bool) *LogInfo {
i.LogResp = logResp
return i
}
func (l *LogInfo) AfterHandle(rc *ReqCtx) {
li := rc.LogInfo
if li == nil || !li.NeedLog {
return
}
lfs := log.Fields{}
if la := rc.LoginAccount; la != nil {
lfs["uid"] = la.Id
lfs["uname"] = la.Username
}
req := rc.Req
lfs[req.Method] = req.URL.Path
if err := rc.err; err != nil {
log.WithFields(lfs).Error(getErrMsg(rc, err))
return
}
log.WithFields(lfs).Info(getLogMsg(rc))
}
func getLogMsg(rc *ReqCtx) string {
msg := rc.LogInfo.Description
if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
}
// 返回结果不为空,则记录返回结果
if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) {
respB, _ := json.Marshal(rc.ResData)
msg = msg + fmt.Sprintf("\n<-- %s", string(respB))
}
return msg
}
func getErrMsg(rc *ReqCtx, err interface{}) string {
msg := rc.LogInfo.Description
if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
}
var errMsg string
switch t := err.(type) {
case *biz.BizError:
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
break
case error:
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack()))
break
case string:
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack()))
}
return (msg + errMsg)
}

View File

@@ -12,7 +12,7 @@ var permissionError = biz.NewBizErrCode(501, "token error")
type PermissionHandler struct{}
func (p *PermissionHandler) Handler(rc *ReqCtx) error {
func (p *PermissionHandler) BeforeHandle(rc *ReqCtx) error {
if !rc.NeedToken {
return nil
}

View File

@@ -1,30 +1,73 @@
package ctx
import (
"mayfly-go/base/ginx"
"mayfly-go/base/model"
"net/http"
"github.com/gin-gonic/gin"
)
type ReqCtx struct {
Req *http.Request
NeedToken bool // 是否需要token
LoginAccount *LoginAccount // 登录账号信息
// // 获取数据函数
// type getDataFunc func(*ReqCtx) interface{}
// 日志相关信息
NeedLog bool // 是否需要记录日志
LogResp bool // 是否记录返回结果
Description string // 请求描述
ReqParam interface{} // 请求参数
RespObj interface{} // 响应结果
// // 操作函数,无返回数据
// type operationFunc func(*ReqCtx)
// 处理函数
type HandlerFunc func(*ReqCtx)
type ReqCtx struct {
Req *http.Request // http request
GinCtx *gin.Context // gin context
NeedToken bool // 是否需要token
LoginAccount *model.LoginAccount // 登录账号信息
LogInfo *LogInfo // 日志相关信息
ReqParam interface{} // 请求参数,主要用于记录日志
ResData interface{} // 响应结果
err interface{} // 请求错误
}
// 请求前置处理器
func (rc *ReqCtx) Handle(handler HandlerFunc) {
ginCtx := rc.GinCtx
defer func() {
if err := recover(); err != nil {
rc.err = err
ginx.ErrorRes(ginCtx, err)
}
// 应用所有请求后置处理器
ApplyAfterHandler(rc)
}()
if ginCtx == nil {
panic("ginContext == nil")
}
rc.Req = ginCtx.Request
// 默认为不记录请求参数可在handler回调函数中覆盖赋值
rc.ReqParam = 0
// 默认响应结果为nil可在handler中赋值
rc.ResData = nil
// 调用请求前所有处理器
err := ApplyBeforeHandler(rc)
if err != nil {
panic(err)
}
handler(rc)
ginx.SuccessRes(ginCtx, rc.ResData)
}
// 请求前置处理器返回error则停止后续逻辑
type BeforeHandler interface {
Handler(rc *ReqCtx) error
BeforeHandle(rc *ReqCtx) error
}
// 请求后置处理器
type AfterHandler interface {
Handler(rc *ReqCtx, err error)
AfterHandle(rc *ReqCtx)
}
var (
@@ -35,8 +78,7 @@ var (
// 应用所有请求前置处理器
func ApplyBeforeHandler(rc *ReqCtx) error {
for _, e := range BeforeHandlers {
err := e.Handler(rc)
if err != nil {
if err := e.BeforeHandle(rc); err != nil {
return err
}
}
@@ -44,19 +86,29 @@ func ApplyBeforeHandler(rc *ReqCtx) error {
}
// 应用所有后置处理器
func ApplyAfterHandler(rc *ReqCtx, err error) {
func ApplyAfterHandler(rc *ReqCtx) {
for _, e := range AfterHandlers {
e.Handler(rc, err)
e.AfterHandle(rc)
}
}
// 新建请求上下文,默认需要记录日志
// @param needToken 是否需要token才可访问
// @param description 请求描述
func NewReqCtx(needToken bool, description string) *ReqCtx {
return &ReqCtx{NeedToken: needToken, Description: description, NeedLog: true}
// 新建请求上下文,默认需要校验token
func NewReqCtx() *ReqCtx {
return &ReqCtx{NeedToken: true}
}
func NewNoLogReqCtx(needToken bool) *ReqCtx {
return &ReqCtx{NeedToken: needToken, NeedLog: false}
func NewReqCtxWithGin(g *gin.Context) *ReqCtx {
return &ReqCtx{NeedToken: true, GinCtx: g}
}
// 调用该方法设置请求描述,则默认记录日志,并不记录响应结果
func (r *ReqCtx) WithLog(li *LogInfo) *ReqCtx {
r.LogInfo = li
return r
}
// 是否需要token
func (r *ReqCtx) WithNeedToken(needToken bool) *ReqCtx {
r.NeedToken = needToken
return r
}

View File

@@ -1,86 +0,0 @@
package ctx
import (
"encoding/json"
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/mlog"
"mayfly-go/base/utils"
"reflect"
log "github.com/sirupsen/logrus"
)
func init() {
// customFormatter := new(log.TextFormatter)
// customFormatter.TimestampFormat = "2006-01-02 15:04:05.000"
// customFormatter.FullTimestamp = true
log.SetFormatter(new(mlog.LogFormatter))
log.SetReportCaller(true)
AfterHandlers = append(AfterHandlers, new(LogHandler))
}
type LogHandler struct{}
func (l *LogHandler) Handler(rc *ReqCtx, err error) {
if !rc.NeedLog {
return
}
lfs := log.Fields{}
if la := rc.LoginAccount; la != nil {
lfs["uid"] = la.Id
lfs["uname"] = la.Username
}
if err != nil {
// lfs["errMsg"] = err.Error()
// switch t := err.(type) {
// case *biz.BizError:
// lfs["errCode"] = t.Code()
// break
// default:
// }
log.WithFields(lfs).Error(getErrMsg(rc, err))
return
}
// rb, _ := json.Marshal(rc.ReqParam)
// lfs["req"] = string(rb)
// // 返回结果不为空,则记录返回结果
// if rc.LogResp && !utils.IsBlank(reflect.ValueOf(rc.RespObj)) {
// respB, _ := json.Marshal(rc.RespObj)
// lfs["resp"] = string(respB)
// }
log.WithFields(lfs).Info(getLogMsg(rc))
}
func getLogMsg(rc *ReqCtx) string {
msg := rc.Description
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
// 返回结果不为空,则记录返回结果
if rc.LogResp && !utils.IsBlank(reflect.ValueOf(rc.RespObj)) {
respB, _ := json.Marshal(rc.RespObj)
msg = msg + fmt.Sprintf("\n<-- %s", string(respB))
}
return msg
}
func getErrMsg(rc *ReqCtx, err error) string {
msg := rc.Description
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
var errMsg string
switch t := err.(type) {
case *biz.BizError:
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
break
default:
errMsg = fmt.Sprintf("\n<-e errMsg: %s", t.Error())
}
return (msg + errMsg)
}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"mayfly-go/base/biz"
"mayfly-go/base/model"
"time"
"github.com/dgrijalva/jwt-go"
@@ -31,7 +32,7 @@ func CreateToken(userId uint64, username string) string {
}
// 解析token并返回登录者账号信息
func ParseToken(tokenStr string) (*LoginAccount, error) {
func ParseToken(tokenStr string) (*model.LoginAccount, error) {
if tokenStr == "" {
return nil, errors.New("token error")
}
@@ -43,5 +44,5 @@ func ParseToken(tokenStr string) (*LoginAccount, error) {
return nil, err
}
i := token.Claims.(jwt.MapClaims)
return &LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
return &model.LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
}

61
base/ginx/ginx.go Normal file
View File

@@ -0,0 +1,61 @@
package ginx
import (
"mayfly-go/base/biz"
"mayfly-go/base/mlog"
"mayfly-go/base/model"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// 绑定并校验请求结构体参数
func BindJsonAndValid(g *gin.Context, data interface{}) {
err := g.BindJSON(data)
if err != nil {
panic(biz.NewBizErr(err.Error()))
}
}
// 获取分页参数
func GetPageParam(g *gin.Context) *model.PageParam {
return &model.PageParam{PageNum: QueryInt(g, "pageNum", 1), PageSize: QueryInt(g, "pageSize", 10)}
}
// 获取查询参数中指定参数值并转为int
func QueryInt(g *gin.Context, qm string, defaultInt int) int {
qv := g.Query(qm)
if qv == "" {
return defaultInt
}
qvi, err := strconv.Atoi(qv)
biz.BizErrIsNil(err, "query param not int")
return qvi
}
// 返回统一成功结果
func SuccessRes(g *gin.Context, data interface{}) {
g.JSON(http.StatusOK, model.Success(data))
}
// 返回失败结果集
func ErrorRes(g *gin.Context, err interface{}) {
switch t := err.(type) {
case *biz.BizError:
g.JSON(http.StatusOK, model.Error(t.Code(), t.Error()))
break
case error:
g.JSON(http.StatusOK, model.ServerError())
mlog.Log.Error(t)
// panic(err)
break
case string:
g.JSON(http.StatusOK, model.ServerError())
mlog.Log.Error(t)
// panic(err)
break
default:
mlog.Log.Error(t)
}
}

17
base/global/global.go Normal file
View File

@@ -0,0 +1,17 @@
package global
import (
"mayfly-go/base/config"
"mayfly-go/base/mlog"
"gorm.io/gorm"
)
// 日志
var Log = mlog.Log
// config.yml配置文件映射对象
var Config = config.Conf
// gorm
var Db *gorm.DB

39
base/initialize/gorm.go Normal file
View File

@@ -0,0 +1,39 @@
package initialize
import (
"mayfly-go/base/global"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
func GormMysql() *gorm.DB {
m := global.Config.Mysql
if m.Dbname == "" {
return nil
}
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置
}
ormConfig := &gorm.Config{NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_",
SingularTable: true,
}, Logger: logger.Default.LogMode(logger.Silent)}
if db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig); err != nil {
global.Log.Panic("mysql连接失败")
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
}

27
base/middleware/cors.go Normal file
View File

@@ -0,0 +1,27 @@
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
// 处理跨域请求,支持options访问
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
}

View File

@@ -0,0 +1,15 @@
package middleware
import "github.com/gin-gonic/gin"
// RecoveryMiddleware 崩溃恢复中间件
func RecoveryMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
}
}()
c.Next()
}
}

View File

@@ -0,0 +1,25 @@
package middleware
import (
"mayfly-go/base/ctx"
"github.com/gin-gonic/gin"
)
var logHandler = new(ctx.LogInfo)
func ReqLog() gin.HandlerFunc {
return func(c *gin.Context) {
// 处理请求
c.Next()
reqCtxI, exist := c.Get("reqCtx")
if !exist {
return
}
reqCtx := reqCtxI.(*ctx.ReqCtx)
logHandler.AfterHandle(reqCtx)
}
}

View File

@@ -2,6 +2,7 @@ package mlog
import (
"fmt"
"path/filepath"
"strings"
"time"
@@ -26,8 +27,14 @@ func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
level := entry.Level
logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String()))
// 如果存在调用信息且为error级别以上记录文件及行号
if caller := entry.Caller; caller != nil && level <= logrus.ErrorLevel {
logMsg = logMsg + fmt.Sprintf(" [%s:%d]", caller.File, caller.Line)
if caller := entry.Caller; caller != nil {
var fp string
if level <= logrus.ErrorLevel {
fp = caller.File
} else {
fp = filepath.Base(caller.File)
}
logMsg = logMsg + fmt.Sprintf(" [%s:%d]", fp, caller.Line)
}
for k, v := range entry.Data {
logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v)

View File

@@ -0,0 +1,15 @@
package model
type AppContext struct {
}
type LoginAccount struct {
Id uint64
Username string
}
type Permission struct {
CheckToken bool // 是否检查token
Code string // 权限码
Name string // 描述
}

View File

@@ -1,31 +1,25 @@
package model
import (
"errors"
"mayfly-go/base/biz"
"mayfly-go/base/ctx"
"mayfly-go/base/utils"
"reflect"
"mayfly-go/base/global"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
"github.com/siddontang/go/log"
)
type Model struct {
Id uint64 `orm:"column(id);auto" json:"id"`
CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"createTime"`
CreatorId uint64 `orm:"column(creator_id)" json:"creatorId"`
Creator string `orm:"column(creator)" json:"creator"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"updateTime"`
ModifierId uint64 `orm:"column(modifier_id)" json:"modifierId"`
Modifier string `orm:"column(modifier)" json:"modifier"`
Id uint64 `json:"id"`
CreateTime *time.Time `json:"createTime"`
CreatorId uint64 `json:"creatorId"`
Creator string `json:"creator"`
UpdateTime *time.Time `json:"updateTime"`
ModifierId uint64 `json:"modifierId"`
Modifier string `json:"modifier"`
}
// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息
func (m *Model) SetBaseInfo(account *ctx.LoginAccount) {
func (m *Model) SetBaseInfo(account *LoginAccount) {
nowTime := time.Now()
isCreate := m.Id == 0
if isCreate {
@@ -46,257 +40,179 @@ func (m *Model) SetBaseInfo(account *ctx.LoginAccount) {
m.ModifierId = id
}
// 获取orm querySeter
func QuerySetter(table interface{}) orm.QuerySeter {
return getOrm().QueryTable(table)
}
// 根据id获取实体对象。model需为指针类型需要将查询出来的值赋值给model
//
// 若error不为nil则为不存在该记录
func GetById(model interface{}, id uint64, cols ...string) error {
return QuerySetter(model).Filter("Id", id).One(model, cols...)
return global.Db.Debug().Select(cols).Where("id = ?", id).First(model).Error
}
// 根据id更新model更新字段为model中不为空的值即int类型不为0ptr类型不为nil这类字段值
func UpdateById(model interface{}) (int64, error) {
var id uint64
params := orm.Params{}
err := utils.DoWithFields(model, func(ft reflect.StructField, fv reflect.Value) error {
if utils.IsBlank(fv) {
return nil
}
if ft.Name == "Id" {
if id = fv.Uint(); id == 0 {
return errors.New("根据id更新model时Id不能为0")
}
return nil
}
params[ft.Name] = fv.Interface()
return nil
})
if err != nil {
return 0, err
}
return QuerySetter(model).Filter("Id", id).Update(params)
func UpdateById(model interface{}) error {
return global.Db.Model(model).Updates(model).Error
}
// 根据id删除model
func DeleteById(model interface{}, id uint64) (int64, error) {
return QuerySetter(model).Filter("Id", id).Delete()
func DeleteById(model interface{}, id uint64) error {
// return QuerySetter(model).Filter("Id", id).Delete()
return global.Db.Delete(model).Error
}
// 插入model
func Insert(model interface{}) (int64, error) {
return getOrm().Insert(model)
func Insert(model interface{}) error {
return global.Db.Create(model).Error
}
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users []*User
func ListByCondition(model interface{}, list interface{}) {
qs := QuerySetter(model)
utils.DoWithFields(model, func(ft reflect.StructField, fv reflect.Value) error {
if !utils.IsBlank(fv) {
qs = qs.Filter(ft.Name, fv.Interface())
}
return nil
})
qs.All(list)
// @param list为数组类型 如 var users []*User可指定为非model结构体即只包含需要返回的字段结构体
func ListBy(model interface{}, list interface{}, cols ...string) {
global.Db.Debug().Model(model).Select(cols).Where(model).Find(list)
}
// 获取满足model中不为空的字段值条件的单个对象。model需为指针类型需要将查询出来的值赋值给model
//
// 若 error不为nil则为不存在该记录
func GetByCondition(model interface{}, cols ...string) error {
qs := QuerySetter(model)
utils.DoWithFields(model, func(ft reflect.StructField, fv reflect.Value) error {
if !utils.IsBlank(fv) {
qs = qs.Filter(ft.Name, fv.Interface())
}
return nil
})
return qs.One(model, cols...)
func GetBy(model interface{}, cols ...string) error {
return global.Db.Debug().Select(cols).Where(model).First(model).Error
}
// 获取满足conditionModel中不为空的字段值条件的单个对象。model需为指针类型需要将查询出来的值赋值给model
// @param toModel 需要查询的字段
// 若 error不为nil则为不存在该记录
func GetByConditionTo(conditionModel interface{}, toModel interface{}) error {
return global.Db.Debug().Model(conditionModel).Where(conditionModel).First(toModel).Error
}
// 获取分页结果
func GetPage(seter orm.QuerySeter, pageParam *PageParam, models interface{}, toModels interface{}) PageResult {
count, _ := seter.Count()
func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interface{}, orderBy ...string) PageResult {
var count int64
global.Db.Debug().Model(conditionModel).Where(conditionModel).Count(&count)
if count == 0 {
return PageResult{Total: 0, List: nil}
return PageResult{Total: 0, List: []string{}}
}
_, qerr := seter.Limit(pageParam.PageSize, pageParam.PageNum-1).All(models, getFieldNames(toModels)...)
biz.BizErrIsNil(qerr, "查询错误")
err := utils.Copy(toModels, models)
biz.BizErrIsNil(err, "实体转换错误")
page := pageParam.PageNum
pageSize := pageParam.PageSize
var orderByStr string
if orderBy == nil {
orderByStr = "id desc"
} else {
orderByStr = strings.Join(orderBy, ",")
}
global.Db.Debug().Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels)
return PageResult{Total: count, List: toModels}
}
// 根据sql获取分页对象
func GetPageBySql(sql string, toModel interface{}, param *PageParam, args ...interface{}) PageResult {
func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...interface{}) PageResult {
db := global.Db
selectIndex := strings.Index(sql, "SELECT ") + 7
fromIndex := strings.Index(sql, " FROM")
selectCol := sql[selectIndex:fromIndex]
countSql := strings.Replace(sql, selectCol, "COUNT(*) AS total ", 1)
// 查询count
o := getOrm()
type TotalRes struct {
Total int64
}
var totalRes TotalRes
_ = o.Raw(countSql, args).QueryRow(&totalRes)
total := totalRes.Total
if total == 0 {
return PageResult{Total: 0, List: nil}
var count int
db.Raw(countSql, args...).Scan(&count)
if count == 0 {
return PageResult{Total: 0, List: []string{}}
}
// 分页查询
limitSql := sql + " LIMIT " + strconv.Itoa(param.PageNum-1) + ", " + strconv.Itoa(param.PageSize)
var maps []orm.Params
_, err := o.Raw(limitSql, args).Values(&maps)
if err != nil {
panic(errors.New("查询错误 : " + err.Error()))
}
e := ormParams2Struct(maps, toModel)
if e != nil {
panic(e)
}
return PageResult{Total: total, List: toModel}
db.Raw(limitSql).Scan(toModel)
return PageResult{Total: int64(count), List: toModel}
}
func GetListBySql(sql string, params ...interface{}) *[]orm.Params {
var maps []orm.Params
_, err := getOrm().Raw(sql, params).Values(&maps)
if err != nil {
log.Error("根据sql查询数据列表失败%s", err.Error())
}
return &maps
func GetListBySql(sql string, params ...interface{}) []map[string]interface{} {
var maps []map[string]interface{}
global.Db.Raw(sql, params).Scan(&maps)
return maps
}
// 获取所有列表数据
// model为数组类型 如 var users []*User
func GetList(seter orm.QuerySeter, model interface{}, toModel interface{}) {
_, _ = seter.All(model, getFieldNames(toModel)...)
err := utils.Copy(toModel, model)
biz.BizErrIsNil(err, "实体转换错误")
}
// func GetList(seter orm.QuerySeter, model interface{}, toModel interface{}) {
// _, _ = seter.All(model, getFieldNames(toModel)...)
// err := utils.Copy(toModel, model)
// biz.BizErrIsNil(err, "实体转换错误")
// }
// 根据toModel结构体字段查询单条记录并将值赋值给toModel
func GetOne(seter orm.QuerySeter, model interface{}, toModel interface{}) error {
err := seter.One(model, getFieldNames(toModel)...)
if err != nil {
return err
}
cerr := utils.Copy(toModel, model)
biz.BizErrIsNil(cerr, "实体转换错误")
return nil
}
// func getOrm() orm.Ormer {
// return orm.NewOrm()
// }
// 根据实体以及指定字段值查询实体若字段数组为空则默认用id查
func GetBy(model interface{}, fs ...string) error {
err := getOrm().Read(model, fs...)
if err != nil {
if err == orm.ErrNoRows {
return errors.New("该数据不存在")
} else {
return errors.New("查询失败")
}
}
return nil
}
// // 结果模型缓存
// var resultModelCache = make(map[string][]string)
func Update(model interface{}, fs ...string) error {
_, err := getOrm().Update(model, fs...)
if err != nil {
return errors.New("数据更新失败")
}
return nil
}
// // 获取实体对象的字段名
// func getFieldNames(obj interface{}) []string {
// objType := indirectType(reflect.TypeOf(obj))
// cacheKey := objType.PkgPath() + "." + objType.Name()
// cache := resultModelCache[cacheKey]
// if cache != nil {
// return cache
// }
// cache = getFieldNamesByType("", reflect.TypeOf(obj))
// resultModelCache[cacheKey] = cache
// return cache
// }
func Delete(model interface{}, fs ...string) error {
_, err := getOrm().Delete(model, fs...)
if err != nil {
return errors.New("数据删除失败")
}
return nil
}
// func indirectType(reflectType reflect.Type) reflect.Type {
// for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
// reflectType = reflectType.Elem()
// }
// return reflectType
// }
func getOrm() orm.Ormer {
return orm.NewOrm()
}
// func getFieldNamesByType(namePrefix string, reflectType reflect.Type) []string {
// var fieldNames []string
// 结果模型缓存
var resultModelCache = make(map[string][]string)
// if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
// for i := 0; i < reflectType.NumField(); i++ {
// t := reflectType.Field(i)
// tName := t.Name
// // 判断结构体字段是否为结构体,是的话则跳过
// it := indirectType(t.Type)
// if it.Kind() == reflect.Struct {
// itName := it.Name()
// // 如果包含Time或time则表示为time类型无需递归该结构体字段
// if !strings.Contains(itName, "BaseModel") && !strings.Contains(itName, "Time") &&
// !strings.Contains(itName, "time") {
// fieldNames = append(fieldNames, getFieldNamesByType(tName+"__", it)...)
// continue
// }
// }
// 获取实体对象的字段名
func getFieldNames(obj interface{}) []string {
objType := indirectType(reflect.TypeOf(obj))
cacheKey := objType.PkgPath() + "." + objType.Name()
cache := resultModelCache[cacheKey]
if cache != nil {
return cache
}
cache = getFieldNamesByType("", reflect.TypeOf(obj))
resultModelCache[cacheKey] = cache
return cache
}
// if t.Anonymous {
// fieldNames = append(fieldNames, getFieldNamesByType("", t.Type)...)
// } else {
// fieldNames = append(fieldNames, namePrefix+tName)
// }
// }
// }
func indirectType(reflectType reflect.Type) reflect.Type {
for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
reflectType = reflectType.Elem()
}
return reflectType
}
// return fieldNames
// }
func getFieldNamesByType(namePrefix string, reflectType reflect.Type) []string {
var fieldNames []string
// func ormParams2Struct(maps []orm.Params, structs interface{}) error {
// structsV := reflect.Indirect(reflect.ValueOf(structs))
// valType := structsV.Type()
// valElemType := valType.Elem()
// sliceType := reflect.SliceOf(valElemType)
if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
for i := 0; i < reflectType.NumField(); i++ {
t := reflectType.Field(i)
tName := t.Name
// 判断结构体字段是否为结构体,是的话则跳过
it := indirectType(t.Type)
if it.Kind() == reflect.Struct {
itName := it.Name()
// 如果包含Time或time则表示为time类型无需递归该结构体字段
if !strings.Contains(itName, "BaseModel") && !strings.Contains(itName, "Time") &&
!strings.Contains(itName, "time") {
fieldNames = append(fieldNames, getFieldNamesByType(tName+"__", it)...)
continue
}
}
// length := len(maps)
if t.Anonymous {
fieldNames = append(fieldNames, getFieldNamesByType("", t.Type)...)
} else {
fieldNames = append(fieldNames, namePrefix+tName)
}
}
}
// valSlice := structsV
// if valSlice.IsNil() {
// // Make a new slice to hold our result, same size as the original data.
// valSlice = reflect.MakeSlice(sliceType, length, length)
// }
return fieldNames
}
func ormParams2Struct(maps []orm.Params, structs interface{}) error {
structsV := reflect.Indirect(reflect.ValueOf(structs))
valType := structsV.Type()
valElemType := valType.Elem()
sliceType := reflect.SliceOf(valElemType)
length := len(maps)
valSlice := structsV
if valSlice.IsNil() {
// Make a new slice to hold our result, same size as the original data.
valSlice = reflect.MakeSlice(sliceType, length, length)
}
for i := 0; i < length; i++ {
err := utils.Map2Struct(maps[i], valSlice.Index(i).Addr().Interface())
if err != nil {
return err
}
}
structsV.Set(valSlice)
return nil
}
// for i := 0; i < length; i++ {
// err := utils.Map2Struct(maps[i], valSlice.Index(i).Addr().Interface())
// if err != nil {
// return err
// }
// }
// structsV.Set(valSlice)
// return nil
// }

View File

@@ -21,3 +21,24 @@ func GetInt4Map(m map[string]interface{}, key string) int {
}
return 0
}
// map构造器
type mapBuilder struct {
m map[string]interface{}
}
func MapBuilder(key string, value interface{}) *mapBuilder {
mb := new(mapBuilder)
mb.m = make(map[string]interface{}, 4)
mb.m[key] = value
return mb
}
func (mb *mapBuilder) Put(key string, value interface{}) *mapBuilder {
mb.m[key] = value
return mb
}
func (mb *mapBuilder) ToMap() map[string]interface{} {
return mb.m
}

View File

@@ -0,0 +1,9 @@
package utils
import "runtime"
// 获取调用堆栈信息
func GetStackTrace() string {
var buf [2 << 10]byte
return string(buf[:runtime.Stack(buf[:], false)])
}

View File

@@ -1,4 +1,4 @@
package yml
package utils
import (
"errors"
@@ -16,7 +16,7 @@ func LoadYml(path string, out interface{}) error {
// yaml解析
err := yaml.Unmarshal(yamlFileBytes, out)
if err != nil {
return errors.New("无法解析 [" + path + "]")
return errors.New("无法解析 [" + path + "] -- " + err.Error())
}
return nil
}