This commit is contained in:
新亮
2021-01-01 10:51:55 +08:00
parent aa5dec6eeb
commit 458ebe10c6
51 changed files with 1018 additions and 2848 deletions

View File

@@ -1,45 +0,0 @@
package config
var (
ApiAuthConfig = map[string]map[string]string{
// 调用方
"DEMO": {
"md5": "IgkibX71IEf382PT",
"aes": "IgkibX71IEf382PT",
"rsa": "rsa/public.pem",
},
}
)
const (
AppName = "go-gin-api"
// 签名超时时间
AppSignExpiry = "120"
// RSA Private File
AppRsaPrivateFile = "rsa/private.pem"
// 日志文件
AppErrorLogName = "log/" + AppName + "-error.log"
AppGrpcLogName = "log/" + AppName + "-grpc.log"
// 系统告警邮箱信息
SystemEmailUser = "xinliangnote@163.com"
SystemEmailPass = "" //密码或授权码
SystemEmailHost = "smtp.163.com"
SystemEmailPort = 465
// 告警接收人
ErrorNotifyUser = "xinliangnote@163.com"
// 告警开关 1=开通 -1=关闭
ErrorNotifyOpen = -1
// Jaeger 配置信息
JaegerHostPort = "172.20.2.212:6831"
// Jaeger 配置开关 1=开通 -1=关闭
JaegerOpen = 1
)

View File

