first commit

This commit is contained in:
2022-03-17 15:59:24 +08:00
commit 2b0debb847
592 changed files with 73946 additions and 0 deletions

83
pkg/api/api.go Normal file
View 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
View 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
}

View 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
View 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
View 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)
}

View 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"},
)

View 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()
}
}