first commit
This commit is contained in:
83
pkg/api/api.go
Normal file
83
pkg/api/api.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// 处理跨域请求,支持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-Headers", "*") //Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,FZM-APP-ID
|
||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
// 放行所有OPTIONS方法,因为有的模板是要请求两次的
|
||||
if method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// defaultLogFormatter is the default log format function Logger middleware uses.
|
||||
var Chat33GinLogFormatter = func(param gin.LogFormatterParams) string {
|
||||
var statusColor, methodColor, resetColor string
|
||||
if param.IsOutputColor() {
|
||||
statusColor = param.StatusCodeColor()
|
||||
methodColor = param.MethodColor()
|
||||
resetColor = param.ResetColor()
|
||||
}
|
||||
|
||||
if param.Latency > time.Minute {
|
||||
// Truncate in a golang < 1.8 safe way
|
||||
param.Latency = param.Latency - param.Latency%time.Second
|
||||
}
|
||||
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
||||
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, param.StatusCode, resetColor,
|
||||
param.Latency,
|
||||
param.ClientIP,
|
||||
methodColor, param.Method, resetColor,
|
||||
param.Path,
|
||||
//param.Keys[DeviceType], param.Keys[Version], param.Keys[AppId], param.Keys[UserId], param.Keys[Uuid],
|
||||
param.ErrorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
func CheckNumber(
|
||||
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||
) bool {
|
||||
val := field.Interface()
|
||||
|
||||
switch val.(type) {
|
||||
case int:
|
||||
return true
|
||||
case string:
|
||||
if val.(string) == "" {
|
||||
return true
|
||||
}
|
||||
_, err := strconv.ParseInt(val.(string), 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case int64:
|
||||
return true
|
||||
default:
|
||||
//utility_log.Error("func ToInt error unknow type")
|
||||
return false
|
||||
}
|
||||
}
|
||||
33
pkg/api/const.go
Normal file
33
pkg/api/const.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RespMiddleWareDisabled = "RespMiddleWareDisabled"
|
||||
|
||||
ReqError = "error"
|
||||
ReqResult = "result"
|
||||
)
|
||||
|
||||
const (
|
||||
Address = "address"
|
||||
Signature = "signature"
|
||||
DeviceName = "deviceName"
|
||||
DeviceType = "deviceType"
|
||||
Uuid = "uuid"
|
||||
Version = "version"
|
||||
)
|
||||
|
||||
const HeaderTimeOut = 120 * time.Second
|
||||
|
||||
func NewAddrWithContext(ctx context.Context) string {
|
||||
addr, ok := ctx.Value(Address).(string)
|
||||
if !ok {
|
||||
addr = ""
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
142
pkg/api/logger/logmiddleware.go
Normal file
142
pkg/api/logger/logmiddleware.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"gitlab.33.cn/chat/dtalk/pkg/logger"
|
||||
|
||||
"gitlab.33.cn/chat/dtalk/pkg/api"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog"
|
||||
xerror "gitlab.33.cn/chat/dtalk/pkg/error"
|
||||
)
|
||||
|
||||
type Middleware struct {
|
||||
log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewMiddleware(log zerolog.Logger) *Middleware {
|
||||
return &Middleware{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Middleware) Handle() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
body := dumpRequest(ctx.Request)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
ctx.Next()
|
||||
|
||||
tlog := logger.NewLogWithCtx(ctx, m.log)
|
||||
|
||||
latency := time.Since(start)
|
||||
if latency > time.Minute {
|
||||
latency = latency - latency%time.Second
|
||||
}
|
||||
reqURI := ctx.Request.RequestURI
|
||||
if reqURI == "" {
|
||||
reqURI = ctx.Request.URL.RequestURI()
|
||||
}
|
||||
|
||||
tlog.Info().
|
||||
Str("clientIP", ctx.ClientIP()).
|
||||
Str("method", ctx.Request.Method).
|
||||
Str("Path", reqURI).
|
||||
Dur("span", latency).
|
||||
Str("|body", body).
|
||||
Int("status", ctx.Writer.Status()).
|
||||
Msg("http req")
|
||||
|
||||
err, ok := ctx.Get(api.ReqError)
|
||||
if ok {
|
||||
code, msg := parseErr(err)
|
||||
if code != 0 {
|
||||
tlog.Error().
|
||||
Int("code", code).
|
||||
Str("msg", msg).
|
||||
Msg("http err")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ParseErr(err interface{}) (code int, msg string) {
|
||||
return parseErr(err)
|
||||
}
|
||||
|
||||
func parseErr(err interface{}) (code int, msg string) {
|
||||
if err != nil {
|
||||
switch ty := err.(type) {
|
||||
case *xerror.Error:
|
||||
code = ty.Code()
|
||||
msg = ty.Error()
|
||||
case error:
|
||||
code = xerror.CodeInnerError
|
||||
msg = err.(error).Error()
|
||||
default:
|
||||
e := xerror.NewError(xerror.CodeInnerError)
|
||||
code = e.Code()
|
||||
msg = e.Error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
code = xerror.CodeOK
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) {
|
||||
var buf bytes.Buffer
|
||||
tee := io.TeeReader(reader, &buf)
|
||||
return ioutil.NopCloser(tee), ioutil.NopCloser(&buf)
|
||||
}
|
||||
|
||||
// dumpRequest 格式化请求样式
|
||||
func dumpRequest(req *http.Request) string {
|
||||
var dup io.ReadCloser
|
||||
var err error
|
||||
//req.Body, dup = iox.DupReadCloser(req.Body)
|
||||
req.Body, dup = DupReadCloser(req.Body)
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
//reqURI := req.RequestURI
|
||||
//if reqURI == "" {
|
||||
// reqURI = req.URL.RequestURI()
|
||||
//}
|
||||
|
||||
//fmt.Fprintf(&b, "%s - %s - HTTP/%d.%d - OperaotrId:%s - ReqBody:", req.Method,
|
||||
// reqURI, req.ProtoMajor, req.ProtoMinor, operatorId)
|
||||
|
||||
chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
|
||||
if req.Body != nil {
|
||||
var n int64
|
||||
var dest io.Writer = &b
|
||||
if chunked {
|
||||
dest = httputil.NewChunkedWriter(dest)
|
||||
}
|
||||
n, err = io.Copy(dest, req.Body)
|
||||
if chunked {
|
||||
dest.(io.Closer).Close()
|
||||
}
|
||||
if n > 0 {
|
||||
//io.WriteString(&b, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
req.Body = dup
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
192
pkg/api/midware.go
Normal file
192
pkg/api/midware.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
zlog "github.com/rs/zerolog/log"
|
||||
"gitlab.33.cn/chat/dtalk/pkg/address"
|
||||
xerror "gitlab.33.cn/chat/dtalk/pkg/error"
|
||||
"gitlab.33.cn/chat/dtalk/pkg/util"
|
||||
)
|
||||
|
||||
var log = zlog.Logger
|
||||
|
||||
func composeHttpResp(code int, msg string, data interface{}) interface{} {
|
||||
type HttpAck struct {
|
||||
Result int `json:"result"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
var ret HttpAck
|
||||
ret.Result = code
|
||||
ret.Message = msg
|
||||
ret.Data = data
|
||||
return &ret
|
||||
}
|
||||
|
||||
func parseRlt(result interface{}, err interface{}) (code int, msg string, data interface{}) {
|
||||
if err != nil {
|
||||
switch ty := err.(type) {
|
||||
case *xerror.Error:
|
||||
code = ty.Code()
|
||||
msg = ty.Error()
|
||||
data = ty.Data()
|
||||
default:
|
||||
log.Warn().Interface("err", err).Msg("inner error type")
|
||||
e := xerror.NewError(xerror.CodeInnerError)
|
||||
code = e.Code()
|
||||
msg = e.Error()
|
||||
data = err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
code = xerror.CodeOK
|
||||
if isNil(result) {
|
||||
data = gin.H{}
|
||||
} else {
|
||||
data = result
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isNil(i interface{}) bool {
|
||||
vi := reflect.ValueOf(i)
|
||||
if vi.Kind() == reflect.Ptr {
|
||||
return vi.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RespMiddleWare() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Next()
|
||||
if v, ok := c.Get(RespMiddleWareDisabled); ok && v == true {
|
||||
return
|
||||
}
|
||||
err := c.MustGet(ReqError)
|
||||
result, _ := c.Get(ReqResult)
|
||||
ret := composeHttpResp(parseRlt(result, err))
|
||||
c.PureJSON(http.StatusOK, ret)
|
||||
//c.PureJSON()
|
||||
}
|
||||
}
|
||||
|
||||
func AuthMiddleWare() gin.HandlerFunc {
|
||||
return func(context *gin.Context) {
|
||||
sig := context.GetHeader("FZM-SIGNATURE")
|
||||
uuid := context.GetHeader("FZM-UUID")
|
||||
device := context.GetHeader("FZM-DEVICE")
|
||||
deviceName := context.GetHeader("FZM-DEVICE-NAME")
|
||||
version := context.GetHeader("FZM-VERSION")
|
||||
|
||||
//
|
||||
if sig == "MOCK" || sig == "MOCK2" {
|
||||
mockAddr := ""
|
||||
switch sig {
|
||||
case "MOCK":
|
||||
mockAddr = "1FKxgaEh5fuSm7a35BfUnKYAmradowpiTR"
|
||||
case "MOCK2":
|
||||
mockAddr = "1AsPsahP7FvpR7F2de1LhSB4SU5ShqZ7eu"
|
||||
}
|
||||
//set val
|
||||
context.Set(Signature, sig)
|
||||
context.Set(Address, mockAddr)
|
||||
context.Set(Uuid, uuid)
|
||||
context.Set(DeviceType, device)
|
||||
context.Set(DeviceName, deviceName)
|
||||
context.Set(Version, version)
|
||||
} else {
|
||||
pubKey, err := VerifyAddress(sig)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("VerifyAddress failed")
|
||||
context.Set(ReqError, err)
|
||||
context.Abort()
|
||||
return
|
||||
}
|
||||
addr := address.PublicKeyToAddress(address.NormalVer, pubKey)
|
||||
if addr == "" {
|
||||
log.Debug().Msg("PublicKeyToAddress addr is empty")
|
||||
context.Set(ReqError, err)
|
||||
context.Abort()
|
||||
return
|
||||
}
|
||||
//set val
|
||||
context.Set(Signature, sig)
|
||||
context.Set(Address, addr)
|
||||
context.Set(Uuid, uuid)
|
||||
context.Set(DeviceType, device)
|
||||
context.Set(DeviceName, deviceName)
|
||||
context.Set(Version, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HeaderMiddleWare() gin.HandlerFunc {
|
||||
return func(context *gin.Context) {
|
||||
uuid := context.GetHeader("FZM-UUID")
|
||||
device := context.GetHeader("FZM-DEVICE")
|
||||
deviceName := context.GetHeader("FZM-DEVICE-NAME")
|
||||
version := context.GetHeader("FZM-VERSION")
|
||||
|
||||
//set val
|
||||
context.Set(Uuid, uuid)
|
||||
context.Set(DeviceType, device)
|
||||
context.Set(DeviceName, deviceName)
|
||||
context.Set(Version, version)
|
||||
}
|
||||
}
|
||||
|
||||
//get pubKey
|
||||
func VerifyAddress(str string) ([]byte, error) {
|
||||
//<signature>#<msg>#<address>; <>
|
||||
ss := strings.SplitN(str, "#", -1)
|
||||
if len(ss) < 3 {
|
||||
log.Debug().Err(fmt.Errorf("need length:%v,got:%v", 3, len(ss))).Str("sig", str).Msg("split signature failed")
|
||||
return nil, xerror.NewError(xerror.SignatureInvalid)
|
||||
}
|
||||
sigData := ss[0]
|
||||
msgData := ss[1]
|
||||
pubKeyData := ss[2]
|
||||
|
||||
msg := strings.SplitN(msgData, "*", -1)
|
||||
if len(msg) < 2 {
|
||||
log.Debug().Err(fmt.Errorf("need msg length:%v,got:%v", 2, len(ss))).Str("msgData", msgData).Msg("split msg data failed")
|
||||
return nil, xerror.NewError(xerror.SignatureInvalid)
|
||||
}
|
||||
time, err := strconv.ParseInt(msg[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("datetime", msg[0]).Msg("ParseInt datetime failed")
|
||||
return nil, xerror.NewError(xerror.SignatureInvalid)
|
||||
}
|
||||
//secp256
|
||||
sig, err := base64.StdEncoding.DecodeString(sigData)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("sigData", sigData).Msg("base64 decode sig data failed")
|
||||
return nil, xerror.NewError(xerror.SignatureInvalid)
|
||||
}
|
||||
pubKey, err := util.HexDecode(pubKeyData)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("pubKeyData", pubKeyData).Msg("hex decode pubKey failed")
|
||||
return nil, xerror.NewError(xerror.SignatureInvalid)
|
||||
}
|
||||
msg256 := sha256.Sum256([]byte(msgData))
|
||||
if !util.Secp256k1Verify(msg256[:], sig, pubKey) {
|
||||
log.Debug().Err(err).Str("msgData", msgData).Bytes("sig", sig).Bytes("pubKey", pubKey).
|
||||
Msg("Secp256k1Verify failed")
|
||||
return nil, xerror.NewError(xerror.SignatureInvalid)
|
||||
}
|
||||
//检查时间是否过期
|
||||
if util.CheckTimeOut(time, HeaderTimeOut) {
|
||||
log.Debug().Err(err).Int64("time", time).Msg("verify timeout")
|
||||
return nil, xerror.NewError(xerror.SignatureExpired)
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
15
pkg/api/midware_test.go
Normal file
15
pkg/api/midware_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package api
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_VerifyAddress(t *testing.T) {
|
||||
//sig := "5a3tPpKtUwVzOXAmEFuMRNdHJqJnkjbWUPKVYJDLJRV9+AksajpvT9UUSeNFVVL1W1F8EUDQt01bp11jtV8gbwA=#1610336258730*6XofpoSc#0375610055c57e011a0a51457e0ce451849a4ca588b0ff0beb0ba5d929ca2dd82b"
|
||||
sig := "sjL53g5zbfwREjuBDfTpyaQRfULgip2Ax0Es3tVEIuBCxvWryXm7EVRv/jEmmi6ZlMZZbEXeKlBxrFt41OCPWAE=#1626170987818458*2syd7kfaiw#036801a786cba366d5f62283173681dd5801740d3ef6fe1d4383680f3c0f8e7d4f"
|
||||
|
||||
b, err := VerifyAddress(sig)
|
||||
if err != nil {
|
||||
t.Errorf("verify failed: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("verify:%v", b)
|
||||
}
|
||||
88
pkg/api/prometheus/prometheus.go
Normal file
88
pkg/api/prometheus/prometheus.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func PrometheusHandler() gin.HandlerFunc {
|
||||
h := promhttp.Handler()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(i interface{}) bool {
|
||||
vi := reflect.ValueOf(i)
|
||||
if vi.Kind() == reflect.Ptr {
|
||||
return vi.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func PrometheusMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
//path := c.FullPath()
|
||||
//timer := prometheus.NewTimer(httpDuration.WithLabelValues(path))
|
||||
//c.Next()
|
||||
//
|
||||
//// 统计错误 code
|
||||
//err := c.MustGet(api.ReqError)
|
||||
//code, _, _ := parseErr(nil, err)
|
||||
////if code != 0 {
|
||||
//// responseCode.WithLabelValues(strconv.Itoa(code)).Inc()
|
||||
////}
|
||||
//
|
||||
//status := c.Writer.Status()
|
||||
////responseStatus.WithLabelValues(strconv.Itoa(status)).Inc()
|
||||
//totalRequests.WithLabelValues(path, strconv.Itoa(status), strconv.Itoa(code)).Inc()
|
||||
//timer.ObserveDuration()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
_ = prometheus.Register(totalRequests)
|
||||
//_ = prometheus.Register(responseStatus)
|
||||
_ = prometheus.Register(httpDuration)
|
||||
//_ = prometheus.Register(responseCode)
|
||||
}
|
||||
|
||||
// 统计所有 url 访问次数
|
||||
var totalRequests = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "http_requests_total",
|
||||
Help: "Number of get requests.",
|
||||
},
|
||||
[]string{"path", "status", "code"},
|
||||
)
|
||||
|
||||
//
|
||||
//var responseStatus = prometheus.NewCounterVec(
|
||||
// prometheus.CounterOpts{
|
||||
// Name: "http_response_status",
|
||||
// Help: "Status of HTTP response",
|
||||
// },
|
||||
// []string{"status"},
|
||||
//)
|
||||
//
|
||||
////
|
||||
//var responseCode = prometheus.NewCounterVec(
|
||||
// prometheus.CounterOpts{
|
||||
// Name: "http_response_code",
|
||||
// Help: "Status of HTTP response",
|
||||
// },
|
||||
// []string{"code"},
|
||||
//)
|
||||
|
||||
//
|
||||
var httpDuration = promauto.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "http_response_time_seconds",
|
||||
Help: "Duration of HTTP requests.",
|
||||
},
|
||||
[]string{"path"},
|
||||
)
|
||||
28
pkg/api/trace/tracemiddleware.go
Normal file
28
pkg/api/trace/tracemiddleware.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
const DtalkTraceId = "X-dtalk-tracd-id"
|
||||
|
||||
func NewTraceIdWithContext(ctx context.Context) string {
|
||||
logId, ok := ctx.Value(DtalkTraceId).(string)
|
||||
if !ok {
|
||||
logId = xid.New().String()
|
||||
}
|
||||
|
||||
return logId
|
||||
}
|
||||
|
||||
func TraceMiddleware() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
// 或者从 header 中拿
|
||||
|
||||
ctx.Set(DtalkTraceId, NewTraceIdWithContext(ctx))
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user