@@ -101,7 +101,8 @@ type response []struct {
// @Tags Demo
// @Accept json
// @Produce json
// @Param name path string true "用户名"
// @Param name path string true "用户名(Tom)"
// @Param Authorization header string true "签名"
// @Success 200 {object} response "用户信息"
// @Router /demo/user/{name} [get]
func (d *Demo) User() core.HandlerFunc {
@@ -121,6 +122,7 @@ func (d *Demo) User() core.HandlerFunc {
httpclient.WithTTL(time.Second*2),
httpclient.WithJournal(c.Journal()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
)
if err1 != nil {
d.logger.Error("get [demo/get] err", zap.Error(err1))
@@ -132,22 +134,25 @@ func (d *Demo) User() core.HandlerFunc {
httpclient.WithTTL(time.Second*2),
httpclient.WithJournal(c.Journal()),
httpclient.WithLogger(c.Logger()),
httpclient.WithHeader("Authorization", c.GetHeader("Authorization")),
)
if err2 != nil {
d.logger.Error("post [demo/post] err", zap.Error(err2))
}
data := &response{
{
Name: jsonparse.Get(string(body1), "data.name").(string),
Job: jsonparse.Get(string(body1), "data.job").(string),
},
{
Name: jsonparse.Get(string(body2), "data.name").(string),
Job: jsonparse.Get(string(body2), "data.job").(string),
},
data := &response{}
if err1 == nil && err2 == nil {
data = &response{
{
Name: jsonparse.Get(string(body1), "data.name").(string),
Job: jsonparse.Get(string(body1), "data.job").(string),
},
{
Name: jsonparse.Get(string(body2), "data.name").(string),
Job: jsonparse.Get(string(body2), "data.job").(string),
},
}
}
c.SetPayload(errno.OK.WithData(data))
}
}
@@ -158,9 +163,9 @@ func (d *Demo) RsaTest() core.HandlerFunc {
encryptStr := "param_1=xxx&param_2=xxx&ak=xxx&ts=1111111111"
count := 500
cfg := configs.Get()
rsaPublic := rsa.NewPublic(cfg.Rsa.Public)
rsaPrivate := rsa.NewPrivate(cfg.Rsa.Private)
cfg := configs.Get().Rsa
rsaPublic := rsa.NewPublic(cfg.Public)
rsaPrivate := rsa.NewPrivate(cfg.Private)
for i := 0; i < count; i++ {
// 生成签名
@@ -187,8 +192,8 @@ func (d *Demo) AesTest() core.HandlerFunc {
encryptStr := "param_1=xxx&param_2=xxx&ak=xxx&ts=1111111111"
count := 1000000
cfg := configs.Get()
aes := aes.New(cfg.Aes.Key, cfg.Aes.Iv)
cfg := configs.Get().Aes
aes := aes.New(cfg.Key, cfg.Iv)
for i := 0; i < count; i++ {
// 生成签名
sn, err := aes.Encrypt(encryptStr)

View File

@@ -0,0 +1,47 @@
package demo
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
"github.com/xinliangnote/go-gin-api/internal/pkg/token"
"go.uber.org/zap"
)
type loginRequest struct {
UserID int `json:"user_id" form:"user_id"` // 用户ID>0
UserName string `json:"user_name" form:"user_name"` // 用户名
}
type loginResponse struct {
Authorization string `json:"authorization"` // 签名
}
// 登录获取 Authorization 码
// @Summary 登录获取 Authorization 码
// @Description 登录获取 Authorization 码
// @Tags Demo
// @Accept json
// @Produce json
// @Param loginRequest body loginRequest true "请求信息"
// @Success 200 {object} loginResponse "签名信息"
// @Router /user/login [post]
func (d *Demo) Login() core.HandlerFunc {
return func(c core.Context) {
req := new(loginRequest)
res := new(loginResponse)
if err := c.ShouldBindJSON(req); err != nil {
c.SetPayload(errno.ErrParam)
return
}
tokenString, err := token.Sign(req.UserID, req.UserName)
if err != nil {
d.logger.Error("token sign err", zap.Error(err))
res.Authorization = ""
} else {
res.Authorization = tokenString
}
c.SetPayload(errno.OK.WithData(res))
}
}

View File

@@ -1,58 +0,0 @@
package jaeger_conn
import (
"context"
"github.com/xinliangnote/go-gin-api/internal/api/model/proto/listen"
"github.com/xinliangnote/go-gin-api/internal/api/model/proto/read"
"github.com/xinliangnote/go-gin-api/internal/api/model/proto/speak"
"github.com/xinliangnote/go-gin-api/internal/api/model/proto/write"
"github.com/xinliangnote/go-gin-api/internal/api/util/grpc_client"
"github.com/xinliangnote/go-gin-api/internal/api/util/request"
"github.com/xinliangnote/go-gin-api/internal/api/util/response"
"github.com/gin-gonic/gin"
)
func JaegerTest(c *gin.Context) {
// 调用 gRPC 服务
conn := grpc_client.CreateServiceListenConn(c)
grpcListenClient := listen.NewListenClient(conn)
resListen, _ := grpcListenClient.ListenData(context.Background(), &listen.Request{Name: "listen"})
// 调用 gRPC 服务
conn = grpc_client.CreateServiceSpeakConn(c)
grpcSpeakClient := speak.NewSpeakClient(conn)
resSpeak, _ := grpcSpeakClient.SpeakData(context.Background(), &speak.Request{Name: "speak"})
// 调用 gRPC 服务
conn = grpc_client.CreateServiceReadConn(c)
grpcReadClient := read.NewReadClient(conn)
resRead, _ := grpcReadClient.ReadData(context.Background(), &read.Request{Name: "read"})
// 调用 gRPC 服务
conn = grpc_client.CreateServiceWriteConn(c)
grpcWriteClient := write.NewWriteClient(conn)
resWrite, _ := grpcWriteClient.WriteData(context.Background(), &write.Request{Name: "write"})
defer conn.Close()
// 调用 HTTP 服务
resHttpGet := ""
_, err := request.HttpGet("http://localhost:9905/sing", c)
if err == nil {
resHttpGet = "[HttpGetOk]"
}
// 业务处理...
msg := resListen.Message + "-" +
resSpeak.Message + "-" +
resRead.Message + "-" +
resWrite.Message + "-" +
resHttpGet
utilGin := response.Gin{Ctx: c}
utilGin.Response(1, msg, nil)
}

View File

@@ -1,208 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: listen.proto
package listen
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_f05da38a4a3e5177, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_f05da38a4a3e5177, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "listen.Request")
proto.RegisterType((*Response)(nil), "listen.Response")
}
func init() { proto.RegisterFile("listen.proto", fileDescriptor_f05da38a4a3e5177) }
var fileDescriptor_f05da38a4a3e5177 = []byte{
// 133 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xc9, 0xc9, 0x2c, 0x2e,
0x49, 0xcd, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0x94, 0x64, 0xb9, 0xd8,
0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25,
0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82,
0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12,
0x18, 0xd7, 0xc8, 0x9a, 0x8b, 0xcd, 0x07, 0x6c, 0x9c, 0x90, 0x21, 0x17, 0x17, 0x84, 0xe5, 0x92,
0x58, 0x92, 0x28, 0xc4, 0xaf, 0x07, 0xb5, 0x13, 0x6a, 0x85, 0x94, 0x00, 0x42, 0x00, 0x62, 0xa8,
0x12, 0x43, 0x12, 0x1b, 0xd8, 0x41, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xbf, 0x9d,
0x0d, 0xa0, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// ListenClient is the client API for Listen service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ListenClient interface {
// 定义 ListenData 方法
ListenData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type listenClient struct {
cc *grpc.ClientConn
}
func NewListenClient(cc *grpc.ClientConn) ListenClient {
return &listenClient{cc}
}
func (c *listenClient) ListenData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/listen.Listen/ListenData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ListenServer is the server API for Listen service.
type ListenServer interface {
// 定义 ListenData 方法
ListenData(context.Context, *Request) (*Response, error)
}
// UnimplementedListenServer can be embedded to have forward compatible implementations.
type UnimplementedListenServer struct {
}
func (*UnimplementedListenServer) ListenData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListenData not implemented")
}
func RegisterListenServer(s *grpc.Server, srv ListenServer) {
s.RegisterService(&_Listen_serviceDesc, srv)
}
func _Listen_ListenData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ListenServer).ListenData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/listen.Listen/ListenData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ListenServer).ListenData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Listen_serviceDesc = grpc.ServiceDesc{
ServiceName: "listen.Listen",
HandlerType: (*ListenServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListenData",
Handler: _Listen_ListenData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "listen.proto",
}

View File

@@ -1,208 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: read.proto
package read
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_7b10ec61df6818dd, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_7b10ec61df6818dd, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "read.Request")
proto.RegisterType((*Response)(nil), "read.Response")
}
func init() { proto.RegisterFile("read.proto", fileDescriptor_7b10ec61df6818dd) }
var fileDescriptor_7b10ec61df6818dd = []byte{
// 132 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x4a, 0x4d, 0x4c,
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0x64, 0xb9, 0xd8, 0x83, 0x52,
0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15,
0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc, 0xbc,
0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18, 0xd7,
0xc8, 0x98, 0x8b, 0x25, 0x28, 0x35, 0x31, 0x45, 0x48, 0x1b, 0xa4, 0x3a, 0x31, 0xc5, 0x25, 0xb1,
0x24, 0x51, 0x88, 0x57, 0x0f, 0x6c, 0x17, 0xd4, 0x70, 0x29, 0x3e, 0x18, 0x17, 0x62, 0x98, 0x12,
0x43, 0x12, 0x1b, 0xd8, 0x19, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x42, 0x4a, 0x9b, 0x2d,
0x94, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// ReadClient is the client API for Read service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ReadClient interface {
// 定义方法
ReadData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type readClient struct {
cc *grpc.ClientConn
}
func NewReadClient(cc *grpc.ClientConn) ReadClient {
return &readClient{cc}
}
func (c *readClient) ReadData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/read.Read/ReadData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ReadServer is the server API for Read service.
type ReadServer interface {
// 定义方法
ReadData(context.Context, *Request) (*Response, error)
}
// UnimplementedReadServer can be embedded to have forward compatible implementations.
type UnimplementedReadServer struct {
}
func (*UnimplementedReadServer) ReadData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReadData not implemented")
}
func RegisterReadServer(s *grpc.Server, srv ReadServer) {
s.RegisterService(&_Read_serviceDesc, srv)
}
func _Read_ReadData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ReadServer).ReadData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/read.Read/ReadData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ReadServer).ReadData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Read_serviceDesc = grpc.ServiceDesc{
ServiceName: "read.Read",
HandlerType: (*ReadServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ReadData",
Handler: _Read_ReadData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "read.proto",
}

View File

@@ -1,208 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: speak.proto
package speak
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_3c7d3e2f29338937, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_3c7d3e2f29338937, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "speak.Request")
proto.RegisterType((*Response)(nil), "speak.Response")
}
func init() { proto.RegisterFile("speak.proto", fileDescriptor_3c7d3e2f29338937) }
var fileDescriptor_3c7d3e2f29338937 = []byte{
// 132 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2e, 0x48, 0x4d,
0xcc, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x64, 0xb9, 0xd8, 0x83,
0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18,
0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc,
0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18,
0xd7, 0xc8, 0x9c, 0x8b, 0x35, 0x18, 0x64, 0x9a, 0x90, 0x1e, 0x17, 0x27, 0x98, 0xe1, 0x92, 0x58,
0x92, 0x28, 0xc4, 0xa7, 0x07, 0xb1, 0x0f, 0x6a, 0xbe, 0x14, 0x3f, 0x9c, 0x0f, 0x31, 0x50, 0x89,
0x21, 0x89, 0x0d, 0xec, 0x16, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4d, 0x04, 0x6f, 0x05,
0x9a, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// SpeakClient is the client API for Speak service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SpeakClient interface {
// 定义方法
SpeakData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type speakClient struct {
cc *grpc.ClientConn
}
func NewSpeakClient(cc *grpc.ClientConn) SpeakClient {
return &speakClient{cc}
}
func (c *speakClient) SpeakData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/speak.Speak/SpeakData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SpeakServer is the server API for Speak service.
type SpeakServer interface {
// 定义方法
SpeakData(context.Context, *Request) (*Response, error)
}
// UnimplementedSpeakServer can be embedded to have forward compatible implementations.
type UnimplementedSpeakServer struct {
}
func (*UnimplementedSpeakServer) SpeakData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method SpeakData not implemented")
}
func RegisterSpeakServer(s *grpc.Server, srv SpeakServer) {
s.RegisterService(&_Speak_serviceDesc, srv)
}
func _Speak_SpeakData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SpeakServer).SpeakData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/speak.Speak/SpeakData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SpeakServer).SpeakData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Speak_serviceDesc = grpc.ServiceDesc{
ServiceName: "speak.Speak",
HandlerType: (*SpeakServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SpeakData",
Handler: _Speak_SpeakData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "speak.proto",
}

View File

@@ -1,208 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: write.proto
package write
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Request 请求结构
type Request struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_67966b2b12a73214, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetName() string {
if m != nil {
return m.Name
}
return ""
}
// Response 响应结构
type Response struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_67966b2b12a73214, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "write.Request")
proto.RegisterType((*Response)(nil), "write.Response")
}
func init() { proto.RegisterFile("write.proto", fileDescriptor_67966b2b12a73214) }
var fileDescriptor_67966b2b12a73214 = []byte{
// 132 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0x2f, 0xca, 0x2c,
0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x73, 0x94, 0x64, 0xb9, 0xd8, 0x83,
0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18,
0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x15, 0x2e, 0x8e, 0xa0, 0xd4, 0xe2, 0x82, 0xfc,
0xbc, 0xe2, 0x54, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0x74, 0x98, 0x12, 0x18,
0xd7, 0xc8, 0x9c, 0x8b, 0x35, 0x1c, 0x64, 0x9a, 0x90, 0x1e, 0x17, 0x27, 0x98, 0xe1, 0x92, 0x58,
0x92, 0x28, 0xc4, 0xa7, 0x07, 0xb1, 0x0f, 0x6a, 0xbe, 0x14, 0x3f, 0x9c, 0x0f, 0x31, 0x50, 0x89,
0x21, 0x89, 0x0d, 0xec, 0x16, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x7d, 0xc4, 0x7d,
0x9a, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// WriteClient is the client API for Write service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type WriteClient interface {
// 定义方法
WriteData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}
type writeClient struct {
cc *grpc.ClientConn
}
func NewWriteClient(cc *grpc.ClientConn) WriteClient {
return &writeClient{cc}
}
func (c *writeClient) WriteData(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/write.Write/WriteData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WriteServer is the server API for Write service.
type WriteServer interface {
// 定义方法
WriteData(context.Context, *Request) (*Response, error)
}
// UnimplementedWriteServer can be embedded to have forward compatible implementations.
type UnimplementedWriteServer struct {
}
func (*UnimplementedWriteServer) WriteData(ctx context.Context, req *Request) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method WriteData not implemented")
}
func RegisterWriteServer(s *grpc.Server, srv WriteServer) {
s.RegisterService(&_Write_serviceDesc, srv)
}
func _Write_WriteData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WriteServer).WriteData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/write.Write/WriteData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WriteServer).WriteData(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
var _Write_serviceDesc = grpc.ServiceDesc{
ServiceName: "write.Write",
HandlerType: (*WriteServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "WriteData",
Handler: _Write_WriteData_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "write.proto",
}

View File

@@ -1,53 +0,0 @@
package exception
import (
"fmt"
"runtime/debug"
"strings"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/util/response"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/mail"
"github.com/xinliangnote/go-util/time"
)
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
DebugStack := ""
for _, v := range strings.Split(string(debug.Stack()), "\n") {
DebugStack += v + "<br>"
}
subject := fmt.Sprintf("【重要错误】%s 项目出错了!", config.AppName)
body := strings.ReplaceAll(MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", err))
body = strings.ReplaceAll(body, "{RequestTime}", time.GetCurrentDate())
body = strings.ReplaceAll(body, "{RequestURL}", c.Request.Method+" "+c.Request.Host+c.Request.RequestURI)
body = strings.ReplaceAll(body, "{RequestUA}", c.Request.UserAgent())
body = strings.ReplaceAll(body, "{RequestIP}", c.ClientIP())
body = strings.ReplaceAll(body, "{DebugStack}", DebugStack)
options := &mail.Options{
MailHost: config.SystemEmailHost,
MailPort: config.SystemEmailPort,
MailUser: config.SystemEmailUser,
MailPass: config.SystemEmailPass,
MailTo: config.ErrorNotifyUser,
Subject: subject,
Body: body,
}
_ = mail.Send(options)
utilGin := response.Gin{Ctx: c}
utilGin.Response(500, "系统异常,请联系管理员!", nil)
}
}()
c.Next()
}
}

View File

@@ -1,946 +0,0 @@
package exception
var MailTemplate = `<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div class="content-wrap" style="margin: 0px auto; overflow: hidden; padding-top: 15px; background-color: rgb(255, 255, 255); width: 600px;">
<!---->
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(62, 207, 88); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: center; color: rgb(255, 255, 255); font-size: 16px;">
<div>
<p style="line-height: 20px; margin: 0px;">系统告警</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" vertical-align="middle" style="padding-top: 40px; width: 600px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 20%; max-width: 20%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 120px;">
<tbody>
<tr>
<td style="direction: ltr; width: 120px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 13px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: center; color: rgb(51, 51, 51); font-size: 16px;">
<div>
<p style="line-height: 20px; margin: 0px;">告警</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 6px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(188, 12, 39); font-size: 21px;">
<div>
<p style="line-height: 20px; margin: 0px;">{ErrorMsg}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" vertical-align="middle" style="padding-top: 15px; width: 600px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(255, 255, 255); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 20%; max-width: 20%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 120px;">
<tbody>
<tr>
<td style="direction: ltr; width: 120px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 13px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: center; color: rgb(51, 51, 51); font-size: 16px;">
<div>
<p style="line-height: 20px; margin: 0px;">详情</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
<td style="width: 40%; max-width: 40%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px; line-height: 0px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 240px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="line-height: 0px; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; padding: 25px 0px; word-break: break-word; width: 240px; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse: collapse; border-spacing: 0px;">
<tbody>
<tr>
<td style="width: 375px; border-top: 1px solid rgb(204, 204, 204);"></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(241, 245, 240); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div columnnumber="3">
<div>
<table align="center" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; border: 0px;"><a target="_blank" href="javascript:;" style="cursor: default;">
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求时间:</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestTime}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求地址:</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestURL}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求 UA</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestUA}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请求 IP</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{RequestIP}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 10px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">DebugStack</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
<div class="mj-column-per-25" style="width: 100%; max-width: 100%; font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" border="0" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td border="0">
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 0px 20px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(102, 102, 102); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">{DebugStack}</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div></a></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; padding-top: 0px; text-align: center; vertical-align: top;">
<div class="outlook-group-fix" style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" vertical-align="middle" style="padding-top: 15px; width: 600px; background-image: url(&quot;&quot;); background-size: 100px; background-position: 10% 50%; background-repeat: no-repeat;"></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="background-color: rgb(65, 207, 88); background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 1% 50%;">
<tbody>
<tr>
<td style="direction: ltr; font-size: 0px; text-align: center; vertical-align: top; width: 600px;">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width: 100%; max-width: 100%; min-height: 1px; font-size: 13px; text-align: left; direction: ltr; vertical-align: top; padding: 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="center" style="font-size: 0px; word-break: break-word;">
<table align="center" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; border-spacing: 0px; width: 100%;">
<tbody>
<tr>
<td>
<div class="full">
<div style="margin: 0px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 600px;">
<tbody>
<tr>
<td style="direction: ltr; width: 600px; font-size: 0px; padding-bottom: 0px; text-align: center; vertical-align: top; background-image: url(&quot;&quot;); background-repeat: no-repeat; background-size: 100px; background-position: 10% 50%;">
<div style="font-size: 13px; text-align: left; direction: ltr; display: inline-block; vertical-align: top; width: 100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align: top;">
<tbody>
<tr>
<td align="left" style="font-size: 0px; padding: 20px 10px; word-break: break-word;">
<div class="text" style="margin: 0px; text-align: left; color: rgb(255, 255, 255); font-size: 12px;">
<div>
<p style="line-height: 20px; margin: 0px;">请注意,该邮件地址不接收回复邮件。</p>
</div>
</div></td>
</tr>
</tbody>
</table>
</div></td>
</tr>
</tbody>
</table>
</div>
</div></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>`

View File

@@ -1,40 +0,0 @@
package jaeger
import (
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/util/jaeger_trace"
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
if config.JaegerOpen == 1 {
var parentSpan opentracing.Span
tracer, closer := jaeger_trace.NewJaegerTracer("LXL-TEST", config.JaegerHostPort)
defer closer.Close()
spCtx, err := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(c.Request.Header))
if err != nil {
parentSpan = tracer.StartSpan(c.Request.URL.Path)
defer parentSpan.Finish()
} else {
parentSpan = opentracing.StartSpan(
c.Request.URL.Path,
opentracing.ChildOf(spCtx),
opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
ext.SpanKindRPCServer,
)
defer parentSpan.Finish()
}
c.Set("Tracer", tracer)
c.Set("ParentSpanContext", parentSpan.Context())
}
c.Next()
}
}

View File

@@ -1,124 +0,0 @@
package sign_aes
import (
"errors"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/util/response"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/aes"
timeUtil "github.com/xinliangnote/go-util/time"
)
var AppSecret string
// AES 对称加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["aes"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := timeUtil.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
sn, err := createSign(req)
if err != nil {
return nil, errors.New("sn Exception")
}
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": sn,
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp-tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" {
return nil, errors.New("sn Error")
}
decryptStr, decryptErr := aes.Decrypt(sn, []byte(AppSecret), AppSecret)
if decryptErr != nil {
return nil, errors.New(decryptErr.Error())
}
if decryptStr != createEncryptStr(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) (string, error) {
return aes.Encrypt(createEncryptStr(params), []byte(AppSecret), AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}

View File

@@ -1,112 +0,0 @@
package sign_md5
import (
"errors"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/util/response"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/md5"
timeUtil "github.com/xinliangnote/go-util/time"
)
var AppSecret string
// MD5 组合加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["md5"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := timeUtil.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": createSign(req),
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp-tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" || sn != createSign(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) string {
// 自定义 MD5 组合
return md5.MD5(AppSecret + createEncryptStr(params) + AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}

View File

@@ -1,124 +0,0 @@
package sign_rsa
import (
"errors"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/util/response"
"github.com/gin-gonic/gin"
"github.com/xinliangnote/go-util/rsa"
timeUtil "github.com/xinliangnote/go-util/time"
)
var AppSecret string
// RSA 非对称加密
func SetUp() gin.HandlerFunc {
return func(c *gin.Context) {
utilGin := response.Gin{Ctx: c}
sign, err := verifySign(c)
if sign != nil {
utilGin.Response(-1, "Debug Sign", sign)
c.Abort()
return
}
if err != nil {
utilGin.Response(-1, err.Error(), sign)
c.Abort()
return
}
c.Next()
}
}
// 验证签名
func verifySign(c *gin.Context) (map[string]string, error) {
_ = c.Request.ParseForm()
req := c.Request.Form
debug := strings.Join(c.Request.Form["debug"], "")
ak := strings.Join(c.Request.Form["ak"], "")
sn := strings.Join(c.Request.Form["sn"], "")
ts := strings.Join(c.Request.Form["ts"], "")
// 验证来源
value, ok := config.ApiAuthConfig[ak]
if ok {
AppSecret = value["rsa"]
} else {
return nil, errors.New("ak Error")
}
if debug == "1" {
currentUnix := timeUtil.GetCurrentUnix()
req.Set("ts", strconv.FormatInt(currentUnix, 10))
sn, err := createSign(req)
if err != nil {
return nil, errors.New("sn Exception")
}
res := map[string]string{
"ts": strconv.FormatInt(currentUnix, 10),
"sn": sn,
}
return res, nil
}
// 验证过期时间
timestamp := time.Now().Unix()
exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64)
tsInt, _ := strconv.ParseInt(ts, 10, 64)
if tsInt > timestamp || timestamp-tsInt >= exp {
return nil, errors.New("ts Error")
}
// 验证签名
if sn == "" {
return nil, errors.New("sn Error")
}
decryptStr, decryptErr := rsa.PrivateDecrypt(sn, config.AppRsaPrivateFile)
if decryptErr != nil {
return nil, errors.New(decryptErr.Error())
}
if decryptStr != createEncryptStr(req) {
return nil, errors.New("sn Error")
}
return nil, nil
}
// 创建签名
func createSign(params url.Values) (string, error) {
return rsa.PublicEncrypt(createEncryptStr(params), AppSecret)
}
func createEncryptStr(params url.Values) string {
var key []string
var str = ""
for k := range params {
if k != "sn" && k != "debug" {
key = append(key, k)
}
}
sort.Strings(key)
for i := 0; i < len(key); i++ {
if i == 0 {
str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i]))
} else {
str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i]))
}
}
return str
}

View File

@@ -1,77 +0,0 @@
package grpc_client
import (
"context"
"fmt"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-gin-api/internal/api/util/grpc_log"
"github.com/xinliangnote/go-gin-api/internal/api/util/jaeger_trace"
"github.com/gin-gonic/gin"
grpc_middeware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/opentracing/opentracing-go"
"google.golang.org/grpc"
)
func CreateServiceListenConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9901", c)
}
func CreateServiceSpeakConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9902", c)
}
func CreateServiceReadConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9903", c)
}
func CreateServiceWriteConn(c *gin.Context) *grpc.ClientConn {
return createGrpcConn("127.0.0.1:9904", c)
}
func createGrpcConn(serviceAddress string, c *gin.Context) *grpc.ClientConn {
var conn *grpc.ClientConn
var err error
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
if config.JaegerOpen == 1 {
tracer, _ := c.Get("Tracer")
parentSpanContext, _ := c.Get("ParentSpanContext")
conn, err = grpc.DialContext(
ctx,
serviceAddress,
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithUnaryInterceptor(
grpc_middeware.ChainUnaryClient(
jaeger_trace.ClientInterceptor(tracer.(opentracing.Tracer), parentSpanContext.(opentracing.SpanContext)),
grpc_log.ClientInterceptor(),
),
),
)
} else {
conn, err = grpc.DialContext(
ctx,
serviceAddress,
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithUnaryInterceptor(
grpc_middeware.ChainUnaryClient(
grpc_log.ClientInterceptor(),
),
),
)
}
if err != nil {
fmt.Println(serviceAddress, "grpc conn err:", err)
}
return conn
}

View File

@@ -1,63 +0,0 @@
package grpc_log
import (
"context"
"fmt"
"log"
"os"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/xinliangnote/go-util/json"
"github.com/xinliangnote/go-util/time"
"google.golang.org/grpc"
)
var grpcChannel = make(chan string, 100)
func ClientInterceptor() grpc.UnaryClientInterceptor {
go handleGrpcChannel()
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 开始时间
startTime := time.GetCurrentMilliUnix()
err := invoker(ctx, method, req, reply, cc, opts...)
// 结束时间
endTime := time.GetCurrentMilliUnix()
// 日志格式
grpcLogMap := make(map[string]interface{})
grpcLogMap["request_time"] = startTime
grpcLogMap["request_data"] = req
grpcLogMap["request_method"] = method
grpcLogMap["response_data"] = reply
grpcLogMap["response_error"] = err
grpcLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
grpcLogJson, _ := json.Encode(grpcLogMap)
grpcChannel <- grpcLogJson
return err
}
}
func handleGrpcChannel() {
if f, err := os.OpenFile(config.AppGrpcLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
log.Println(err)
} else {
for accessLog := range grpcChannel {
_, _ = f.WriteString(accessLog + "\n")
}
}
return
}

View File

@@ -1,98 +0,0 @@
package jaeger_trace
import (
"context"
"fmt"
"io"
"strings"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
"github.com/uber/jaeger-client-go"
jaegerConfig "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func NewJaegerTracer(serviceName string, jaegerHostPort string) (opentracing.Tracer, io.Closer) {
cfg := &jaegerConfig.Configuration{
Sampler: &jaegerConfig.SamplerConfig{
Type: "const", //固定采样
Param: 1, //1=全采样、0=不采样
},
Reporter: &jaegerConfig.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: jaegerHostPort,
},
ServiceName: serviceName,
}
tracer, closer, err := cfg.NewTracer(jaegerConfig.Logger(jaeger.StdLogger))
if err != nil {
panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
}
opentracing.SetGlobalTracer(tracer)
return tracer, closer
}
type MDReaderWriter struct {
metadata.MD
}
// ForeachKey implements ForeachKey of opentracing.TextMapReader
func (c MDReaderWriter) ForeachKey(handler func(key, val string) error) error {
for k, vs := range c.MD {
for _, v := range vs {
if err := handler(k, v); err != nil {
return err
}
}
}
return nil
}
// Set implements Set() of opentracing.TextMapWriter
func (c MDReaderWriter) Set(key, val string) {
key = strings.ToLower(key)
c.MD[key] = append(c.MD[key], val)
}
// ClientInterceptor grpc client
func ClientInterceptor(tracer opentracing.Tracer, spanContext opentracing.SpanContext) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
span := opentracing.StartSpan(
"call gRPC",
opentracing.ChildOf(spanContext),
opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
ext.SpanKindRPCClient,
)
defer span.Finish()
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(nil)
} else {
md = md.Copy()
}
err := tracer.Inject(span.Context(), opentracing.TextMap, MDReaderWriter{md})
if err != nil {
span.LogFields(log.String("inject-error", err.Error()))
}
newCtx := metadata.NewOutgoingContext(ctx, md)
err = invoker(newCtx, method, req, reply, cc, opts...)
if err != nil {
span.LogFields(log.String("call-error", err.Error()))
}
return err
}
}

View File

@@ -1,64 +0,0 @@
package request
import (
"crypto/tls"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/xinliangnote/go-gin-api/internal/api/config"
"github.com/gin-gonic/gin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
func HttpGet(url string, c *gin.Context) (string, error) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Timeout: time.Second * 5, //默认5秒超时时间
Transport: tr,
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
if config.JaegerOpen == 1 {
tracer, _ := c.Get("Tracer")
parentSpanContext, _ := c.Get("ParentSpanContext")
span := opentracing.StartSpan(
"call Http Get",
opentracing.ChildOf(parentSpanContext.(opentracing.SpanContext)),
opentracing.Tag{Key: string(ext.Component), Value: "HTTP"},
ext.SpanKindRPCClient,
)
span.Finish()
injectErr := tracer.(opentracing.Tracer).Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
if injectErr != nil {
log.Fatalf("%s: Couldn't inject headers", err)
}
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
content, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return "", err
}
return string(content), err
}

View File

@@ -1,22 +0,0 @@
package response
import "github.com/gin-gonic/gin"
type Gin struct {
Ctx *gin.Context
}
type Response struct {
Code int `json:"code"`
Message string `json:"msg"`
Data interface{} `json:"data"`
}
func (g *Gin) Response(code int, msg string, data interface{}) {
g.Ctx.JSON(200, Response{
Code: code,
Message: msg,
Data: data,
})
return
}

View File

@@ -9,10 +9,17 @@ import (
var config = new(Config)
type Config struct {
DB struct {
Mail struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
} `mapstructure:"db"`
User string `mapstructure:"user"`
Pass string `mapstructure:"pass"`
To string `mapstructure:"to"`
} `mapstructure:"mail"`
JWT struct {
Secret string `mapstructure:"secret"`
} `mapstructure:"jwt"`
Aes struct {
Key string `mapstructure:"key"`

View File

@@ -5,6 +5,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
@@ -20,10 +21,14 @@ type HandlerFunc func(c Context)
type Journal = journal.T
const (
_JournalName = "_journal_"
_LoggerName = "_logger_"
_BodyName = "_body_"
_PayloadName = "_payload_"
_Alias = "_alias_"
_JournalName = "_journal_"
_LoggerName = "_logger_"
_BodyName = "_body_"
_PayloadName = "_payload_"
_UserID = "_user_id_"
_UserName = "_user_name_"
_AbortErrorName = "_abort_error_"
)
var contextPool = &sync.Pool{
@@ -87,14 +92,22 @@ type Context interface {
GetHeader(key string) string
SetHeader(key, value string)
UserID() int
setUserID(userID int)
UserName() string
setUserName(userName string)
AbortWithError(err errno.Error)
abortError() errno.Error
Alias() string
setAlias(path string)
RawData() []byte
Method() string
Host() string
Path() string
URI() string
}
@@ -208,6 +221,59 @@ func (c *context) SetHeader(key, value string) {
c.ctx.Header(key, value)
}
func (c *context) UserID() int {
val, ok := c.ctx.Get(_UserID)
if !ok {
return 0
}
return val.(int)
}
func (c *context) setUserID(userID int) {
c.ctx.Set(_UserID, userID)
}
func (c *context) UserName() string {
val, ok := c.ctx.Get(_UserName)
if !ok {
return ""
}
return val.(string)
}
func (c *context) setUserName(userName string) {
c.ctx.Set(_UserName, userName)
}
func (c *context) AbortWithError(err errno.Error) {
if err != nil {
c.ctx.AbortWithStatus(http.StatusInternalServerError)
c.ctx.Set(_AbortErrorName, err)
}
}
func (c *context) abortError() errno.Error {
err, _ := c.ctx.Get(_AbortErrorName)
return err.(errno.Error)
}
func (c *context) Alias() string {
path, ok := c.ctx.Get(_Alias)
if !ok {
return ""
}
return path.(string)
}
func (c *context) setAlias(path string) {
if path = strings.TrimSpace(path); path != "" {
c.ctx.Set(_Alias, path)
}
}
func (c *context) RawData() []byte {
body, ok := c.ctx.Get(_BodyName)
if !ok {

View File

@@ -19,6 +19,7 @@ import (
cors "github.com/rs/cors/wrapper/gin"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"go.uber.org/multierr"
"go.uber.org/zap"
"golang.org/x/time/rate"
)
@@ -41,6 +42,7 @@ type option struct {
disableSwagger bool
disablePrometheus bool
panicNotify OnPanicNotify
recordMetrics RecordMetrics
enableCors bool
enableRate bool
}
@@ -48,6 +50,10 @@ type option struct {
// OnPanicNotify 发生panic时通知用
type OnPanicNotify func(ctx Context, err interface{}, stackInfo string)
// RecordMetrics 记录prometheus指标用
// 如果使用AliasForRecordMetrics配置了别名uri将被替换为别名。
type RecordMetrics func(method, uri string, success bool, httpCode, businessCode int, costSeconds float64)
// WithDisablePProf 禁用 pprof
func WithDisablePProf() Option {
return func(opt *option) {
@@ -73,6 +79,14 @@ func WithDisableproPrometheus() Option {
func WithPanicNotify(notify OnPanicNotify) Option {
return func(opt *option) {
opt.panicNotify = notify
fmt.Println(color.Green("* [register panic notify]"))
}
}
// WithRecordMetrics 设置记录prometheus记录指标回调
func WithRecordMetrics(record RecordMetrics) Option {
return func(opt *option) {
opt.recordMetrics = record
}
}
@@ -80,12 +94,14 @@ func WithPanicNotify(notify OnPanicNotify) Option {
func WithEnableCors() Option {
return func(opt *option) {
opt.enableCors = true
fmt.Println(color.Green("* [register cors]"))
}
}
func WithEnableRate() Option {
return func(opt *option) {
opt.enableRate = true
fmt.Println(color.Green("* [register rate]"))
}
}
@@ -94,6 +110,27 @@ func DisableJournal(ctx Context) {
ctx.disableJournal()
}
// AliasForRecordMetrics 对请求uri起个别名用于prometheus记录指标。
// 如Get /user/:username 这样的uri因为username会有非常多的情况这样记录prometheus指标会非常的不有好。
func AliasForRecordMetrics(path string) HandlerFunc {
return func(ctx Context) {
ctx.setAlias(path)
}
}
// WrapAuthHandler 用来处理 Auth 的入口在之后的handler中只需 ctx.UserID() ctx.UserName() 即可。
func WrapAuthHandler(handler func(Context) (userID int, userName string, err errno.Error)) HandlerFunc {
return func(ctx Context) {
userID, userName, err := handler(ctx)
if err != nil {
ctx.AbortWithError(err)
return
}
ctx.setUserID(userID)
ctx.setUserName(userName)
}
}
// RouterGroup 包装gin的RouterGroup
type RouterGroup interface {
Group(string, ...HandlerFunc) RouterGroup
@@ -199,8 +236,6 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
gin.SetMode(gin.ReleaseMode)
gin.DisableBindValidation()
engine := gin.New()
mux := &mux{
engine: gin.New(),
}
@@ -232,15 +267,18 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
}
if !opt.disablePProf {
pprof.Register(engine) // register pprof to gin
pprof.Register(mux.engine) // register pprof to gin
fmt.Println(color.Green("* [register pprof]"))
}
if !opt.disableSwagger {
mux.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // register swagger
fmt.Println(color.Green("* [register swagger]"))
}
if !opt.disablePrometheus {
mux.engine.GET("/metrics", gin.WrapH(promhttp.Handler())) // register prometheus
fmt.Println(color.Green("* [register prometheus]"))
}
if opt.enableCors {
@@ -303,18 +341,48 @@ func New(logger *zap.Logger, options ...Option) (Mux, error) {
return
}
response := context.GetPayload()
if x := context.Journal(); x != nil {
context.SetHeader(journal.JournalHeader, x.ID())
response.WithID(x.ID())
var (
response errno.Error
abortErr error
)
if ctx.IsAborted() {
for i := range ctx.Errors { // gin error
multierr.AppendInto(&abortErr, ctx.Errors[i])
}
if err := context.abortError(); err != nil { // customer err
multierr.AppendInto(&abortErr, errors.New(err.GetMsg()))
response = err
}
} else {
response.WithID("")
response = context.GetPayload()
}
if response != nil {
if x := context.Journal(); x != nil {
context.SetHeader(journal.JournalHeader, x.ID())
response.WithID(x.ID())
} else {
response.WithID("")
}
ctx.JSON(http.StatusOK, response)
}
if opt.recordMetrics != nil {
uri := context.URI()
if alias := context.Alias(); alias != "" {
uri = alias
}
businessCode := 0
if response != nil {
businessCode = response.GetCode()
}
opt.recordMetrics(context.Method(), uri, !ctx.IsAborted() && ctx.Writer.Status() == http.StatusOK, ctx.Writer.Status(), businessCode, time.Since(ts).Seconds())
}
var j *journal.Journal
if x := context.Journal(); x != nil {
j = x.(*journal.Journal)

View File

@@ -13,6 +13,10 @@ type Error interface {
WithData(data interface{}) Error
// WithID 设置当前请求的唯一ID
WithID(id string) Error
// GetCode 获取 Code
GetCode() int
// GetMsg 获取 Msg
GetMsg() string
// ToString 返回 JSON 格式的错误详情
ToString() string
}
@@ -44,6 +48,14 @@ func (e *err) WithID(id string) Error {
return e
}
func (e *err) GetCode() int {
return e.Code
}
func (e *err) GetMsg() string {
return e.Msg
}
// ToString 返回 JSON 格式的错误详情
func (e *err) ToString() string {
err := &struct {

View File

@@ -0,0 +1,6 @@
package metrics
// RecordMetrics 记录指标
func RecordMetrics(method, uri string, success bool, httpCode, businessCode int, costSeconds float64) {
//fmt.Printf(">>>>>>>Metrics\nmethod:%s\nuri:%s\nsuccess:%t\nhttp code:%d\nbusiness code:%d\ncost seconds:%.9f\n<<<<<<<\n", method, uri, success, httpCode, businessCode, costSeconds)
}

View File

@@ -1,29 +0,0 @@
package notify
import (
"bytes"
"fmt"
"strings"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/color"
)
// Email
func Email(ctx core.Context, err interface{}, stackInfo string) {
buf := bytes.NewBuffer(nil)
buf.WriteString(fmt.Sprintf("URI: %s %s%s <br/>", ctx.Method(), ctx.Host(), ctx.URI()))
buf.WriteString(fmt.Sprintf("JournalID: %s <br/>", ctx.Journal().ID()))
buf.WriteString(fmt.Sprintf("ErrorInfo: %+v <br/>", err))
buf.WriteString(fmt.Sprintf("StackInfo: <br/>"))
for _, v := range strings.Split(stackInfo, "\n") {
buf.WriteString(v)
buf.WriteString(" <br/>")
}
content := buf.String()
fmt.Println(color.Red(content))
}

View File

@@ -0,0 +1,39 @@
package mail
import (
"strings"
"gopkg.in/gomail.v2"
)
type Options struct {
MailHost string
MailPort int
MailUser string // 发件人
MailPass string // 发件人密码
MailTo string // 收件人 多个用,分割
Subject string // 邮件主题
Body string // 邮件内容
}
func Send(o *Options) error {
m := gomail.NewMessage()
//设置发件人
m.SetHeader("From", o.MailUser)
//设置发送给多个用户
mailArrTo := strings.Split(o.MailTo, ",")
m.SetHeader("To", mailArrTo...)
//设置邮件主题
m.SetHeader("Subject", o.Subject)
//设置邮件正文
m.SetBody("text/html", o.Body)
d := gomail.NewDialer(o.MailHost, o.MailPort, o.MailUser, o.MailPass)
return d.DialAndSend(m)
}

View File

@@ -0,0 +1,23 @@
package mail
import (
"testing"
)
func TestSend(t *testing.T) {
options := &Options{
MailHost: "smtp.163.com",
MailPort: 465,
MailUser: "xxx@163.com",
MailPass: "", //密码或授权码
MailTo: "",
Subject: "subject",
Body: "body",
}
err := Send(options)
if err != nil {
t.Error("Mail Send error", err)
return
}
t.Log("success")
}

View File

@@ -0,0 +1,44 @@
package mail
import (
"bytes"
"fmt"
"html/template"
"time"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify/mail/templates"
)
// NewPanicHTMLEmail 发送系统异常邮件 html
func NewPanicHTMLEmail(method, host, uri, id string, msg interface{}, stack string) (subject string, body string, err error) {
mailData := &struct {
URL string
ID string
Msg string
Stack string
Year int
}{
URL: fmt.Sprintf("%s %s%s", method, host, uri),
ID: id,
Msg: fmt.Sprintf("%+v", msg),
Stack: stack,
Year: time.Now().Year(),
}
mailTplContent, err := getEmailHTMLContent(templates.PanicMail, mailData)
return fmt.Sprintf("[系统异常]-%s", uri), mailTplContent, err
}
// getEmailHTMLContent 获取邮件模板
func getEmailHTMLContent(mailTpl string, mailData interface{}) (string, error) {
tpl, err := template.New("email tpl").Parse(mailTpl)
if err != nil {
return "", err
}
buffer := new(bytes.Buffer)
err = tpl.Execute(buffer, mailData)
if err != nil {
return "", err
}
return buffer.String(), nil
}

View File

@@ -0,0 +1,119 @@
package templates
const PanicMail = `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style type="text/css" rel="stylesheet" media="all">
/* Media Queries */
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
</head>
<body style="margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td style="width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;" align="center">
<table width="100%" cellpadding="0" cellspacing="0">
<!-- Logo -->
<tr>
<td style="padding: 25px 0; text-align: center;">
系统异常
</td>
</tr>
<!-- Email Body -->
<tr>
<td style="width: 100%; margin: 0; padding: 0; border-top: 1px solid #EDEFF2; border-bottom: 1px solid #EDEFF2; background-color: #FFF;"
width="100%">
<table style="width: auto; max-width: 750px; margin: 0 auto; padding: 0;" align="center"
width="750" cellpadding="0" cellspacing="0">
<tr>
<td style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px;">
<!-- Greeting -->
<h1 style="margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;">
Hello!
</h1>
<!-- Intro -->
<p style="margin-top: 0; color: #74787E; line-height: 1.5em;">
您收到此电子邮件,请紧急安排处理。
</p>
<!-- Action Button -->
<table style="width: 100%; margin: 30px auto; padding: 0;"
width="100%" cellpadding="0" cellspacing="0">
<tr style="margin-top: 0; color: #74787E; line-height: 1.5em;">
<td style="width: 10%;">
ID:
</td>
<td style="width: 90%">
{{.ID}}
</td>
</tr>
<tr style="margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;">
<td style="width: 10%;">
URL:
</td>
<td style="width: 90%">
{{.URL}}
</td>
</tr>
<tr style="margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;">
<td style="width: 10%;">
Error:
</td>
<td style="width: 90%">
{{.Msg}}
</td>
</tr>
<tr style="margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;">
<td style="width: 10%;">
Stack:
</td>
<td style="width: 90%;">
{{.Stack}}}
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- Footer -->
<tr>
<td>
<table style="width: auto; max-width: 570px; margin: 0 auto; padding: 0; text-align: center;"
align="center" width="750" cellpadding="0" cellspacing="0">
<tr>
<td style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; color: #AEAEAE; padding: 35px; text-align: center;">
<p style="margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;">
&copy; {{.Year}}
All rights reserved.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
`

View File

@@ -0,0 +1,38 @@
package notify
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/configs"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify/mail"
"go.uber.org/zap"
)
// OnPanicNotify
func OnPanicNotify(ctx core.Context, err interface{}, stackInfo string) {
cfg := configs.Get().Mail
if cfg.Host == "" || cfg.Port == 0 || cfg.User == "" || cfg.Pass == "" || cfg.To == "" {
ctx.Logger().Error("Mail config error")
return
}
subject, body, htmlErr := mail.NewPanicHTMLEmail(ctx.Method(), ctx.Host(), ctx.URI(), ctx.Journal().ID(), err, stackInfo)
if htmlErr != nil {
ctx.Logger().Error("NewPanicHTMLEmail error", zap.Error(htmlErr))
return
}
options := &mail.Options{
MailHost: cfg.Host,
MailPort: cfg.Port,
MailUser: cfg.User,
MailPass: cfg.Pass,
MailTo: cfg.To,
Subject: subject,
Body: body,
}
sendErr := mail.Send(options)
if sendErr != nil {
ctx.Logger().Error("Mail Send error", zap.Error(sendErr))
}
return
}

View File

@@ -0,0 +1,53 @@
package token
import (
"time"
"github.com/dgrijalva/jwt-go"
)
//var secret = configs.Get().JWT.Secret
var secret = "i1ydX9RtHyuJTrw7frcu"
type claims struct {
UserID int
UserName string
jwt.StandardClaims
}
func Sign(userId int, userName string) (tokenString string, err error) {
// The token content.
// iss: Issuer签发者
// iat: Issued At签发时间用Unix时间戳表示
// exp: Expiration Time过期时间用Unix时间戳表示
// aud: Audience接收该JWT的一方
// sub: Subject该JWT的主题
// nbf: Not Before不要早于这个时间
// jti: JWT ID用于标识JWT的唯一ID
claims := claims{
userId,
userName,
jwt.StandardClaims{
NotBefore: time.Now().Unix(),
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
Issuer: "go-gin-api",
},
}
tokenString, err = jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
return
}
func Parse(tokenString string) (*claims, error) {
tokenClaims, err := jwt.ParseWithClaims(tokenString, &claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}

View File

@@ -0,0 +1,26 @@
package token
import (
"testing"
)
// 执行 Test 时,先将 token.secret 设置值
func TestSign(t *testing.T) {
tokenString, err := Sign(123456789, "xinliangnote")
if err != nil {
t.Error("sign error", err)
return
}
t.Log(tokenString)
}
func TestParse(t *testing.T) {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMzQ1Njc4OSwidXNlcm5hbWUiOiJ4aW5saWFuZyIsImV4cCI6MTYwOTQ2NzcwNCwiaWF0IjoxNjA5MzgxMzA0LCJpc3MiOiJnby1naW4tYXBpIiwibmJmIjoxNjA5MzgxMzA0fQ.hccv8F713WpKcwiSldBrFLZz_2SZzOTPedPi-8ps7M4"
user, err := Parse(tokenString)
if err != nil {
t.Error("parse error", err)
return
}
t.Log(user)
}

View File

@@ -0,0 +1,29 @@
package middleware
import (
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/errno"
"github.com/xinliangnote/go-gin-api/internal/pkg/token"
)
func AuthHandler(ctx core.Context) (userId int, userName string, err errno.Error) {
auth := ctx.GetHeader("Authorization")
if auth == "" {
err = errno.ErrSignParam
return
}
claims, errParse := token.Parse(auth)
if errParse != nil {
err = errno.ErrSignParam
return
}
userId = claims.UserID
if userId <= 0 {
err = errno.ErrSignParam
return
}
userName = claims.UserName
return
}

View File

@@ -3,7 +3,9 @@ package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/metrics"
"github.com/xinliangnote/go-gin-api/internal/pkg/notify"
"github.com/xinliangnote/go-gin-api/internal/router/middleware"
"github.com/pkg/errors"
"go.uber.org/zap"
@@ -18,24 +20,27 @@ func NewHTTPMux(logger *zap.Logger) (core.Mux, error) {
mux, err := core.New(logger,
core.WithEnableCors(),
core.WithEnableRate(),
core.WithPanicNotify(notify.Email),
core.WithPanicNotify(notify.OnPanicNotify),
core.WithRecordMetrics(metrics.RecordMetrics),
)
if err != nil {
panic(err)
}
//设置路由中间件
//engine.Use(exception.SetUp(), jaeger.SetUp())
demoHandler := demo.NewDemo(logger)
d := mux.Group("/demo")
u := mux.Group("/user")
{
d.GET("user/:name", demoHandler.User())
u.POST("/login", demoHandler.Login())
}
d := mux.Group("/demo", core.WrapAuthHandler(middleware.AuthHandler)) //使用 auth 验证
{
d.GET("user/:name", demoHandler.User(), core.DisableJournal)
// 模拟数据
d.GET("get/:name", demoHandler.Get(), core.DisableJournal)
d.GET("get/:name", demoHandler.Get())
d.POST("post", demoHandler.Post(), core.DisableJournal)
// 测试加密性能
@@ -44,8 +49,5 @@ func NewHTTPMux(logger *zap.Logger) (core.Mux, error) {
d.GET("/md5/test", demoHandler.MD5Test())
}
// 测试链路追踪
//mux.GET("/jaeger_test", jaeger_conn.JaegerTest)
return mux, nil
}