mirror of
https://github.com/xinliangnote/go-gin-api.git
synced 2024-04-21 12:31:46 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e1ef9c658 | ||
|
|
8a6a34348f | ||
|
|
66a5e29c9c | ||
|
|
84cc0c9cbc | ||
|
|
3e918fce59 | ||
|
|
36ef904211 | ||
|
|
49ccaf9bec | ||
|
|
4381a9a6f6 | ||
|
|
5e5db8917d | ||
|
|
f7b3dbae5e | ||
|
|
5d7975056c | ||
|
|
499e171d63 | ||
|
|
8d6cd2174e | ||
|
|
3823eee2ca | ||
|
|
0b6b9dd69f | ||
|
|
7327275b7a | ||
|
|
9b5028e9de | ||
|
|
cc8012b09d | ||
|
|
334bfbb4aa | ||
|
|
a4701309cb | ||
|
|
e5ea279f2e | ||
|
|
ad8639fea8 | ||
|
|
b346216f79 | ||
|
|
e1314cf139 | ||
|
|
a1a9208a27 | ||
|
|
4ed8b81c57 | ||
|
|
106d5b65d7 | ||
|
|
d17c58ffe1 | ||
|
|
845ef70a77 | ||
|
|
f32a8a1fb5 | ||
|
|
2466dfcbfc | ||
|
|
f96d33fdef | ||
|
|
f2cd04993f | ||
|
|
f4072dc5fb | ||
|
|
458ebe10c6 | ||
|
|
aa5dec6eeb | ||
|
|
ef8989fe33 | ||
|
|
34f85ad14b | ||
|
|
155f5fca1b | ||
|
|
f8cfc2f16c | ||
|
|
a017831aba |
+5
-1
@@ -1 +1,5 @@
|
||||
.idea
|
||||
/.vscode
|
||||
.vscode
|
||||
/.idea
|
||||
.idea
|
||||
mfmt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Gin-Gonic
|
||||
Copyright (c) 2020 新亮笔记
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -1,159 +1,50 @@
|
||||

|
||||
## 关于
|
||||
|
||||
## go-gin-api
|
||||
`go-gin-api` 是基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发,同时增加了更多限制,约束项目组开发成员,规避混乱无序及自由随意的编码。
|
||||
|
||||
基于 [Gin](https://github.com/gin-gonic/gin) 进行模块化设计的 API 框架,封装了常用的功能,使用简单,致力于进行快速的业务研发。
|
||||
供参考学习,线上使用请谨慎!
|
||||
|
||||
持续更新...
|
||||
集成组件:
|
||||
|
||||
## Features
|
||||
1. 支持 [rate](https://golang.org/x/time/rate) 接口限流
|
||||
1. 支持 panic 异常时邮件通知
|
||||
1. 支持 [cors](https://github.com/rs/cors) 接口跨域
|
||||
1. 支持 [Prometheus](https://github.com/prometheus/client_golang) 指标记录
|
||||
1. 支持 [Swagger](https://github.com/swaggo/gin-swagger) 接口文档生成
|
||||
1. 支持 [GraphQL](https://github.com/99designs/gqlgen) 查询语言
|
||||
1. 支持 trace 项目内部链路追踪
|
||||
1. 支持 [pprof](https://github.com/gin-contrib/pprof) 性能剖析
|
||||
1. 支持 [jwt](https://github.com/dgrijalva/jwt-go) 接口鉴权
|
||||
1. 支持 errno 统一定义错误码
|
||||
1. 支持 [zap](https://go.uber.org/zap) 日志收集
|
||||
1. 支持 [viper](https://github.com/spf13/viper) 配置文件解析
|
||||
1. 支持 [gorm](https://gorm.io/gorm) 数据库组件
|
||||
1. 支持 [go-redis](https://github.com/go-redis/redis/v7) 组件
|
||||
1. 支持 RESTful API 返回值规范
|
||||
1. 支持 gormgen、handlergen 代码生成工具
|
||||
1. 支持 web 界面,使用的 [Light Year Admin 模板](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe)
|
||||
|
||||
- [x] 使用 go modules 初始化项目
|
||||
- [x] 安装 Gin 框架
|
||||
- [x] 性能分析工具(pprof)
|
||||
- [x] 支持优雅地重启或停止
|
||||
- [x] 规划项目目录
|
||||
- [x] 参数验证(validator.v9)
|
||||
- [x] 模型绑定和验证
|
||||
- [x] 自定义验证器
|
||||
- [x] 路由中间件
|
||||
- [x] 签名验证
|
||||
- [x] MD5 组合加密
|
||||
- [x] AES 对称加密
|
||||
- [x] RSA 非对称加密
|
||||
- [x] 日志记录
|
||||
- [x] 异常捕获
|
||||
- [x] 链路追踪(Jaeger)
|
||||
- [x] 限流
|
||||
- [x] 自定义告警
|
||||
- [x] 邮件(gomail)
|
||||
- [ ] 微信
|
||||
- [ ] 短信
|
||||
- [ ] 钉钉
|
||||
- [ ] 存储
|
||||
- [ ] MySQL
|
||||
- [ ] Redis
|
||||
- [ ] MongoDB
|
||||
- [ ] gRPC
|
||||
- [ ] ...
|
||||
|
||||
## Download
|
||||
## 文档索引
|
||||
|
||||
```
|
||||
git clone https://github.com/xinliangnote/go-gin-api.git
|
||||
```
|
||||
go-gin-api 文档由以下几个主要部分组成:
|
||||
|
||||
## Quick start
|
||||
- 准备工作
|
||||
- 快速开始
|
||||
- 目录接口
|
||||
- 核心封装
|
||||
- 组件指南
|
||||
- 工具包
|
||||
|
||||
#### Requirements
|
||||
**地址:[https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5](https://www.yuque.com/xinliangnote/go-gin-api/ngc3x5)**
|
||||
|
||||
- Go version >= 1.12
|
||||
- Global environment configure (Linux/Mac)
|
||||
## 其他
|
||||
|
||||
```
|
||||
export GO111MODULE=on
|
||||
export GOPROXY=https://goproxy.io
|
||||
```
|
||||
查看 Jaeger 链路追踪代码,请查看 [v1.0版](https://github.com/xinliangnote/go-gin-api/releases/tag/v1.0),文档点这里 [jaeger.md](https://github.com/xinliangnote/go-gin-api/blob/master/docs/jaeger.md) 。
|
||||
|
||||
#### Build & Run
|
||||
## Special Thanks
|
||||
|
||||
```
|
||||
cd go-gin-api
|
||||
|
||||
go run main.go
|
||||
|
||||
输出如下,表示 Http Server 启动成功。
|
||||
|-----------------------------------|
|
||||
| go-gin-api |
|
||||
|-----------------------------------|
|
||||
| Go Http Server Start Successful |
|
||||
| Port:9999 Pid:xxxxx |
|
||||
|-----------------------------------|
|
||||
```
|
||||
|
||||
#### HTTP Demo
|
||||
|
||||
```
|
||||
curl -X POST http://127.0.0.1:9999/product
|
||||
```
|
||||
|
||||
#### Jaeger Demo
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:9999/jaeger_test
|
||||
```
|
||||
|
||||
服务端测试代码:
|
||||
|
||||
- [https://github.com/xinliangnote/go-jaeger-demo](https://github.com/xinliangnote/go-jaeger-demo)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
#### pprof
|
||||
|
||||
```go
|
||||
http://127.0.0.1:9999/debug/pprof
|
||||
```
|
||||
|
||||
说明文档:
|
||||
|
||||
```go
|
||||
// 查看 CPU 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/profile
|
||||
...
|
||||
(pprof)
|
||||
|
||||
//输入 web,生成 svg 文件。
|
||||
//输入 png,生成 png 文件。
|
||||
//输入 top,查看排名前 20 的信息。
|
||||
//查看更多命令,请执行 pprof help。
|
||||
```
|
||||
|
||||
其他同理,比如:
|
||||
|
||||
```go
|
||||
// 查看 内存 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/heap
|
||||
|
||||
// 查看 协程 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/goroutine
|
||||
|
||||
// 查看 锁 信息
|
||||
go tool pprof 127.0.0.1:9999/debug/pprof/mutex
|
||||
```
|
||||
如果还想查看火焰图,请执行如下命令:
|
||||
|
||||
```go
|
||||
// 1.下载 pprof 工具
|
||||
go get -u github.com/google/pprof
|
||||
|
||||
// 2.启动可视化界面
|
||||
pprof -http=:9998 xxx.cpu.prof
|
||||
|
||||
// 3.查看可视化界面
|
||||
http://127.0.0.1:9998/ui/
|
||||
```
|
||||
|
||||
## Dependence
|
||||
|
||||
- WEB 框架:github.com/gin-gonic/gin
|
||||
- 链路追踪:github.com/jaegertracing/jaeger-client-go
|
||||
- 限流:golang.org/x/time/rate
|
||||
- 工具包:github.com/xinliangnote/go-util
|
||||
|
||||
## Document
|
||||
|
||||
- [1. 使用 go modules 初始化项目](https://mp.weixin.qq.com/s/1XNTEgZ0XGZZdxFOfR5f_A)
|
||||
- [2. 规划项目目录和参数验证](https://mp.weixin.qq.com/s/11AuXptWGmL5QfiJArNLnA)
|
||||
- [3. 路由中间件 - 日志记录](https://mp.weixin.qq.com/s/eTygPXnrYM2xfrRQyfn8Tg)
|
||||
- [4. 路由中间件 - 捕获异常](https://mp.weixin.qq.com/s/SconDXB_x7Gan6T0Awdh9A)
|
||||
- [5. 路由中间件 - Jaeger 链路追踪(理论篇)](https://mp.weixin.qq.com/s/28UBEsLOAHDv530ePilKQA)
|
||||
- [6. 路由中间件 - Jaeger 链路追踪(实战篇)](https://mp.weixin.qq.com/s/Ea28475_UTNaM9RNfgPqJA)
|
||||
- [7. 路由中间件 - 签名验证](https://mp.weixin.qq.com/s/0cozELotcpX3Gd6WPJiBbQ)
|
||||
[@koketama](https://github.com/koketama)
|
||||
|
||||
## Learning together
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package config
|
||||
|
||||
var (
|
||||
ApiAuthConfig = map[string] map[string]string {
|
||||
|
||||
// 调用方
|
||||
"DEMO" : {
|
||||
"md5" : "IgkibX71IEf382PT",
|
||||
"aes" : "IgkibX71IEf382PT",
|
||||
"rsa" : "rsa/public.pem",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
AppMode = "release" //debug or release
|
||||
AppPort = ":9999"
|
||||
AppName = "go-gin-api"
|
||||
|
||||
// 签名超时时间
|
||||
AppSignExpiry = "120"
|
||||
|
||||
// RSA Private File
|
||||
AppRsaPrivateFile = "rsa/private.pem"
|
||||
|
||||
// 超时时间
|
||||
AppReadTimeout = 120
|
||||
AppWriteTimeout = 120
|
||||
|
||||
// 日志文件
|
||||
AppAccessLogName = "log/" + AppName + "-access.log"
|
||||
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 = "127.0.0.1:6831"
|
||||
|
||||
// Jaeger 配置开关 1=开通 -1=关闭
|
||||
JaegerOpen = 1
|
||||
)
|
||||
@@ -1,57 +0,0 @@
|
||||
package jaeger_conn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/model/proto/listen"
|
||||
"go-gin-api/app/model/proto/read"
|
||||
"go-gin-api/app/model/proto/speak"
|
||||
"go-gin-api/app/model/proto/write"
|
||||
"go-gin-api/app/util/grpc_client"
|
||||
"go-gin-api/app/util/request"
|
||||
"go-gin-api/app/util/response"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package param_bind
|
||||
|
||||
type ProductAdd struct {
|
||||
Name string `form:"name" json:"name" validate:"required,NameValid"`
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package param_verify
|
||||
|
||||
import (
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
func NameValid(fl validator.FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
if val == "admin" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/controller/param_bind"
|
||||
"go-gin-api/app/controller/param_verify"
|
||||
"go-gin-api/app/util/bind"
|
||||
"go-gin-api/app/util/response"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
// 新增
|
||||
func Add(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
|
||||
// 参数绑定
|
||||
s, e := bind.Bind(¶m_bind.ProductAdd{}, c)
|
||||
if e != nil {
|
||||
utilGin.Response(-1, e.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
validate := validator.New()
|
||||
|
||||
// 注册自定义验证
|
||||
_ = validate.RegisterValidation("NameValid", param_verify.NameValid)
|
||||
|
||||
if err := validate.Struct(s); err != nil {
|
||||
utilGin.Response(-1, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 业务处理...
|
||||
|
||||
utilGin.Response(1, "success", nil)
|
||||
}
|
||||
|
||||
// 编辑
|
||||
func Edit(c *gin.Context) {
|
||||
fmt.Println(c.Request.RequestURI)
|
||||
}
|
||||
|
||||
// 删除
|
||||
func Delete(c *gin.Context) {
|
||||
fmt.Println(c.Request.RequestURI)
|
||||
}
|
||||
|
||||
// 详情
|
||||
|
||||
func Detail(c *gin.Context) {
|
||||
fmt.Println(c.Request.RequestURI)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/aes"
|
||||
"github.com/xinliangnote/go-util/md5"
|
||||
"github.com/xinliangnote/go-util/rsa"
|
||||
"go-gin-api/app/util/response"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Md5Test(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
appSecret := "IgkibX71IEf382PT"
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 1000000
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
md5.MD5(appSecret + encryptStr + appSecret)
|
||||
|
||||
// 验证签名
|
||||
md5.MD5(appSecret + encryptStr + appSecret)
|
||||
}
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
|
||||
}
|
||||
|
||||
func AesTest(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
appSecret := "IgkibX71IEf382PT"
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 1000000
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
sn, _ := aes.Encrypt(encryptStr, []byte(appSecret), appSecret)
|
||||
|
||||
// 验证签名
|
||||
aes.Decrypt(sn, []byte(appSecret), appSecret)
|
||||
}
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
|
||||
}
|
||||
|
||||
func RsaTest(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111"
|
||||
count := 500
|
||||
for i := 0; i < count; i++ {
|
||||
// 生成签名
|
||||
sn, _ := rsa.PublicEncrypt(encryptStr, "rsa/public.pem")
|
||||
|
||||
// 验证签名
|
||||
rsa.PrivateDecrypt(sn, "rsa/private.pem")
|
||||
}
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1, fmt.Sprintf("%v次 - %v", count, time.Since(startTime)), nil)
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: listen.proto
|
||||
|
||||
package listen
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// 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",
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: read.proto
|
||||
|
||||
package read
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// 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",
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: speak.proto
|
||||
|
||||
package speak
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// 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",
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: write.proto
|
||||
|
||||
package write
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// 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",
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package exception
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/mail"
|
||||
"github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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(""); 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>`
|
||||
@@ -1,39 +0,0 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/jaeger_trace"
|
||||
)
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
if config.JaegerOpen == 1 {
|
||||
|
||||
var parentSpan opentracing.Span
|
||||
|
||||
tracer, closer := jaeger_trace.NewJaegerTracer(config.AppName, 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()
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/util/response"
|
||||
"golang.org/x/time/rate"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetUp (maxBurstSize int) gin.HandlerFunc {
|
||||
|
||||
limiter := rate.NewLimiter(rate.Every(time.Second*1), maxBurstSize)
|
||||
return func(c *gin.Context) {
|
||||
if limiter.Allow() {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
fmt.Println("Too many requests")
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(-1, "Too many requests", nil)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
jsonUtil "github.com/xinliangnote/go-util/json"
|
||||
"github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type bodyLogWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
var accessChannel = make(chan string, 100)
|
||||
|
||||
func (w bodyLogWriter) Write(b []byte) (int, error) {
|
||||
w.body.Write(b)
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w bodyLogWriter) WriteString(s string) (int, error) {
|
||||
w.body.WriteString(s)
|
||||
return w.ResponseWriter.WriteString(s)
|
||||
}
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
go handleAccessChannel()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
bodyLogWriter := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
|
||||
c.Writer = bodyLogWriter
|
||||
|
||||
// 开始时间
|
||||
startTime := time.GetCurrentMilliUnix()
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
|
||||
responseBody := bodyLogWriter.body.String()
|
||||
|
||||
var responseCode int
|
||||
var responseMsg string
|
||||
var responseData interface{}
|
||||
|
||||
if responseBody != "" {
|
||||
res := response.Response{}
|
||||
err := json.Unmarshal([]byte(responseBody), &res)
|
||||
if err == nil {
|
||||
responseCode = res.Code
|
||||
responseMsg = res.Message
|
||||
responseData = res.Data
|
||||
}
|
||||
}
|
||||
|
||||
// 结束时间
|
||||
endTime := time.GetCurrentMilliUnix()
|
||||
|
||||
if c.Request.Method == "POST" {
|
||||
_ = c.Request.ParseForm()
|
||||
}
|
||||
|
||||
// 日志格式
|
||||
accessLogMap := make(map[string]interface{})
|
||||
|
||||
accessLogMap["request_time"] = startTime
|
||||
accessLogMap["request_method"] = c.Request.Method
|
||||
accessLogMap["request_uri"] = c.Request.RequestURI
|
||||
accessLogMap["request_proto"] = c.Request.Proto
|
||||
accessLogMap["request_ua"] = c.Request.UserAgent()
|
||||
accessLogMap["request_referer"] = c.Request.Referer()
|
||||
accessLogMap["request_post_data"] = c.Request.PostForm.Encode()
|
||||
accessLogMap["request_client_ip"] = c.ClientIP()
|
||||
|
||||
accessLogMap["response_time"] = endTime
|
||||
accessLogMap["response_code"] = responseCode
|
||||
accessLogMap["response_msg"] = responseMsg
|
||||
accessLogMap["response_data"] = responseData
|
||||
|
||||
accessLogMap["cost_time"] = fmt.Sprintf("%vms", endTime-startTime)
|
||||
|
||||
accessLogJson, _ := jsonUtil.Encode(accessLogMap)
|
||||
accessChannel <- accessLogJson
|
||||
}
|
||||
}
|
||||
|
||||
func handleAccessChannel() {
|
||||
if f, err := os.OpenFile(config.AppAccessLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
for accessLog := range accessChannel {
|
||||
_, _ = f.WriteString(accessLog + "\n")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package requestid
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/uuid"
|
||||
)
|
||||
|
||||
func SetUp() gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
requestId := c.Request.Header.Get("X-Request-Id")
|
||||
if requestId == "" {
|
||||
requestId = uuid.GenUUID()
|
||||
}
|
||||
c.Set("X-Request-Id", requestId)
|
||||
c.Writer.Header().Set("X-Request-Id", requestId)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package sign_aes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/aes"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package sign_md5
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/md5"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package sign_rsa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xinliangnote/go-util/rsa"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/response"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-gin-api/app/controller/jaeger_conn"
|
||||
"go-gin-api/app/controller/product"
|
||||
"go-gin-api/app/controller/test"
|
||||
"go-gin-api/app/route/middleware/exception"
|
||||
"go-gin-api/app/route/middleware/jaeger"
|
||||
"go-gin-api/app/route/middleware/logger"
|
||||
"go-gin-api/app/util/response"
|
||||
)
|
||||
|
||||
func SetupRouter(engine *gin.Engine) {
|
||||
|
||||
//设置路由中间件
|
||||
engine.Use(logger.SetUp(), exception.SetUp(), jaeger.SetUp())
|
||||
|
||||
//404
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(404,"请求方法不存在", nil)
|
||||
})
|
||||
|
||||
engine.GET("/ping", func(c *gin.Context) {
|
||||
utilGin := response.Gin{Ctx: c}
|
||||
utilGin.Response(1,"pong", nil)
|
||||
})
|
||||
|
||||
// 测试链路追踪
|
||||
engine.GET("/jaeger_test", jaeger_conn.JaegerTest)
|
||||
|
||||
//@todo 记录请求超时的路由
|
||||
|
||||
ProductRouter := engine.Group("/product")
|
||||
{
|
||||
// 新增产品
|
||||
ProductRouter.POST("", product.Add)
|
||||
|
||||
// 更新产品
|
||||
ProductRouter.PUT("/:id", product.Edit)
|
||||
|
||||
// 删除产品
|
||||
ProductRouter.DELETE("/:id", product.Delete)
|
||||
|
||||
// 获取产品详情
|
||||
ProductRouter.GET("/:id", product.Detail)
|
||||
}
|
||||
|
||||
// 测试加密性能
|
||||
TestRouter := engine.Group("/test")
|
||||
{
|
||||
// 测试 MD5 组合 的性能
|
||||
TestRouter.GET("/md5", test.Md5Test)
|
||||
|
||||
// 测试 AES 的性能
|
||||
TestRouter.GET("/aes", test.AesTest)
|
||||
|
||||
// 测试 RSA 的性能
|
||||
TestRouter.GET("/rsa", test.RsaTest)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
func Bind(s interface{}, c *gin.Context) (interface{}, error) {
|
||||
b := binding.Default(c.Request.Method, c.ContentType())
|
||||
if err := c.ShouldBindWith(s, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xinliangnote/go-util/json"
|
||||
"github.com/xinliangnote/go-util/mail"
|
||||
timeUtil "github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/route/middleware/exception"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func ErrorNew (text string) error {
|
||||
alarm("INFO", text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 发邮件
|
||||
func ErrorMail (text string) error {
|
||||
alarm("MAIL",text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 发短信
|
||||
func ErrorSms (text string) error {
|
||||
alarm("SMS", text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 发微信
|
||||
func ErrorWeChat (text string) error {
|
||||
alarm("WX", text)
|
||||
return &errorString{text}
|
||||
}
|
||||
|
||||
// 告警方法
|
||||
func alarm(level string, str string) {
|
||||
if level == "MAIL" {
|
||||
DebugStack := ""
|
||||
for _, v := range strings.Split(string(debug.Stack()), "\n") {
|
||||
DebugStack += v + "<br>"
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("【系统告警】%s 项目出错了!", config.AppName)
|
||||
|
||||
body := strings.ReplaceAll(exception.MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", str))
|
||||
body = strings.ReplaceAll(body, "{RequestTime}", timeUtil.GetCurrentDate())
|
||||
body = strings.ReplaceAll(body, "{RequestURL}", "--")
|
||||
body = strings.ReplaceAll(body, "{RequestUA}", "--")
|
||||
body = strings.ReplaceAll(body, "{RequestIP}", "--")
|
||||
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)
|
||||
|
||||
} else if level == "SMS" {
|
||||
// 执行发短信
|
||||
|
||||
} else if level == "WX" {
|
||||
// 执行发微信
|
||||
|
||||
} else if level == "INFO" {
|
||||
// 执行记日志
|
||||
|
||||
if f, err := os.OpenFile(config.AppErrorLogName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666); err != nil {
|
||||
log.Println(err)
|
||||
} else {
|
||||
errorLogMap := make(map[string]interface{})
|
||||
errorLogMap["time"] = time.Now().Format("2006/01/02 - 15:04:05")
|
||||
errorLogMap["info"] = str
|
||||
|
||||
errorLogJson, _ := json.Encode(errorLogMap)
|
||||
_, _ = f.WriteString(errorLogJson + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package grpc_client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
grpc_middeware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"go-gin-api/app/config"
|
||||
"go-gin-api/app/util/grpc_log"
|
||||
"go-gin-api/app/util/jaeger_trace"
|
||||
"google.golang.org/grpc"
|
||||
"time"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package grpc_log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/xinliangnote/go-util/json"
|
||||
"github.com/xinliangnote/go-util/time"
|
||||
"go-gin-api/app/config"
|
||||
"google.golang.org/grpc"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package jaeger_trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"go-gin-api/app/config"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+4303
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
!function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core")):"function"==typeof define&&define.amd?define(["./core"],e):e(r.CryptoJS)}(this,function(r){var s;return s=r.lib.WordArray,r.enc.Base64={stringify:function(r){var e=r.words,t=r.sigBytes,a=this._map;r.clamp();for(var n=[],o=0;o<t;o+=3)for(var i=(e[o>>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,f=0;f<4&&o+.75*f<t;f++)n.push(a.charAt(i>>>6*(3-f)&63));var c=a.charAt(64);if(c)for(;n.length%4;)n.push(c);return n.join("")},parse:function(r){var e=r.length,t=this._map,a=this._reverseMap;if(!a){a=this._reverseMap=[];for(var n=0;n<t.length;n++)a[t.charCodeAt(n)]=n}var o=t.charAt(64);if(o){var i=r.indexOf(o);-1!==i&&(e=i)}return function(r,e,t){for(var a=[],n=0,o=0;o<e;o++)if(o%4){var i=t[r.charCodeAt(o-1)]<<o%4*2,f=t[r.charCodeAt(o)]>>>6-o%4*2,c=i|f;a[n>>>2]|=c<<24-n%4*8,n++}return s.create(a,n)}(r,e,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},r.enc.Base64});
|
||||
@@ -0,0 +1,18 @@
|
||||
;(function (root, factory, undef) {
|
||||
if (typeof exports === "object") {
|
||||
// CommonJS
|
||||
module.exports = exports = factory(require("./core"), require("./sha256"), require("./hmac"));
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(["./core", "./sha256", "./hmac"], factory);
|
||||
}
|
||||
else {
|
||||
// Global (browser)
|
||||
factory(root.CryptoJS);
|
||||
}
|
||||
}(this, function (CryptoJS) {
|
||||
|
||||
return CryptoJS.HmacSHA256;
|
||||
|
||||
}));
|
||||
@@ -0,0 +1,78 @@
|
||||
// original:https://locutus.io/php/array/ksort/
|
||||
|
||||
function ksort(inputArr, sort_flags) {
|
||||
var tmp_arr = {},
|
||||
keys = [],
|
||||
sorter, i, k, that = this,
|
||||
strictForIn = false,
|
||||
populateArr = {};
|
||||
|
||||
switch (sort_flags) {
|
||||
case 'SORT_STRING':
|
||||
// compare items as strings
|
||||
sorter = function (a, b) {
|
||||
return that.strnatcmp(a, b);
|
||||
};
|
||||
break;
|
||||
case 'SORT_LOCALE_STRING':
|
||||
// compare items as strings, original by the current locale (set with i18n_loc_set_default() as of PHP6)
|
||||
var loc = this.i18n_loc_get_default();
|
||||
sorter = this.php_js.i18nLocales[loc].sorting;
|
||||
break;
|
||||
case 'SORT_NUMERIC':
|
||||
// compare items numerically
|
||||
sorter = function (a, b) {
|
||||
return ((a + 0) - (b + 0));
|
||||
};
|
||||
break;
|
||||
// case 'SORT_REGULAR': // compare items normally (don't change types)
|
||||
default:
|
||||
sorter = function (a, b) {
|
||||
var aFloat = parseFloat(a),
|
||||
bFloat = parseFloat(b),
|
||||
aNumeric = aFloat + '' === a,
|
||||
bNumeric = bFloat + '' === b;
|
||||
if (aNumeric && bNumeric) {
|
||||
return aFloat > bFloat ? 1 : aFloat < bFloat ? -1 : 0;
|
||||
} else if (aNumeric && !bNumeric) {
|
||||
return 1;
|
||||
} else if (!aNumeric && bNumeric) {
|
||||
return -1;
|
||||
}
|
||||
return a > b ? 1 : a < b ? -1 : 0;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// Make a list of key names
|
||||
for (k in inputArr) {
|
||||
if (inputArr.hasOwnProperty(k)) {
|
||||
keys.push(k);
|
||||
}
|
||||
}
|
||||
keys.sort(sorter);
|
||||
|
||||
// BEGIN REDUNDANT
|
||||
this.php_js = this.php_js || {};
|
||||
this.php_js.ini = this.php_js.ini || {};
|
||||
// END REDUNDANT
|
||||
strictForIn = this.php_js.ini['phpjs.strictForIn'] && this.php_js.ini['phpjs.strictForIn'].local_value && this.php_js
|
||||
.ini['phpjs.strictForIn'].local_value !== 'off';
|
||||
populateArr = strictForIn ? inputArr : populateArr;
|
||||
|
||||
// Rebuild array with sorted key names
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
k = keys[i];
|
||||
tmp_arr[k] = inputArr[k];
|
||||
if (strictForIn) {
|
||||
delete inputArr[k];
|
||||
}
|
||||
}
|
||||
for (i in tmp_arr) {
|
||||
if (tmp_arr.hasOwnProperty(i)) {
|
||||
populateArr[i] = tmp_arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
return strictForIn || populateArr;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
!function(n){"use strict";function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d((c=d(d(t,n),d(e,u)))<<(f=o)|c>>>32-f,r);var c,f}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;n[t>>5]|=128<<t%32,n[14+(t+64>>>9<<4)]=t;for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h<n.length;h+=16)c=l(r=c,e=f,o=i,u=a,n[h],7,-680876936),a=l(a,c,f,i,n[h+1],12,-389564586),i=l(i,a,c,f,n[h+2],17,606105819),f=l(f,i,a,c,n[h+3],22,-1044525330),c=l(c,f,i,a,n[h+4],7,-176418897),a=l(a,c,f,i,n[h+5],12,1200080426),i=l(i,a,c,f,n[h+6],17,-1473231341),f=l(f,i,a,c,n[h+7],22,-45705983),c=l(c,f,i,a,n[h+8],7,1770035416),a=l(a,c,f,i,n[h+9],12,-1958414417),i=l(i,a,c,f,n[h+10],17,-42063),f=l(f,i,a,c,n[h+11],22,-1990404162),c=l(c,f,i,a,n[h+12],7,1804603682),a=l(a,c,f,i,n[h+13],12,-40341101),i=l(i,a,c,f,n[h+14],17,-1502002290),c=v(c,f=l(f,i,a,c,n[h+15],22,1236535329),i,a,n[h+1],5,-165796510),a=v(a,c,f,i,n[h+6],9,-1069501632),i=v(i,a,c,f,n[h+11],14,643717713),f=v(f,i,a,c,n[h],20,-373897302),c=v(c,f,i,a,n[h+5],5,-701558691),a=v(a,c,f,i,n[h+10],9,38016083),i=v(i,a,c,f,n[h+15],14,-660478335),f=v(f,i,a,c,n[h+4],20,-405537848),c=v(c,f,i,a,n[h+9],5,568446438),a=v(a,c,f,i,n[h+14],9,-1019803690),i=v(i,a,c,f,n[h+3],14,-187363961),f=v(f,i,a,c,n[h+8],20,1163531501),c=v(c,f,i,a,n[h+13],5,-1444681467),a=v(a,c,f,i,n[h+2],9,-51403784),i=v(i,a,c,f,n[h+7],14,1735328473),c=g(c,f=v(f,i,a,c,n[h+12],20,-1926607734),i,a,n[h+5],4,-378558),a=g(a,c,f,i,n[h+8],11,-2022574463),i=g(i,a,c,f,n[h+11],16,1839030562),f=g(f,i,a,c,n[h+14],23,-35309556),c=g(c,f,i,a,n[h+1],4,-1530992060),a=g(a,c,f,i,n[h+4],11,1272893353),i=g(i,a,c,f,n[h+7],16,-155497632),f=g(f,i,a,c,n[h+10],23,-1094730640),c=g(c,f,i,a,n[h+13],4,681279174),a=g(a,c,f,i,n[h],11,-358537222),i=g(i,a,c,f,n[h+3],16,-722521979),f=g(f,i,a,c,n[h+6],23,76029189),c=g(c,f,i,a,n[h+9],4,-640364487),a=g(a,c,f,i,n[h+12],11,-421815835),i=g(i,a,c,f,n[h+15],16,530742520),c=m(c,f=g(f,i,a,c,n[h+2],23,-995338651),i,a,n[h],6,-198630844),a=m(a,c,f,i,n[h+7],10,1126891415),i=m(i,a,c,f,n[h+14],15,-1416354905),f=m(f,i,a,c,n[h+5],21,-57434055),c=m(c,f,i,a,n[h+12],6,1700485571),a=m(a,c,f,i,n[h+3],10,-1894986606),i=m(i,a,c,f,n[h+10],15,-1051523),f=m(f,i,a,c,n[h+1],21,-2054922799),c=m(c,f,i,a,n[h+8],6,1873313359),a=m(a,c,f,i,n[h+15],10,-30611744),i=m(i,a,c,f,n[h+6],15,-1560198380),f=m(f,i,a,c,n[h+13],21,1309151649),c=m(c,f,i,a,n[h+4],6,-145523070),a=m(a,c,f,i,n[h+11],10,-1120210379),i=m(i,a,c,f,n[h+2],15,718787259),f=m(f,i,a,c,n[h+9],21,-343485551),c=d(c,r),f=d(f,e),i=d(i,o),a=d(a,u);return[c,f,i,a]}function a(n){for(var t="",r=32*n.length,e=0;e<r;e+=8)t+=String.fromCharCode(n[e>>5]>>>e%32&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e<t.length;e+=1)t[e]=0;for(var r=8*n.length,e=0;e<r;e+=8)t[e>>5]|=(255&n.charCodeAt(e/8))<<e%32;return t}function e(n){for(var t,r="0123456789abcdef",e="",o=0;o<n.length;o+=1)t=n.charCodeAt(o),e+=r.charAt(t>>>4&15)+r.charAt(15&t);return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h(t=r(n)),8*t.length));var t}function u(n,t){return function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16<o.length&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}(r(n),r(t))}function t(n,t,r){return t?r?u(t,n):e(u(t,n)):r?o(n):e(o(n))}"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:n.md5=t}(this);
|
||||
//# sourceMappingURL=md5.min.js.map
|
||||
@@ -0,0 +1,589 @@
|
||||
/* ==========================================================
|
||||
* bootstrap-maxlength.js v1.9.0
|
||||
*
|
||||
* Copyright (c) 2013-2020 Maurizio Napoleoni;
|
||||
*
|
||||
* Licensed under the terms of the MIT license.
|
||||
* See: https://github.com/mimo84/bootstrap-maxlength/blob/master/LICENSE
|
||||
* ========================================================== */
|
||||
/*global jQuery*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
/**
|
||||
* We need an event when the elements are destroyed
|
||||
* because if an input is removed, we have to remove the
|
||||
* maxlength object associated (if any).
|
||||
* From:
|
||||
* http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
|
||||
*/
|
||||
if (!$.event.special.destroyed) {
|
||||
$.event.special.destroyed = {
|
||||
remove: function (o) {
|
||||
if (o.handler) {
|
||||
o.handler();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
$.fn.extend({
|
||||
maxlength: function (options, callback) {
|
||||
var documentBody = $('body'),
|
||||
defaults = {
|
||||
showOnReady: false, // true to always show when indicator is ready
|
||||
alwaysShow: true, // if true the indicator it's always shown.
|
||||
threshold: 0, // Represents how many chars left are needed to show up the counter
|
||||
warningClass: 'small form-text text-muted',
|
||||
limitReachedClass: 'small form-text text-danger',
|
||||
separator: ' / ',
|
||||
preText: '',
|
||||
postText: '',
|
||||
showMaxLength: true,
|
||||
placement: 'bottom-right-inside',
|
||||
message: null, // an alternative way to provide the message text
|
||||
showCharsTyped: true, // show the number of characters typed and not the number of characters remaining
|
||||
validate: false, // if the browser doesn't support the maxlength attribute, attempt to type more than the indicated chars, will be prevented.
|
||||
utf8: false, // counts using bytesize rather than length. eg: '£' is counted as 2 characters.
|
||||
appendToParent: false, // append the indicator to the input field's parent instead of body
|
||||
twoCharLinebreak: true, // count linebreak as 2 characters to match IE/Chrome textarea validation. As well as DB storage.
|
||||
customMaxAttribute: null, // null = use maxlength attribute and browser functionality, string = use specified attribute instead.
|
||||
allowOverMax: false, // Form submit validation is handled on your own. when maxlength has been exceeded 'overmax' class added to element
|
||||
zIndex: 1099
|
||||
};
|
||||
|
||||
if ($.isFunction(options) && !callback) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
options = $.extend(defaults, options);
|
||||
|
||||
|
||||
/**
|
||||
* Return the byte count of the specified character in UTF8 encoding.
|
||||
* Note: This won't cover UTF-8 characters that are 4 bytes long.
|
||||
*
|
||||
* @param input
|
||||
* @return {number}
|
||||
*/
|
||||
function utf8CharByteCount(character) {
|
||||
var c = character.charCodeAt();
|
||||
// Not c then 0, else c < 128 then 1, else c < 2048 then 2, else 3
|
||||
return !c ? 0 : c < 128 ? 1 : c < 2048 ? 2 : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the specified input in UTF8 encoding.
|
||||
*
|
||||
* @param input
|
||||
* @return {number}
|
||||
*/
|
||||
function utf8Length(string) {
|
||||
return string.split("")
|
||||
.map(utf8CharByteCount)
|
||||
// Prevent reduce from throwing an error if the string is empty.
|
||||
.concat(0)
|
||||
.reduce(function (sum, val) {
|
||||
return sum + val;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the specified input.
|
||||
*
|
||||
* @param input
|
||||
* @return {number}
|
||||
*/
|
||||
function inputLength(input) {
|
||||
var text = input.val();
|
||||
|
||||
if (options.twoCharLinebreak) {
|
||||
// Count all line breaks as 2 characters
|
||||
text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
|
||||
} else {
|
||||
// Remove all double-character (\r\n) linebreaks, so they're counted only once.
|
||||
text = text.replace(/(?:\r\n|\r|\n)/g, '\n');
|
||||
}
|
||||
|
||||
var currentLength = 0;
|
||||
|
||||
if (options.utf8) {
|
||||
currentLength = utf8Length(text);
|
||||
} else {
|
||||
currentLength = text.length;
|
||||
}
|
||||
|
||||
// Remove "C:\fakepath\" from counter when using file input
|
||||
// Fix https://github.com/mimo84/bootstrap-maxlength/issues/146
|
||||
if (input.prop("type") === "file" && input.val() !== "") {
|
||||
currentLength -= 12;
|
||||
}
|
||||
|
||||
return currentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the text of the specified input.
|
||||
*
|
||||
* @param input
|
||||
* @param limit
|
||||
*/
|
||||
function truncateChars(input, maxlength) {
|
||||
var text = input.val();
|
||||
|
||||
if (options.twoCharLinebreak) {
|
||||
text = text.replace(/\r(?!\n)|\n(?!\r)/g, '\r\n');
|
||||
|
||||
if (text[text.length - 1] === '\n') {
|
||||
maxlength -= text.length % 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.utf8) {
|
||||
var indexedSize = text.split("").map(utf8CharByteCount);
|
||||
for (
|
||||
var removedBytes = 0,
|
||||
bytesPastMax = utf8Length(text) - maxlength; removedBytes < bytesPastMax; removedBytes += indexedSize.pop()
|
||||
);
|
||||
maxlength -= (maxlength - indexedSize.length);
|
||||
}
|
||||
|
||||
input.val(text.substr(0, maxlength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the indicator should be showing up.
|
||||
*
|
||||
* @param input
|
||||
* @param threshold
|
||||
* @param maxlength
|
||||
* @return {number}
|
||||
*/
|
||||
function charsLeftThreshold(input, threshold, maxlength) {
|
||||
var output = true;
|
||||
if (!options.alwaysShow && (maxlength - inputLength(input) > threshold)) {
|
||||
output = false;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many chars are left to complete the fill up of the form.
|
||||
*
|
||||
* @param input
|
||||
* @param maxlength
|
||||
* @return {number}
|
||||
*/
|
||||
function remainingChars(input, maxlength) {
|
||||
var length = maxlength - inputLength(input);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* When called displays the indicator.
|
||||
*
|
||||
* @param indicator
|
||||
*/
|
||||
function showRemaining(currentInput, indicator) {
|
||||
indicator.css({
|
||||
display: 'block'
|
||||
});
|
||||
currentInput.trigger('maxlength.shown');
|
||||
}
|
||||
|
||||
/**
|
||||
* When called shows the indicator.
|
||||
*
|
||||
* @param indicator
|
||||
*/
|
||||
function hideRemaining(currentInput, indicator) {
|
||||
|
||||
if (options.alwaysShow) {
|
||||
return;
|
||||
}
|
||||
|
||||
indicator.css({
|
||||
display: 'none'
|
||||
});
|
||||
currentInput.trigger('maxlength.hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function updates the value in the indicator
|
||||
*
|
||||
* @param maxLengthThisInput
|
||||
* @param typedChars
|
||||
* @return String
|
||||
*/
|
||||
function updateMaxLengthHTML(currentInputText, maxLengthThisInput, typedChars) {
|
||||
var output = '';
|
||||
if (options.message) {
|
||||
if (typeof options.message === 'function') {
|
||||
output = options.message(currentInputText, maxLengthThisInput);
|
||||
} else {
|
||||
output = options.message.replace('%charsTyped%', typedChars)
|
||||
.replace('%charsRemaining%', maxLengthThisInput - typedChars)
|
||||
.replace('%charsTotal%', maxLengthThisInput);
|
||||
}
|
||||
} else {
|
||||
if (options.preText) {
|
||||
output += options.preText;
|
||||
}
|
||||
if (!options.showCharsTyped) {
|
||||
output += maxLengthThisInput - typedChars;
|
||||
} else {
|
||||
output += typedChars;
|
||||
}
|
||||
if (options.showMaxLength) {
|
||||
output += options.separator + maxLengthThisInput;
|
||||
}
|
||||
if (options.postText) {
|
||||
output += options.postText;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function updates the value of the counter in the indicator.
|
||||
* Wants as parameters: the number of remaining chars, the element currently managed,
|
||||
* the maxLength for the current input and the indicator generated for it.
|
||||
*
|
||||
* @param remaining
|
||||
* @param currentInput
|
||||
* @param maxLengthCurrentInput
|
||||
* @param maxLengthIndicator
|
||||
*/
|
||||
function manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator) {
|
||||
if (maxLengthIndicator) {
|
||||
maxLengthIndicator.html(updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, (maxLengthCurrentInput - remaining)));
|
||||
|
||||
if (remaining > 0) {
|
||||
if (charsLeftThreshold(currentInput, options.threshold, maxLengthCurrentInput)) {
|
||||
showRemaining(currentInput, maxLengthIndicator.removeClass(options.limitReachedClass).addClass(options.warningClass));
|
||||
} else {
|
||||
hideRemaining(currentInput, maxLengthIndicator);
|
||||
}
|
||||
} else {
|
||||
showRemaining(currentInput, maxLengthIndicator.removeClass(options.warningClass).addClass(options.limitReachedClass));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.customMaxAttribute) {
|
||||
// class to use for form validation on custom maxlength attribute
|
||||
if (remaining < 0) {
|
||||
currentInput.addClass('overmax');
|
||||
} else {
|
||||
currentInput.removeClass('overmax');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns an object containing all the
|
||||
* informations about the position of the current input
|
||||
*
|
||||
* @param currentInput
|
||||
* @return object {bottom height left right top width}
|
||||
*
|
||||
*/
|
||||
function getPosition(currentInput) {
|
||||
var el = currentInput[0];
|
||||
return $.extend({}, (typeof el.getBoundingClientRect === 'function') ? el.getBoundingClientRect() : {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight
|
||||
}, currentInput.offset());
|
||||
}
|
||||
|
||||
/**
|
||||
* This function places the maxLengthIndicator based on placement config object.
|
||||
*
|
||||
* @param {object} placement
|
||||
* @param {$} maxLengthIndicator
|
||||
* @return null
|
||||
*
|
||||
*/
|
||||
function placeWithCSS(placement, maxLengthIndicator) {
|
||||
if (!placement || !maxLengthIndicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
var POSITION_KEYS = [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'position'
|
||||
];
|
||||
|
||||
var cssPos = {};
|
||||
|
||||
// filter css properties to position
|
||||
$.each(POSITION_KEYS, function (i, key) {
|
||||
var val = options.placement[key];
|
||||
if (typeof val !== 'undefined') {
|
||||
cssPos[key] = val;
|
||||
}
|
||||
});
|
||||
|
||||
maxLengthIndicator.css(cssPos);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function places the maxLengthIndicator at the
|
||||
* top / bottom / left / right of the currentInput
|
||||
*
|
||||
* @param currentInput
|
||||
* @param maxLengthIndicator
|
||||
* @return null
|
||||
*
|
||||
*/
|
||||
function place(currentInput, maxLengthIndicator) {
|
||||
var pos = getPosition(currentInput);
|
||||
|
||||
// Supports custom placement handler
|
||||
if ($.type(options.placement) === 'function') {
|
||||
options.placement(currentInput, maxLengthIndicator, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
// Supports custom placement via css positional properties
|
||||
if ($.isPlainObject(options.placement)) {
|
||||
placeWithCSS(options.placement, maxLengthIndicator);
|
||||
return;
|
||||
}
|
||||
|
||||
var inputOuter = currentInput.outerWidth(),
|
||||
outerWidth = maxLengthIndicator.outerWidth(),
|
||||
actualWidth = maxLengthIndicator.width(),
|
||||
actualHeight = maxLengthIndicator.height();
|
||||
|
||||
// get the right position if the indicator is appended to the input's parent
|
||||
if (options.appendToParent) {
|
||||
pos.top -= currentInput.parent().offset().top;
|
||||
pos.left -= currentInput.parent().offset().left;
|
||||
}
|
||||
|
||||
switch (options.placement) {
|
||||
case 'bottom':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height,
|
||||
left: pos.left + pos.width / 2 - actualWidth / 2
|
||||
});
|
||||
break;
|
||||
case 'top':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left + pos.width / 2 - actualWidth / 2
|
||||
});
|
||||
break;
|
||||
case 'left':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height / 2 - actualHeight / 2,
|
||||
left: pos.left - actualWidth
|
||||
});
|
||||
break;
|
||||
case 'right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height / 2 - actualHeight / 2,
|
||||
left: pos.left + pos.width
|
||||
});
|
||||
break;
|
||||
case 'bottom-right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height,
|
||||
left: pos.left + pos.width
|
||||
});
|
||||
break;
|
||||
case 'top-right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left + inputOuter
|
||||
});
|
||||
break;
|
||||
case 'top-left':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'bottom-left':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + currentInput.outerHeight(),
|
||||
left: pos.left - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'centered-right':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + (actualHeight / 2),
|
||||
left: pos.left + inputOuter - outerWidth - 3
|
||||
});
|
||||
break;
|
||||
|
||||
// Some more options for placements
|
||||
case 'bottom-right-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + pos.height,
|
||||
left: pos.left + pos.width - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'top-right-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left + inputOuter - outerWidth
|
||||
});
|
||||
break;
|
||||
case 'top-left-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top - actualHeight,
|
||||
left: pos.left
|
||||
});
|
||||
break;
|
||||
case 'bottom-left-inside':
|
||||
maxLengthIndicator.css({
|
||||
top: pos.top + currentInput.outerHeight(),
|
||||
left: pos.left
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns true if the indicator position needs to
|
||||
* be recalculated when the currentInput changes
|
||||
*
|
||||
* @return {boolean}
|
||||
*
|
||||
*/
|
||||
function isPlacementMutable() {
|
||||
return options.placement === 'bottom-right-inside' || options.placement === 'top-right-inside' || typeof options.placement === 'function' || (options.message && typeof options.message === 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves the maximum length of currentInput
|
||||
*
|
||||
* @param currentInput
|
||||
* @return {number}
|
||||
*
|
||||
*/
|
||||
function getMaxLength(currentInput) {
|
||||
var max = currentInput.attr('maxlength') || options.customMaxAttribute;
|
||||
|
||||
if (options.customMaxAttribute && !options.allowOverMax) {
|
||||
var custom = currentInput.attr(options.customMaxAttribute);
|
||||
if (!max || custom < max) {
|
||||
max = custom;
|
||||
}
|
||||
}
|
||||
|
||||
if (!max) {
|
||||
max = currentInput.attr('size');
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
return this.each(function () {
|
||||
|
||||
var currentInput = $(this),
|
||||
maxLengthCurrentInput,
|
||||
maxLengthIndicator;
|
||||
|
||||
$(window).resize(function () {
|
||||
if (maxLengthIndicator) {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
});
|
||||
|
||||
function firstInit() {
|
||||
var maxlengthContent = updateMaxLengthHTML(currentInput.val(), maxLengthCurrentInput, '0');
|
||||
maxLengthCurrentInput = getMaxLength(currentInput);
|
||||
|
||||
if (!maxLengthIndicator) {
|
||||
maxLengthIndicator = $('<span class="bootstrap-maxlength"></span>').css({
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
whiteSpace: 'nowrap',
|
||||
zIndex: options.zIndex
|
||||
}).html(maxlengthContent);
|
||||
}
|
||||
|
||||
// We need to detect resizes if we are dealing with a textarea:
|
||||
if (currentInput.is('textarea')) {
|
||||
currentInput.data('maxlenghtsizex', currentInput.outerWidth());
|
||||
currentInput.data('maxlenghtsizey', currentInput.outerHeight());
|
||||
|
||||
currentInput.mouseup(function () {
|
||||
if (currentInput.outerWidth() !== currentInput.data('maxlenghtsizex') || currentInput.outerHeight() !== currentInput.data('maxlenghtsizey')) {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
currentInput.data('maxlenghtsizex', currentInput.outerWidth());
|
||||
currentInput.data('maxlenghtsizey', currentInput.outerHeight());
|
||||
});
|
||||
}
|
||||
|
||||
if (options.appendToParent) {
|
||||
currentInput.parent().append(maxLengthIndicator);
|
||||
currentInput.parent().css('position', 'relative');
|
||||
} else {
|
||||
documentBody.append(maxLengthIndicator);
|
||||
}
|
||||
|
||||
var remaining = remainingChars(currentInput, getMaxLength(currentInput));
|
||||
manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
if (options.showOnReady) {
|
||||
currentInput.ready(function () {
|
||||
firstInit();
|
||||
});
|
||||
} else {
|
||||
currentInput.focus(function () {
|
||||
firstInit();
|
||||
});
|
||||
}
|
||||
|
||||
currentInput.on('maxlength.reposition', function () {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
});
|
||||
|
||||
|
||||
currentInput.on('destroyed', function () {
|
||||
if (maxLengthIndicator) {
|
||||
maxLengthIndicator.remove();
|
||||
}
|
||||
});
|
||||
|
||||
currentInput.on('blur', function () {
|
||||
if (maxLengthIndicator && !options.showOnReady) {
|
||||
maxLengthIndicator.remove();
|
||||
}
|
||||
});
|
||||
|
||||
currentInput.on('input', function () {
|
||||
var maxlength = getMaxLength(currentInput),
|
||||
remaining = remainingChars(currentInput, maxlength),
|
||||
output = true;
|
||||
|
||||
if (options.validate && remaining < 0) {
|
||||
truncateChars(currentInput, maxlength);
|
||||
output = false;
|
||||
} else {
|
||||
manageRemainingVisibility(remaining, currentInput, maxLengthCurrentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
if (isPlacementMutable()) {
|
||||
place(currentInput, maxLengthIndicator);
|
||||
}
|
||||
|
||||
return output;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}(jQuery));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
.mt-wrapper{
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.mt-nav-bar {
|
||||
width : 100%;
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
}
|
||||
.mt-nav-bar .mt-nav{
|
||||
background-color: #fff;
|
||||
}
|
||||
.mt-nav-panel{
|
||||
overflow: hidden;
|
||||
}
|
||||
.mt-nav-panel ul{
|
||||
width: 10000px;
|
||||
}
|
||||
.mt-nav-panel ul li {
|
||||
position: relative;
|
||||
}
|
||||
.mt-tab-content{
|
||||
height: 100%;
|
||||
}
|
||||
.mt-close-tab {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 18px;
|
||||
right: 10px;
|
||||
color: #c2c2c2;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
vertical-align: 2px;
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
-webkit-transition: all .3s cubic-bezier(.645,.045,.355,1);
|
||||
transition: all .3s cubic-bezier(.645,.045,.355,1);
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
}
|
||||
.mt-close-tab:before {
|
||||
-webkit-transform: scale(.8);
|
||||
transform: scale(.8);
|
||||
display: inline-block;
|
||||
/*vertical-align: -1px;*/
|
||||
}
|
||||
li:hover .mt-close-tab {
|
||||
display: inline;
|
||||
}
|
||||
.mt-hidden-list .mt-close-tab {
|
||||
display: none !important;
|
||||
}
|
||||
.mt-nav-bar a {
|
||||
cursor: pointer !important;
|
||||
max-height: 48px;
|
||||
padding-top: 14px;
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
@media (max-width: 767px){
|
||||
.mt-tab-content{
|
||||
/*padding-top: 0 !important;*/
|
||||
}
|
||||
}
|
||||
.mt-tab-content{
|
||||
-webkit-overflow-scrolling:touch;
|
||||
overflow:auto;
|
||||
}
|
||||
.mt-dragging-tab{
|
||||
left: auto;
|
||||
position: absolute !important;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
.mt-dragging-tab > a{
|
||||
background: #FBFDFD !important;
|
||||
}
|
||||
|
||||
/*新增*/
|
||||
.mt-nav-bar .mt-nav .nav-tabs {
|
||||
margin-bottom: 0px;
|
||||
border-color: #eceeef;
|
||||
}
|
||||
.mt-close-tab:hover {
|
||||
color: #f96868;
|
||||
}
|
||||
.mt-dropdown .caret {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
}
|
||||
.mt-dropdown .dropdown-menu {
|
||||
margin-top: 0px;
|
||||
}
|
||||
#contextify-menu {
|
||||
min-width: 80px!important;
|
||||
}
|
||||
.mt-close-tab:hover {
|
||||
background-color: #f96868;
|
||||
color: #fff;
|
||||
}
|
||||
.mt-nav .nav-tabs a:not([data-type="main"]) {
|
||||
padding-right: 40px;
|
||||
}
|
||||
.mt-nav-tools-left li a,
|
||||
.mt-nav-tools-right li a {
|
||||
padding-right: 15px!important;
|
||||
}
|
||||
.mt-nav-bar .mt-nav .nav-tabs .mt-dragging {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
@@ -0,0 +1,104 @@
|
||||
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/ksort.js"></script>');
|
||||
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/crypto-js.min.js"></script>');
|
||||
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/hmac-sha256.js"></script>');
|
||||
document.write('<script type="text/javascript" src="../../bootstrap/js/authorization/enc-base64.min.js"></script>');
|
||||
document.write('<script type="text/javascript" src="../../bootstrap/js/jquery.cookie.min.js"></script>');
|
||||
|
||||
|
||||
function GenerateAuthorization(path, method, params) {
|
||||
let key = "admin";
|
||||
let secret = "12878dd962115106db6d";
|
||||
|
||||
let date = new Date();
|
||||
let datetime = date.getFullYear() + "-" // "年"
|
||||
+ ((date.getMonth() + 1) > 10 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1)) + "-" // "月"
|
||||
+ (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " " // "日"
|
||||
+ (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":" // "小时"
|
||||
+ (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":" // "分钟"
|
||||
+ (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds()); // "秒"
|
||||
|
||||
let sortParamsEncode = decodeURIComponent(jQuery.param(ksort(params)));
|
||||
let encryptStr = path + "|" + method.toUpperCase() + "|" + sortParamsEncode + "|" + datetime;
|
||||
let digest = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(encryptStr, secret));
|
||||
return {authorization: key + " " + digest, date: datetime};
|
||||
}
|
||||
|
||||
function IsJson(str) {
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
let obj = JSON.parse(str);
|
||||
if (typeof obj == 'object' && obj) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error:' + str + '!!!' + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
console.log('It is not a string!')
|
||||
}
|
||||
|
||||
function AjaxError(response) {
|
||||
let errCode = response.status;
|
||||
let errMsg = response.responseText;
|
||||
|
||||
if (errCode === 401) { // 跳转到登录页
|
||||
// 关闭当前界面
|
||||
parent.window.close();
|
||||
window.open("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsJson(response.responseText)) {
|
||||
const errInfo = JSON.parse(response.responseText);
|
||||
errCode = errInfo.code;
|
||||
errMsg = errInfo.message;
|
||||
}
|
||||
|
||||
$.alert({
|
||||
title: '错误提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'red',
|
||||
content: '错误码:' + errCode + '<br/>' + '错误信息:' + errMsg,
|
||||
});
|
||||
}
|
||||
|
||||
function AjaxForm(method, url, params, beforeSendFunction, successFunction, errorFunction) {
|
||||
let authorizationData = GenerateAuthorization(url, method, params);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: method,
|
||||
data: params,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||
'Authorization': authorizationData.authorization,
|
||||
'Authorization-Date': authorizationData.date,
|
||||
'Token': $.cookie("_login_token_"),
|
||||
},
|
||||
beforeSend: beforeSendFunction,
|
||||
success: successFunction,
|
||||
error: errorFunction,
|
||||
});
|
||||
}
|
||||
|
||||
function AjaxPostJson(url, params, beforeSendFunction, successFunction, errorFunction) {
|
||||
let authorizationData = GenerateAuthorization(url, "POST", params);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "POST",
|
||||
data: JSON.stringify(params),
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Authorization': authorizationData.authorization,
|
||||
'Authorization-Date': authorizationData.date,
|
||||
'Token': $.cookie("_login_token_"),
|
||||
},
|
||||
beforeSend: beforeSendFunction,
|
||||
success: successFunction,
|
||||
error: errorFunction,
|
||||
});
|
||||
}
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
;jQuery( function() {
|
||||
// 停止
|
||||
$("body").on('click','[data-stopPropagation]',function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// 滚动条
|
||||
if($('.lyear-scroll')[0]) {
|
||||
$('.lyear-scroll').each(function(){
|
||||
new PerfectScrollbar(this, {
|
||||
swipeEasing: false,
|
||||
suppressScrollX: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 侧边栏
|
||||
$(document).on('click', '.lyear-aside-toggler', function() {
|
||||
$('.lyear-layout-sidebar').toggleClass('lyear-aside-open');
|
||||
$("body").toggleClass('lyear-layout-sidebar-close');
|
||||
|
||||
if ($('.lyear-mask-modal').length == 0) {
|
||||
$('<div class="lyear-mask-modal"></div>').prependTo('body');
|
||||
} else {
|
||||
$( '.lyear-mask-modal' ).remove();
|
||||
}
|
||||
});
|
||||
|
||||
// 遮罩层
|
||||
$(document).on('click', '.lyear-mask-modal', function() {
|
||||
$( this ).remove();
|
||||
$('.lyear-layout-sidebar').toggleClass('lyear-aside-open');
|
||||
$('body').toggleClass('lyear-layout-sidebar-close');
|
||||
});
|
||||
|
||||
// 侧边栏导航
|
||||
$(document).on('click', '.nav-item-has-subnav > a', function() {
|
||||
$subnavToggle = jQuery( this );
|
||||
$navHasSubnav = $subnavToggle.parent();
|
||||
$topHasSubNav = $subnavToggle.parents('.nav-item-has-subnav').last();
|
||||
$subnav = $navHasSubnav.find('.nav-subnav').first();
|
||||
$viSubHeight = $navHasSubnav.siblings().find('.nav-subnav:visible').outerHeight();
|
||||
$scrollBox = $('.lyear-layout-sidebar-info');
|
||||
$navHasSubnav.siblings().find('.nav-subnav:visible').slideUp(500).parent().removeClass('open');
|
||||
$subnav.slideToggle( 300, function() {
|
||||
$navHasSubnav.toggleClass( 'open' );
|
||||
|
||||
// 新增滚动条处理
|
||||
var scrollHeight = 0;
|
||||
pervTotal = $topHasSubNav.prevAll().length,
|
||||
boxHeight = $scrollBox.outerHeight(),
|
||||
innerHeight = $('.sidebar-main').outerHeight(),
|
||||
thisScroll = $scrollBox.scrollTop(),
|
||||
thisSubHeight = $(this).outerHeight(),
|
||||
footHeight = 121;
|
||||
|
||||
if (footHeight + innerHeight - boxHeight >= (pervTotal * 48)) {
|
||||
scrollHeight = pervTotal * 48;
|
||||
}
|
||||
if ($subnavToggle.parents('.nav-item-has-subnav').length == 1) {
|
||||
$scrollBox.animate({scrollTop: scrollHeight}, 300);
|
||||
} else {
|
||||
// 子菜单操作
|
||||
if (typeof($viSubHeight) != 'undefined' && $viSubHeight != null) {
|
||||
scrollHeight = thisScroll + thisSubHeight - $viSubHeight;
|
||||
$scrollBox.animate({scrollTop: scrollHeight}, 300);
|
||||
} else {
|
||||
if ((thisScroll + boxHeight - $scrollBox[0].scrollHeight) == 0) {
|
||||
scrollHeight = thisScroll - thisSubHeight;
|
||||
$scrollBox.animate({scrollTop: scrollHeight}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 读取cookie中的主题设置
|
||||
var the_logo_bg = $.cookie('the_logo_bg'),
|
||||
the_header_bg = $.cookie('the_header_bg'),
|
||||
the_sidebar_bg = $.cookie('the_sidebar_bg');
|
||||
|
||||
if (the_logo_bg) $('body').attr('data-logobg', the_logo_bg);
|
||||
if (the_header_bg) $('body').attr('data-headerbg', the_header_bg);
|
||||
if (the_sidebar_bg) $('body').attr('data-sidebarbg', the_sidebar_bg);
|
||||
|
||||
// 处理主题配色下拉选中
|
||||
$(".dropdown-skin :radio").each(function(){
|
||||
var $this = $(this),
|
||||
radioName = $this.attr('name');
|
||||
switch (radioName) {
|
||||
case 'logo_bg':
|
||||
$this.val() == the_logo_bg && $this.prop("checked", true);
|
||||
break;
|
||||
case 'header_bg':
|
||||
$this.val() == the_header_bg && $this.prop("checked", true);
|
||||
break;
|
||||
case 'sidebar_bg':
|
||||
$this.val() == the_sidebar_bg && $this.prop("checked", true);
|
||||
}
|
||||
});
|
||||
|
||||
// 设置主题配色
|
||||
setTheme = function(input_name, data_name) {
|
||||
$("input[name='"+input_name+"']").click(function(){
|
||||
$('body').attr(data_name, $(this).val());
|
||||
$.cookie('the_'+input_name, $(this).val());
|
||||
});
|
||||
}
|
||||
setTheme('sidebar_bg', 'data-sidebarbg');
|
||||
setTheme('logo_bg', 'data-logobg');
|
||||
setTheme('header_bg', 'data-headerbg');
|
||||
|
||||
// 选项卡
|
||||
$('#iframe-content').multitabs({
|
||||
iframe : true,
|
||||
refresh : 'nav', // iframe中页面是否刷新,'no':'从不刷新','nav':'点击菜单刷新','all':'菜单和tab点击都刷新'
|
||||
nav: {
|
||||
backgroundColor: '#ffffff',
|
||||
maxTabs : 35, // 选项卡最大值
|
||||
},
|
||||
init : [{
|
||||
type : 'main',
|
||||
title : $.cookie('_nav_title_') ? $.cookie('_nav_title_') : '仪表盘',
|
||||
url : $.cookie('_nav_url_') ? $.cookie('_nav_url_') : '/dashboard',
|
||||
}]
|
||||
});
|
||||
|
||||
$(document).on('click', '.nav-item .multitabs', function() {
|
||||
$('.nav-item').removeClass('active');
|
||||
$('.nav-subnav li').removeClass('active');
|
||||
$(this).parent('li').addClass('active');
|
||||
$(this).parents('.nav-item-has-subnav').addClass('open').first().addClass('active');
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + 24 * 60 * 60 * 1000); // 24 * 60 * 60 * 1000 表示 24 小时
|
||||
$.cookie('_nav_url_', $(this).attr('href'), {expires: date});
|
||||
$.cookie('_nav_title_', $(this).text(), {expires: date});
|
||||
});
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
||||
/*! jquery.cookie v1.4.1 | MIT */
|
||||
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* pagination.js 1.5.1
|
||||
* A jQuery plugin to provide simple yet fully customisable pagination.
|
||||
* @version 1.5.1
|
||||
* @author mss
|
||||
* @url https://github.com/Maxiaoxiang/jQuery-plugins
|
||||
*
|
||||
* @调用方法
|
||||
* $(selector).pagination(option, callback);
|
||||
* -此处callback是初始化调用,option里的callback是点击页码后调用
|
||||
*
|
||||
* -- example --
|
||||
* $(selector).pagination({
|
||||
* ... // 配置参数
|
||||
* callback: function(api) {
|
||||
* console.log('点击页码调用该回调'); //切换页码时执行一次回调
|
||||
* }
|
||||
* }, function(){
|
||||
* console.log('初始化'); //插件初始化时调用该方法,比如请求第一次接口来初始化分页配置
|
||||
* });
|
||||
*/
|
||||
;
|
||||
(function (factory) {
|
||||
if (typeof define === "function" && (define.amd || define.cmd) && !jQuery) {
|
||||
// AMD或CMD
|
||||
define(["jquery"], factory);
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// Node/CommonJS
|
||||
module.exports = function (root, jQuery) {
|
||||
if (jQuery === undefined) {
|
||||
if (typeof window !== 'undefined') {
|
||||
jQuery = require('jquery');
|
||||
} else {
|
||||
jQuery = require('jquery')(root);
|
||||
}
|
||||
}
|
||||
factory(jQuery);
|
||||
return jQuery;
|
||||
};
|
||||
} else {
|
||||
//Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
|
||||
//配置参数
|
||||
var defaults = {
|
||||
totalData: 0, //数据总条数
|
||||
showData: 0, //每页显示的条数
|
||||
pageCount: 9, //总页数,默认为9
|
||||
current: 1, //当前第几页
|
||||
prevCls: 'prev', //上一页class
|
||||
nextCls: 'next', //下一页class
|
||||
prevContent: '<', //上一页内容
|
||||
nextContent: '>', //下一页内容
|
||||
activeCls: 'active', //当前页选中状态
|
||||
coping: false, //首页和尾页
|
||||
isHide: false, //当前页数为0页或者1页时不显示分页
|
||||
homePage: '', //首页节点内容
|
||||
endPage: '', //尾页节点内容
|
||||
keepShowPN: false, //是否一直显示上一页下一页
|
||||
mode: 'unfixed', //分页模式,unfixed:不固定页码数量,fixed:固定页码数量
|
||||
count: 4, //mode为unfixed时显示当前选中页前后页数,mode为fixed显示页码总数
|
||||
jump: false, //跳转到指定页数
|
||||
jumpIptCls: 'jump-ipt', //文本框内容
|
||||
jumpBtnCls: 'jump-btn', //跳转按钮
|
||||
jumpBtn: '跳转', //跳转按钮文本
|
||||
callback: function () {} //回调
|
||||
};
|
||||
|
||||
var Pagination = function (element, options) {
|
||||
//全局变量
|
||||
var opts = options, //配置
|
||||
current, //当前页
|
||||
$document = $(document),
|
||||
$obj = $(element); //容器
|
||||
|
||||
/**
|
||||
* 设置总页数
|
||||
* @param {int} page 页码
|
||||
* @return opts.pageCount 总页数配置
|
||||
*/
|
||||
this.setPageCount = function (page) {
|
||||
return opts.pageCount = page;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取总页数
|
||||
* 如果配置了总条数和每页显示条数,将会自动计算总页数并略过总页数配置,反之
|
||||
* @return {int} 总页数
|
||||
*/
|
||||
this.getPageCount = function () {
|
||||
return opts.totalData && opts.showData ? Math.ceil(parseInt(opts.totalData) / opts.showData) : opts.pageCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前页
|
||||
* @return {int} 当前页码
|
||||
*/
|
||||
this.getCurrent = function () {
|
||||
return current;
|
||||
};
|
||||
|
||||
/**
|
||||
* 填充数据
|
||||
* @param {int} 页码
|
||||
*/
|
||||
this.filling = function (index) {
|
||||
var html = '';
|
||||
current = parseInt(index) || parseInt(opts.current); //当前页码
|
||||
var pageCount = this.getPageCount(); //获取的总页数
|
||||
switch (opts.mode) { //配置模式
|
||||
case 'fixed': //固定按钮模式
|
||||
html += '<li class="page-item"><a href="javascript:;" class="page-link ' + opts.prevCls + '">' + opts.prevContent + '</a></li>';
|
||||
if (opts.coping) {
|
||||
var home = opts.coping && opts.homePage ? opts.homePage : '1';
|
||||
html += '<li class="page-item"><a class="page-link" href="javascript:;" data-page="1">' + home + '</a></li>';
|
||||
}
|
||||
var start = current > opts.count - 1 ? current + opts.count - 1 > pageCount ? current - (opts.count - (pageCount - current)) : current - 2 : 1;
|
||||
var end = current + opts.count - 1 > pageCount ? pageCount : start + opts.count;
|
||||
for (; start <= end; start++) {
|
||||
if (start != current) {
|
||||
html += '<li class="page-item"><a class="page-link" href="javascript:;" data-page="' + start + '">' + start + '</a></li>';
|
||||
} else {
|
||||
html += '<li class="page-item active"><span class="page-link ' + opts.activeCls + '">' + start + '</span></li>';
|
||||
}
|
||||
}
|
||||
if (opts.coping) {
|
||||
var _end = opts.coping && opts.endPage ? opts.endPage : pageCount;
|
||||
html += '<li class="page-item"><a class="page-link" href="javascript:;" data-page="' + pageCount + '">' + _end + '</a></li>';
|
||||
}
|
||||
html += '<li class="page-item"><a href="javascript:;" class="page-link ' + opts.nextCls + '">' + opts.nextContent + '</a></li>';
|
||||
break;
|
||||
|
||||
// if (opts.keepShowPN || current > 1) { //上一页
|
||||
// html += '<a href="javascript:;" class="' + opts.prevCls + '">' + opts.prevContent + '</a>';
|
||||
// } else {
|
||||
// if (opts.keepShowPN == false) {
|
||||
// $obj.find('.' + opts.prevCls) && $obj.find('.' + opts.prevCls).remove();
|
||||
// }
|
||||
// }
|
||||
// if (current >= opts.count + 2 && current != 1 && pageCount != opts.count) {
|
||||
// var home = opts.coping && opts.homePage ? opts.homePage : '1';
|
||||
// html += opts.coping ? '<a href="javascript:;" data-page="1">' + home + '</a><span>...</span>' : '';
|
||||
// }
|
||||
// var start = (current - opts.count) <= 1 ? 1 : (current - opts.count);
|
||||
// var end = (current + opts.count) >= pageCount ? pageCount : (current + opts.count);
|
||||
// for (; start <= end; start++) {
|
||||
// if (start <= pageCount && start >= 1) {
|
||||
// if (start != current) {
|
||||
// html += '<a href="javascript:;" data-page="' + start + '">' + start + '</a>';
|
||||
// } else {
|
||||
// html += '<span class="' + opts.activeCls + '">' + start + '</span>';
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (current + opts.count < pageCount && current >= 1 && pageCount > opts.count) {
|
||||
// var end = opts.coping && opts.endPage ? opts.endPage : pageCount;
|
||||
// html += opts.coping ? '<span>...</span><a href="javascript:;" data-page="' + pageCount + '">' + end + '</a>' : '';
|
||||
// }
|
||||
// if (opts.keepShowPN || current < pageCount) { //下一页
|
||||
// html += '<a href="javascript:;" class="' + opts.nextCls + '">' + opts.nextContent + '</a>';
|
||||
// } else {
|
||||
// if (opts.keepShowPN == false) {
|
||||
// $obj.find('.' + opts.nextCls) && $obj.find('.' + opts.nextCls).remove();
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
|
||||
case 'unfixed': //不固定按钮模式
|
||||
if (opts.keepShowPN || current > 1) { //上一页
|
||||
html += '<a href="javascript:;" class="' + opts.prevCls + '">' + opts.prevContent + '</a>';
|
||||
} else {
|
||||
if (opts.keepShowPN == false) {
|
||||
$obj.find('.' + opts.prevCls) && $obj.find('.' + opts.prevCls).remove();
|
||||
}
|
||||
}
|
||||
if (current >= opts.count + 2 && current != 1 && pageCount != opts.count) {
|
||||
var home = opts.coping && opts.homePage ? opts.homePage : '1';
|
||||
html += opts.coping ? '<a href="javascript:;" data-page="1">' + home + '</a><span>...</span>' : '';
|
||||
}
|
||||
var start = (current - opts.count) <= 1 ? 1 : (current - opts.count);
|
||||
var end = (current + opts.count) >= pageCount ? pageCount : (current + opts.count);
|
||||
for (; start <= end; start++) {
|
||||
if (start <= pageCount && start >= 1) {
|
||||
if (start != current) {
|
||||
html += '<a href="javascript:;" data-page="' + start + '">' + start + '</a>';
|
||||
} else {
|
||||
html += '<span class="' + opts.activeCls + '">' + start + '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current + opts.count < pageCount && current >= 1 && pageCount > opts.count) {
|
||||
var end = opts.coping && opts.endPage ? opts.endPage : pageCount;
|
||||
html += opts.coping ? '<span>...</span><a href="javascript:;" data-page="' + pageCount + '">' + end + '</a>' : '';
|
||||
}
|
||||
if (opts.keepShowPN || current < pageCount) { //下一页
|
||||
html += '<a href="javascript:;" class="' + opts.nextCls + '">' + opts.nextContent + '</a>';
|
||||
} else {
|
||||
if (opts.keepShowPN == false) {
|
||||
$obj.find('.' + opts.nextCls) && $obj.find('.' + opts.nextCls).remove();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'easy': //简单模式
|
||||
break;
|
||||
default:
|
||||
}
|
||||
html += opts.jump ? '<input type="text" class="' + opts.jumpIptCls + '"><a href="javascript:;" class="' + opts.jumpBtnCls + '">' + opts.jumpBtn + '</a>' : '';
|
||||
$obj.empty().html(html);
|
||||
};
|
||||
|
||||
//绑定事件
|
||||
this.eventBind = function () {
|
||||
var that = this;
|
||||
var pageCount = that.getPageCount(); //总页数
|
||||
var index = 1;
|
||||
$obj.off().on('click', 'a', function () {
|
||||
if ($(this).hasClass(opts.nextCls)) {
|
||||
if (parseInt($obj.find('.' + opts.activeCls).text()) >= pageCount) {
|
||||
$(this).addClass('disabled');
|
||||
return false;
|
||||
} else {
|
||||
index = parseInt($obj.find('.' + opts.activeCls).text()) + 1;
|
||||
}
|
||||
} else if ($(this).hasClass(opts.prevCls)) {
|
||||
if (parseInt($obj.find('.' + opts.activeCls).text()) <= 1) {
|
||||
$(this).addClass('disabled');
|
||||
return false;
|
||||
} else {
|
||||
index = parseInt($obj.find('.' + opts.activeCls).text()) - 1;
|
||||
}
|
||||
} else if ($(this).hasClass(opts.jumpBtnCls)) {
|
||||
if ($obj.find('.' + opts.jumpIptCls).val() !== '') {
|
||||
index = parseInt($obj.find('.' + opts.jumpIptCls).val());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
index = parseInt($(this).data('page'));
|
||||
}
|
||||
that.filling(index);
|
||||
typeof opts.callback === 'function' && opts.callback(that);
|
||||
});
|
||||
//输入跳转的页码
|
||||
$obj.on('input propertychange', '.' + opts.jumpIptCls, function () {
|
||||
var $this = $(this);
|
||||
var val = $this.val();
|
||||
var reg = /[^\d]/g;
|
||||
if (reg.test(val)) $this.val(val.replace(reg, ''));
|
||||
(parseInt(val) > pageCount) && $this.val(pageCount);
|
||||
if (parseInt(val) === 0) $this.val(1); //最小值为1
|
||||
});
|
||||
//回车跳转指定页码
|
||||
$document.keydown(function (e) {
|
||||
if (e.keyCode == 13 && $obj.find('.' + opts.jumpIptCls).val()) {
|
||||
var index = parseInt($obj.find('.' + opts.jumpIptCls).val());
|
||||
that.filling(index);
|
||||
typeof opts.callback === 'function' && opts.callback(that);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//初始化
|
||||
this.init = function () {
|
||||
this.filling(opts.current);
|
||||
this.eventBind();
|
||||
if (opts.isHide && this.getPageCount() == '1' || this.getPageCount() == '0') {
|
||||
$obj.hide();
|
||||
} else {
|
||||
$obj.show();
|
||||
}
|
||||
};
|
||||
this.init();
|
||||
};
|
||||
|
||||
$.fn.pagination = function (parameter, callback) {
|
||||
if (typeof parameter == 'function') { //重载
|
||||
callback = parameter;
|
||||
parameter = {};
|
||||
} else {
|
||||
parameter = parameter || {};
|
||||
callback = callback || function () {};
|
||||
}
|
||||
var options = $.extend({}, defaults, parameter);
|
||||
return this.each(function () {
|
||||
var pagination = new Pagination(this, options);
|
||||
callback(pagination);
|
||||
});
|
||||
};
|
||||
|
||||
}));
|
||||
Vendored
+141
@@ -0,0 +1,141 @@
|
||||
;jQuery( function() {
|
||||
// 工具提示
|
||||
if($('[data-toggle="tooltip"]')[0]) {
|
||||
$('[data-toggle="tooltip"]').tooltip({
|
||||
"container" : 'body',
|
||||
});
|
||||
}
|
||||
|
||||
// POP弹出框
|
||||
if($('[data-toggle="popover"]')[0]) {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
}
|
||||
|
||||
// 关闭卡片
|
||||
$(document).on('click', '.card-btn-close', function() {
|
||||
$(this).closest('.card').fadeOut(150, function() {
|
||||
if ($(this).parent().children().length == 1) {
|
||||
$(this).parent().remove();
|
||||
} else {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 卡片收缩与打开
|
||||
$(document).on('click', '.card-btn-slide', function(){
|
||||
$(this).toggleClass('rotate-180').closest('.card').find('.card-body').slideToggle();
|
||||
});
|
||||
|
||||
/**
|
||||
* 如果页面中需要用到滚动条,请先导入perfect-scrollbar.min.js
|
||||
*/
|
||||
if($('.lyear-scroll')[0]) {
|
||||
$('.lyear-scroll').each(function(){
|
||||
new PerfectScrollbar(this, {
|
||||
swipeEasing: false,
|
||||
suppressScrollX: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 颜色选取
|
||||
jQuery('.js-colorpicker').each(function() {
|
||||
var $colorpicker = jQuery(this);
|
||||
var $colorpickerMode = $colorpicker.data('colorpicker-mode') ? $colorpicker.data('colorpicker-mode') : 'auto';
|
||||
$colorpicker.colorpicker({
|
||||
'format': $colorpickerMode,
|
||||
});
|
||||
});
|
||||
|
||||
// 日期选择器
|
||||
jQuery("[data-provide = 'datepicker']").each(function() {
|
||||
var options = {
|
||||
language: 'zh-CN', // 默认简体中文
|
||||
multidateSeparator: ', ' // 默认多个日期用,分隔
|
||||
}
|
||||
|
||||
options = $.extend( options, getDataOptions( $(this) ));
|
||||
|
||||
if ( $(this).prop("tagName") != 'INPUT' ) {
|
||||
options.inputs = [$(this).find('input:first'), $(this).find('input:last')];
|
||||
}
|
||||
|
||||
$(this).datepicker(options);
|
||||
});
|
||||
|
||||
// 时间选择器
|
||||
jQuery("[data-provide = 'clockpicker']").each(function() {
|
||||
$(this).clockpicker({
|
||||
donetext: 'Done'
|
||||
});
|
||||
});
|
||||
|
||||
// 时间日期选择器
|
||||
jQuery("[data-provide = 'datetimepicker']").each(function() {
|
||||
var options = {
|
||||
locale: moment.locale(),
|
||||
}
|
||||
|
||||
options = $.extend( options, getDataOptions( $(this) ));
|
||||
|
||||
if ( $(this).prop("tagName") != 'INPUT' ) {
|
||||
options.inputs = [$(this).find('input:first'), $(this).find('input:last')];
|
||||
}
|
||||
console.log(options);
|
||||
$(this).datetimepicker(options);
|
||||
});
|
||||
|
||||
// 标签
|
||||
$('.js-tags-input').each(function() {
|
||||
var $this = $(this);
|
||||
$this.tagsInput({
|
||||
height: $this.data('height') ? $this.data('height') : '36px',
|
||||
width: '100%',
|
||||
defaultText: $this.attr("placeholder"),
|
||||
removeWithBackspace: true,
|
||||
delimiter: [',']
|
||||
});
|
||||
});
|
||||
|
||||
// 复选框全选
|
||||
$("#check-all").change(function () {
|
||||
if ($boxname = $(this).data('name')) {
|
||||
$(this).closest('table').find("input[name='" + $boxname + "']").prop('checked', $(this).prop("checked"));
|
||||
} else {
|
||||
$(this).closest('table').find(".custom-checkbox input[type='checkbox']").prop('checked', $(this).prop("checked"));
|
||||
}
|
||||
});
|
||||
|
||||
// iframe打开tab
|
||||
$(document).on('click', '.js-create-tab', function(){
|
||||
parent.$(parent.document).data('multitabs').create({
|
||||
iframe : true,
|
||||
title : $(this).data('title') ? $(this).data('title') : '标题',
|
||||
url : $(this).data('url') ? $(this).data('url') : '/dashboard'
|
||||
}, true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
// 参考国外模板的写法,获取当前的配置,以data-*(*指插件原有的配置名)
|
||||
getDataOptions = function(el, castList) {
|
||||
var options = {};
|
||||
|
||||
$.each( $(el).data(), function(key, value){
|
||||
|
||||
key = dataToOption(key);
|
||||
|
||||
if ( key == 'provide' ) {
|
||||
return;
|
||||
}
|
||||
options[key] = value;
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
dataToOption = function(name) {
|
||||
return name.replace(/-([a-z])/g, function(x){return x[1].toUpperCase();});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Vendored
+4
File diff suppressed because one or more lines are too long
@@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">新增管理员</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" class="form-control" maxlength="10" id="username"
|
||||
placeholder="请输入用户名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>昵称</label>
|
||||
<input type="text" class="form-control" maxlength="10" id="nickname"
|
||||
placeholder="请输入昵称">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>手机号</label>
|
||||
<input type="text" class="form-control" maxlength="11" id="mobile"
|
||||
placeholder="请输入手机号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码</label>
|
||||
<input type="password" class="form-control" maxlength="20" id="password"
|
||||
placeholder="请输入密码">
|
||||
</div>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/authorization/md5.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
$("input#username").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$("input#nickname").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$("input#mobile").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$("input#password").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$('#btnOk').on('click', function () {
|
||||
const username = $("#username").val();
|
||||
if (username === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入用户名。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const nickname = $("#nickname").val();
|
||||
if (nickname === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入昵称。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const password = $("#password").val();
|
||||
if (password === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入密码。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
username: username,
|
||||
nickname: nickname,
|
||||
mobile: $("#mobile").val(),
|
||||
password: md5(password),
|
||||
};
|
||||
|
||||
|
||||
AjaxForm(
|
||||
"POST",
|
||||
"/api/admin",
|
||||
postData,
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
},
|
||||
function (data) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 创建完成。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.href = "/admin/list";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,328 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-toolbar d-flex flex-column flex-md-row">
|
||||
<div class="toolbar-btn-action">
|
||||
<a class="btn btn-primary m-r-5" href="/admin/add"><i class="mdi mdi-plus"></i> 新增</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>用户名</th>
|
||||
<th>昵称</th>
|
||||
<th>手机号</th>
|
||||
<th>创建日期</th>
|
||||
<th>更新日期</th>
|
||||
<th style="text-align: center; ">状态</th>
|
||||
<th style="text-align: center; ">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="tbody">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul class="pagination">
|
||||
<ul class="pagination" id="paginationDiv">
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.pagination.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
// 加载列表页数据
|
||||
getPageListData();
|
||||
|
||||
function getPageListData(page = 0, page_size = 0) {
|
||||
|
||||
if (parseInt(page) < 1) {
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (parseInt(page_size) < 1) {
|
||||
page_size = 10;
|
||||
}
|
||||
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/admin",
|
||||
{page: page, page_size: page_size},
|
||||
function () {},
|
||||
function (data) {
|
||||
if (data.list.length > 0) {
|
||||
var totalNum = data.pagination.total; //总条数
|
||||
var pageNum = Math.ceil(totalNum / data.pagination.pre_page_count); //分页的总页数
|
||||
|
||||
$("#paginationDiv").pagination({
|
||||
current: data.pagination.current_page,
|
||||
pageCount: pageNum,
|
||||
coping: true,
|
||||
homePage: '首页',
|
||||
endPage: '末页',
|
||||
mode: 'fixed',
|
||||
prevContent: '上一页',
|
||||
nextContent: '下一页',
|
||||
activeCls: 'pageActive',
|
||||
prevCls: 'pagePrev',
|
||||
nextCls: 'pageNext',
|
||||
callback: function (api) {
|
||||
$(".tbody").html("");
|
||||
getPageListData(api.getCurrent());
|
||||
}
|
||||
});
|
||||
|
||||
$.each(data.list, function (index, value) {
|
||||
var showUsedBadge = "";
|
||||
var optionUsedName = "";
|
||||
|
||||
if (value.is_used === 1) {
|
||||
optionUsedName = '禁用';
|
||||
showUsedBadge = '<span class="badge badge-success">启用</span></td>'
|
||||
}
|
||||
|
||||
if (value.is_used === -1) {
|
||||
optionUsedName = '启用';
|
||||
showUsedBadge = '<span class="badge badge-danger">禁用</span></td>'
|
||||
}
|
||||
|
||||
const tr = '<tr>\n' +
|
||||
'<td>' + value.id + '</td>\n' +
|
||||
'<td>' + value.username + '</td>\n' +
|
||||
'<td>' + value.nickname + '</td>\n' +
|
||||
'<td>' + value.mobile + '</td>\n' +
|
||||
'<td>' + value.created_at + '</td>\n' +
|
||||
'<td>' + value.updated_at + '</td>\n' +
|
||||
'<td style="text-align: center; ">' + showUsedBadge + '</td>\n' +
|
||||
'<td style="text-align: center; ">\n' +
|
||||
'<div class="btn-group">\n' +
|
||||
' <a class="btn btn-xs btn-default btn-option" href="#!" title=""\n' +
|
||||
' data-id="' + value.hashid + '"' +
|
||||
' data-is-used="' + value.is_used + '"' +
|
||||
' data-toggle="tooltip" data-original-title="' + optionUsedName + '">' + optionUsedName + '</a>\n' +
|
||||
' <a class="btn btn-xs btn-default btn-resetPassword" href="#!" title=""\n' +
|
||||
' data-id="' + value.hashid + '"' +
|
||||
' data-toggle="tooltip" data-original-title="重置密码">重置密码</a>\n' +
|
||||
' <a class="btn btn-xs btn-default btn-confirm" href="#!" title=""\n' +
|
||||
' data-id="' + value.hashid + '"' +
|
||||
' data-toggle="tooltip" data-original-title="删除">删除</a>\n' +
|
||||
'</div>\n' +
|
||||
'</td>\n' +
|
||||
'</tr>';
|
||||
|
||||
$(".tbody").append(tr);
|
||||
})
|
||||
} else {
|
||||
// 数据为空
|
||||
const tr = '<tr><td colspan="8" style="text-align: center">暂无数据</td></tr>';
|
||||
$(".tbody").append(tr);
|
||||
}
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// 启用/禁用
|
||||
$(document).on('click', '.btn-option', function () {
|
||||
const id = $(this).attr('data-id');
|
||||
const isUsed = $(this).attr('data-is-used');
|
||||
|
||||
var tipMessage = "";
|
||||
var wantUsed = 0;
|
||||
if (isUsed === "1") { // 1=当前为启用状态,需要改成禁用
|
||||
tipMessage = "禁用";
|
||||
wantUsed = -1;
|
||||
}
|
||||
if (isUsed === "-1") { // -1=当前为禁用状态,需要改成启用
|
||||
tipMessage = "启用";
|
||||
wantUsed = 1;
|
||||
}
|
||||
|
||||
const patchData = {
|
||||
id: id,
|
||||
used: wantUsed,
|
||||
};
|
||||
|
||||
$.confirm({
|
||||
title: '谨慎操作',
|
||||
content: '确认要 <strong style="color: red">' + tipMessage + '</strong> 吗?',
|
||||
icon: 'mdi mdi-alert',
|
||||
animation: 'scale',
|
||||
closeAnimation: 'zoom',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '确认',
|
||||
keys: ['enter'],
|
||||
btnClass: 'btn-orange',
|
||||
action: function () {
|
||||
AjaxForm(
|
||||
"PATCH",
|
||||
"/api/admin/used",
|
||||
patchData,
|
||||
function () {},
|
||||
function (data) {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 已' + tipMessage + '。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
keys: ['ctrl', 'shift'],
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 重置密码
|
||||
$(document).on('click', '.btn-resetPassword', function () {
|
||||
const id = $(this).attr('data-id');
|
||||
|
||||
$.confirm({
|
||||
title: '谨慎操作',
|
||||
content: '确认要 <strong style="color: red">重置密码</strong> 吗?' + '<hr>' + '重置后密码为:123456',
|
||||
icon: 'mdi mdi-alert',
|
||||
animation: 'scale',
|
||||
closeAnimation: 'zoom',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '确认',
|
||||
keys: ['enter'],
|
||||
btnClass: 'btn-orange',
|
||||
action: function () {
|
||||
AjaxForm(
|
||||
"PATCH",
|
||||
'/api/admin/reset_password/' + id,
|
||||
"",
|
||||
function () {},
|
||||
function (data) {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 密码重置完成。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
keys: ['ctrl', 'shift'],
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 删除
|
||||
$(document).on('click', '.btn-confirm', function () {
|
||||
const id = $(this).attr('data-id');
|
||||
|
||||
$.confirm({
|
||||
title: '谨慎操作',
|
||||
content: '确认要 <strong style="color: red">删除</strong> 吗?',
|
||||
icon: 'mdi mdi-alert',
|
||||
animation: 'scale',
|
||||
closeAnimation: 'zoom',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '确认',
|
||||
keys: ['enter'],
|
||||
btnClass: 'btn-orange',
|
||||
action: function () {
|
||||
AjaxForm(
|
||||
"DELETE",
|
||||
'/api/admin/' + id,
|
||||
"",
|
||||
function () {},
|
||||
function (data) {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 已删除。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
keys: ['ctrl', 'shift'],
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
|
||||
<title>登录页面</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="bootstrap/favicon.ico">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-touch-fullscreen" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="stylesheet" type="text/css" href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="../../bootstrap/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="../../bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="../../bootstrap/css/style.min.css">
|
||||
|
||||
<style>
|
||||
.login-form .has-feedback {
|
||||
position: relative;
|
||||
}
|
||||
.login-form .has-feedback .form-control {
|
||||
padding-left: 36px;
|
||||
}
|
||||
.login-form .has-feedback .mdi {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: auto;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
z-index: 4;
|
||||
color: #dcdcdc;
|
||||
display: block;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.login-form .has-feedback.row .mdi {
|
||||
left: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="center-vh">
|
||||
<div class="card card-shadowed p-5 w-420 mb-0 mr-2 ml-2">
|
||||
<div class="text-center mb-3">
|
||||
<img src="../../bootstrap/images/logo-sidebar.png">
|
||||
</div>
|
||||
<form class="login-form">
|
||||
<div class="form-group has-feedback">
|
||||
<span class="mdi mdi-account" aria-hidden="true"></span>
|
||||
<input type="text" class="form-control" id="username" placeholder="用户名">
|
||||
</div>
|
||||
|
||||
<div class="form-group has-feedback">
|
||||
<span class="mdi mdi-lock" aria-hidden="true"></span>
|
||||
<input type="password" class="form-control" id="password" placeholder="密码">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-block btn-primary" id="btnOk" type="button">立即登录</button>
|
||||
</div>
|
||||
<div class="alert alert-primary" style="text-align: center;" role="alert">
|
||||
默认
|
||||
<span class="mdi mdi-account" aria-hidden="true"></span> admin
|
||||
<span class="mdi mdi-lock" aria-hidden="true"></span> admin
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/authorization/md5.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('#btnOk').on('click', function () {
|
||||
const username = $("#username").val();
|
||||
if (username === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入用户名。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const password = $("#password").val();
|
||||
if (password === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入密码。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
username: username,
|
||||
password: md5(password),
|
||||
};
|
||||
|
||||
AjaxForm(
|
||||
"POST",
|
||||
"/login/web",
|
||||
postData,
|
||||
function () {},
|
||||
function (data) {
|
||||
let date = new Date();
|
||||
date.setTime(date.getTime() + 24 * 60 * 60 * 1000); // 24 * 60 * 60 * 1000 表示 24 小时
|
||||
$.cookie('_login_token_', data.token, {expires: date});
|
||||
|
||||
location.href = "/";
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">个人信息</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<form class="site-form">
|
||||
<div class="form-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" class="form-control" id="username" disabled="disabled">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>昵称</label>
|
||||
<input type="text" class="form-control" maxlength="10" id="nickname" placeholder="输入您的昵称">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>手机号</label>
|
||||
<input type="text" class="form-control" maxlength="11" id="mobile" placeholder="请输入您的手机号">
|
||||
</div>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">保存</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
$("input#nickname, input#mobile").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/admin/info",
|
||||
"",
|
||||
function () {},
|
||||
function (data) {
|
||||
$("#username").val(data.username);
|
||||
$("#nickname").val(data.nickname);
|
||||
$("#mobile").val(data.mobile);
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
|
||||
$('#btnOk').on('click', function () {
|
||||
const nickname = $("#nickname").val();
|
||||
if (nickname === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入您的昵称。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
nickname: nickname,
|
||||
mobile: $("#mobile").val(),
|
||||
};
|
||||
|
||||
AjaxForm(
|
||||
"PATCH",
|
||||
"/api/admin/modify_personal_info",
|
||||
postData,
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
},
|
||||
function () {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '信息修改成功。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">新增管理员</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label>旧密码</label>
|
||||
<input type="password" class="form-control" maxlength="20" id="old_password"
|
||||
placeholder="输入账号的原登录密码">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>新密码</label>
|
||||
<input type="password" class="form-control" maxlength="20" id="new_password"
|
||||
placeholder="输入新的密码">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>确认新密码</label>
|
||||
<input type="password" class="form-control" maxlength="20" id="confirm_password"
|
||||
placeholder="再次输入新的密码">
|
||||
</div>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/authorization/md5.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
$("input#old_password, input#new_password, input#confirm_password").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$('#btnOk').on('click', function () {
|
||||
const old_password = $("#old_password").val();
|
||||
if (old_password === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入账号的原登录密码。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const new_password = $("#new_password").val();
|
||||
if (new_password === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入新的密码。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const confirm_password = $("#confirm_password").val();
|
||||
if (confirm_password === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请再次输入新的密码。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_password !== confirm_password) {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请确认两次密码一致。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
old_password: md5(old_password),
|
||||
new_password: md5(new_password),
|
||||
};
|
||||
|
||||
|
||||
AjaxForm(
|
||||
"PATCH",
|
||||
"/api/admin/modify_password",
|
||||
postData,
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
},
|
||||
function () {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '密码修改成功。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
parent.window.close();
|
||||
window.open("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">新增调用方</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput">调用方</label>
|
||||
<input type="text" class="form-control" maxlength="25" id="business_key"
|
||||
placeholder="请输入调用方标识">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="formGroupExampleInput2">调用方对接人</label>
|
||||
<input type="text" class="form-control" maxlength="50" id="business_developer"
|
||||
placeholder="请输入调用方对接人">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exampleFormControlTextarea1">备注</label>
|
||||
<textarea class="form-control" maxlength="225" rows="3" id="remark"
|
||||
placeholder="备注"></textarea>
|
||||
</div>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
$("input#business_key").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$("input#business_developer").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$("textarea#remark").maxlength({
|
||||
threshold: 255,
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$('#btnOk').on('click', function () {
|
||||
const businessKey = $("#business_key").val();
|
||||
if (businessKey === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入调用方标识。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const businessDeveloper = $("#business_developer").val();
|
||||
if (businessDeveloper === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入调用方对接人。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
business_key: businessKey,
|
||||
business_developer: businessDeveloper,
|
||||
remark: $("#remark").val(),
|
||||
};
|
||||
|
||||
|
||||
AjaxForm(
|
||||
"POST",
|
||||
"/api/authorized",
|
||||
postData,
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
},
|
||||
function (data) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 创建完成。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.href = "/authorized/list";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,259 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/bootstrap-multitabs/multitabs.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">接口授权</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
接口地址支持通配符(*),其中 * 表示 1 级,** 表示 n 级。
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="request_method">选择请求方式</label>
|
||||
</div>
|
||||
<select class="custom-select" id="request_method">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="inputGroup-sizing-default">输入接口地址</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" maxlength="60" id="request_api"
|
||||
placeholder="接口地址">
|
||||
</div>
|
||||
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<div class="card-title">已授权接口</div>
|
||||
</header>
|
||||
<div class="card-body apis">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
const hash_id = {{ .HashID }}
|
||||
|
||||
$("input#request_api").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
// 加载列表页数据
|
||||
getListData();
|
||||
|
||||
function getListData() {
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/authorized_api",
|
||||
{id: hash_id},
|
||||
function () {
|
||||
|
||||
},
|
||||
function (data) {
|
||||
if (data.list.length > 0) {
|
||||
var badgeMethodClass = "";
|
||||
|
||||
$.each(data.list, function (index, value) {
|
||||
if (value.method === "GET") {
|
||||
badgeMethodClass = "badge-primary";
|
||||
} else if (value.method === "POST") {
|
||||
badgeMethodClass = "badge-success";
|
||||
} else if (value.method === "DELETE") {
|
||||
badgeMethodClass = "badge-danger";
|
||||
} else if (value.method === "PUT") {
|
||||
badgeMethodClass = "badge-yellow";
|
||||
} else if (value.method === "PATCH") {
|
||||
badgeMethodClass = "badge-cyan";
|
||||
} else {
|
||||
badgeMethodClass = "badge-dark";
|
||||
}
|
||||
|
||||
const p = '<p>\n' +
|
||||
'<a href="#!" data-id="' + value.hash_id + '" data-api="' + value.api + '" class="del">' +
|
||||
'<span class="badge badge-dark"><i class="mdi mdi-window-close"></i></span>\n' +
|
||||
'</a>\n' +
|
||||
'<span class="badge ' + badgeMethodClass + '">' + value.method + '</span>\n' + value.api
|
||||
;
|
||||
|
||||
$(".apis").append(p);
|
||||
})
|
||||
} else {
|
||||
// 数据为空
|
||||
const p = '<p>暂无授权接口</p>';
|
||||
$(".apis").append(p);
|
||||
}
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
$('#btnOk').on('click', function () {
|
||||
const requestMethod = $("#request_method").val();
|
||||
const requestApi = $("#request_api").val();
|
||||
if (requestMethod === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请选择请求方式。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requestApi === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入请求地址。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
method: requestMethod,
|
||||
api: requestApi,
|
||||
id: hash_id,
|
||||
};
|
||||
|
||||
AjaxForm(
|
||||
"POST",
|
||||
"/api/authorized_api",
|
||||
postData,
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
},
|
||||
function () {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '接口:' + requestApi + ' 授权完成。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$(document).on('click', '.del', function () {
|
||||
const id = $(this).attr('data-id');
|
||||
const api = $(this).attr('data-api');
|
||||
|
||||
$.confirm({
|
||||
title: '谨慎操作',
|
||||
content: '确认要 <strong style="color: red">取消授权</strong> 吗?',
|
||||
icon: 'mdi mdi-alert',
|
||||
animation: 'scale',
|
||||
closeAnimation: 'zoom',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '确认',
|
||||
keys: ['enter'],
|
||||
btnClass: 'btn-orange',
|
||||
action: function () {
|
||||
AjaxForm(
|
||||
"DELETE",
|
||||
'/api/authorized_api/' + id,
|
||||
"",
|
||||
function () {
|
||||
|
||||
},
|
||||
function () {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '接口:' + api + ' 已取消授权。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
keys: ['ctrl', 'shift'],
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<div class="card-title">如何给调用方开通 KEY 和 SECRET?</div>
|
||||
</header>
|
||||
|
||||
<div class="card-body">
|
||||
<p>1. 新增调用方,输入调用方标识、调用方对接人、备注等信息;</p>
|
||||
<p>2. 授权调用方可调用的接口;</p>
|
||||
<p>3. 查看详情,将调用方的 <code>KEY</code>、<code>SECRET</code> 发给调用方;</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<div class="card-title">调用方如何传递 Token?</div>
|
||||
</header>
|
||||
|
||||
<div class="card-body">
|
||||
<p>基于 HTTP Header 中的两个参数 <code>Authorization</code>、<code>Authorization-Date</code> 存储签名信息。</p>
|
||||
<p>1. Authorization 存储签名信息,格式:调用方 KEY + 空格分隔符 + 摘要(加密串),例如:</p>
|
||||
<pre>Authorization:blog MjJjMDE1MWFkZjMwOWFmYjFlNzViNDFjYjYwMWFlMmM=</pre>
|
||||
<p>2. Authorization-Date 存储时间信息,格式:0000-00-00 00:00:00,使用 <code>Asia/Shanghai</code> 时区,例如;</p>
|
||||
<pre>Authorization-Date:2021-04-03 21:12:36</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title">不同语言生成签名的方法,供参考</div></header>
|
||||
<div class="card-body">
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#go" aria-selected="true">Go 语言</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#php" aria-selected="false">PHP 语言</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#js" aria-selected="false">JS 语言</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade active show" id="go">
|
||||
<pre>
|
||||
func New(key, secret string, ttl time.Duration) Signature {
|
||||
return &signature{
|
||||
key: key,
|
||||
secret: secret,
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate
|
||||
// path 请求的路径 (不附带 querystring)
|
||||
func (s *signature) Generate(path string, method string, params url.Values) (authorization, date string, err error) {
|
||||
if path == "" {
|
||||
err = errors.New("path required")
|
||||
return
|
||||
}
|
||||
|
||||
if method == "" {
|
||||
err = errors.New("method required")
|
||||
return
|
||||
}
|
||||
|
||||
methodName := strings.ToUpper(method)
|
||||
if !methods[methodName] {
|
||||
err = errors.New("method param error")
|
||||
return
|
||||
}
|
||||
|
||||
// Date
|
||||
date = time_parse.CSTLayoutString()
|
||||
|
||||
// Encode() 方法中自带 sorted by key
|
||||
sortParamsEncode, err := url.QueryUnescape(params.Encode())
|
||||
if err != nil {
|
||||
err = errors.Errorf("url QueryUnescape %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 加密字符串规则
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString(path)
|
||||
buffer.WriteString(delimiter)
|
||||
buffer.WriteString(methodName)
|
||||
buffer.WriteString(delimiter)
|
||||
buffer.WriteString(sortParamsEncode)
|
||||
buffer.WriteString(delimiter)
|
||||
buffer.WriteString(date)
|
||||
|
||||
// 对数据进行 sha256 加密,并进行 base64 encode
|
||||
hash := hmac.New(sha256.New, []byte(s.secret))
|
||||
hash.Write(buffer.Bytes())
|
||||
digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||
|
||||
authorization = fmt.Sprintf("%s %s", s.key, digest)
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟数据
|
||||
const (
|
||||
key = "blog"
|
||||
secret = "i1ydX9RtHyuJTrw7frcu"
|
||||
ttl = time.Minute * 10
|
||||
)
|
||||
|
||||
func TestSignature_Generate(t *testing.T) {
|
||||
path := "/echo"
|
||||
method := "POST"
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("a", "a1")
|
||||
params.Add("d", "d1")
|
||||
params.Add("c", "c1 c2*")
|
||||
|
||||
authorization, date, err := New(key, secret, ttl).Generate(path, method, params)
|
||||
t.Log("authorization:", authorization)
|
||||
t.Log("authorization-date:", date)
|
||||
t.Log("err:", err)
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="php">
|
||||
<pre>
|
||||
// 模拟数据
|
||||
$key = "blog";
|
||||
$secret = "i1ydX9RtHyuJTrw7frcu";
|
||||
|
||||
$path = "/echo";
|
||||
$method = "POST";
|
||||
|
||||
$params['a'] = "a1";
|
||||
$params['d'] = "d1";
|
||||
$params['c'] = "c1 c2*";
|
||||
|
||||
// 对 params key 进行排序
|
||||
ksort($params);
|
||||
|
||||
// 对 sortParams 进行操作
|
||||
$sortParamsEncode = rawurldecode(http_build_query($params, "", "&", PHP_QUERY_RFC3986));
|
||||
|
||||
// 时间 使用 Asia/Shanghai 时区
|
||||
$date = date("Y-m-d H:i:s", time());
|
||||
|
||||
// 加密字符串规则
|
||||
$encryptStr = $path."|".strtoupper($method)."|".$sortParamsEncode."|".$date;
|
||||
|
||||
// 对数据进行 sha256 加密,并进行 base64 encode
|
||||
$digest = base64_encode(hash_hmac("sha256", $encryptStr, $secret, true));
|
||||
|
||||
$authorization = $key." ".$digest;
|
||||
|
||||
echo "authorization:{$authorization}";
|
||||
echo "---";
|
||||
echo "authorization-date:{$date}";
|
||||
</pre>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="js">
|
||||
<pre>
|
||||
let key = "blog";
|
||||
let secret = "i1ydX9RtHyuJTrw7frcu";
|
||||
|
||||
let date = new Date();
|
||||
let datetime = date.getFullYear() + "-" // "年"
|
||||
+ ((date.getMonth() + 1) > 10 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1)) + "-" // "月"
|
||||
+ (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " " // "日"
|
||||
+ (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":" // "小时"
|
||||
+ (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":" // "分钟"
|
||||
+ (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds()); // "秒"
|
||||
|
||||
let path = "/echo";
|
||||
let method = "POST";
|
||||
let params = {a:'a1', d:'d1', c: 'c1 c2*'};
|
||||
|
||||
let sortParamsEncode = decodeURIComponent(jQuery.param(ksort(params)));
|
||||
let encryptStr = path + "|" + method.toUpperCase() + "|" + sortParamsEncode + "|" + datetime;
|
||||
let digest = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(encryptStr, secret));
|
||||
console.log({authorization: key + " " + digest, date: datetime});
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/main.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,294 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-toolbar d-flex flex-column flex-md-row">
|
||||
<div class="toolbar-btn-action">
|
||||
<a class="btn btn-primary m-r-5" href="/authorized/add"><i class="mdi mdi-plus"></i> 新增</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>编号</th>
|
||||
<th>调用方</th>
|
||||
<th>对接人</th>
|
||||
<th>创建日期</th>
|
||||
<th>更新日期</th>
|
||||
<th style="text-align: center; ">状态</th>
|
||||
<th style="text-align: center; ">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="tbody">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul class="pagination">
|
||||
<ul class="pagination" id="paginationDiv">
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.pagination.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
// 加载列表页数据
|
||||
getPageListData();
|
||||
|
||||
function getPageListData(page = 0, page_size = 0) {
|
||||
|
||||
if (parseInt(page) < 1) {
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (parseInt(page_size) < 1) {
|
||||
page_size = 10;
|
||||
}
|
||||
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/authorized",
|
||||
{page: page, page_size: page_size},
|
||||
function () {},
|
||||
function (data) {
|
||||
if (data.list.length > 0) {
|
||||
var totalNum = data.pagination.total; //总条数
|
||||
var pageNum = Math.ceil(totalNum / data.pagination.pre_page_count); //分页的总页数
|
||||
|
||||
$("#paginationDiv").pagination({
|
||||
current: data.pagination.current_page,
|
||||
pageCount: pageNum,
|
||||
coping: true,
|
||||
homePage: '首页',
|
||||
endPage: '末页',
|
||||
mode: 'fixed',
|
||||
prevContent: '上一页',
|
||||
nextContent: '下一页',
|
||||
activeCls: 'pageActive',
|
||||
prevCls: 'pagePrev',
|
||||
nextCls: 'pageNext',
|
||||
callback: function (api) {
|
||||
$(".tbody").html("");
|
||||
getPageListData(api.getCurrent());
|
||||
}
|
||||
});
|
||||
|
||||
$.each(data.list, function (index, value) {
|
||||
var showUsedBadge = "";
|
||||
var optionUsedName = "";
|
||||
|
||||
if (value.is_used === 1) {
|
||||
optionUsedName = '禁用';
|
||||
showUsedBadge = '<span class="badge badge-success">启用</span></td>'
|
||||
}
|
||||
|
||||
if (value.is_used === -1) {
|
||||
optionUsedName = '启用';
|
||||
showUsedBadge = '<span class="badge badge-danger">禁用</span></td>'
|
||||
}
|
||||
|
||||
const tr = '<tr>\n' +
|
||||
'<td>' + value.id + '</td>\n' +
|
||||
'<td>' + value.business_key + '</td>\n' +
|
||||
'<td>' + value.business_developer + '</td>\n' +
|
||||
'<td>' + value.created_at + '</td>\n' +
|
||||
'<td>' + value.updated_at + '</td>\n' +
|
||||
'<td style="text-align: center; ">' + showUsedBadge + '</td>\n' +
|
||||
'<td style="text-align: center; ">\n' +
|
||||
'<div class="btn-group">\n' +
|
||||
' <a class="btn btn-xs btn-default btn-detail" href="#!" title=""\n' +
|
||||
' data-business-key="' + value.business_key + '"' +
|
||||
' data-business-secret="' + value.business_secret + '"' +
|
||||
' data-remark="' + value.remark + '"' +
|
||||
' data-toggle="tooltip" data-original-title="详情">详情</a>\n' +
|
||||
' <a class="btn btn-xs btn-default btn-option" href="#!" title=""\n' +
|
||||
' data-id="' + value.hashid + '"' +
|
||||
' data-is-used="' + value.is_used + '"' +
|
||||
' data-toggle="tooltip" data-original-title="' + optionUsedName + '">' + optionUsedName + '</a>\n' +
|
||||
' <a class="btn btn-xs btn-default" href="/authorized/api/'+value.hashid+'" title=""\n' +
|
||||
' data-toggle="tooltip" data-original-title="接口">接口</a>\n' +
|
||||
' <a class="btn btn-xs btn-default btn-confirm" href="#!" title=""\n' +
|
||||
' data-id="' + value.hashid + '"' +
|
||||
' data-toggle="tooltip" data-original-title="删除">删除</a>\n' +
|
||||
'</div>\n' +
|
||||
'</td>\n' +
|
||||
'</tr>';
|
||||
|
||||
$(".tbody").append(tr);
|
||||
})
|
||||
} else {
|
||||
// 数据为空
|
||||
const tr = '<tr><td colspan="7" style="text-align: center">暂无数据</td></tr>';
|
||||
$(".tbody").append(tr);
|
||||
}
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// 详情
|
||||
$(document).on('click', '.btn-detail', function () {
|
||||
const business_key = $(this).attr('data-business-key');
|
||||
const business_secret = $(this).attr('data-business-secret');
|
||||
const business_remark = $(this).attr('data-remark');
|
||||
|
||||
$.alert({
|
||||
title: '详情',
|
||||
content: '调用方 KEY :' + business_key + '<br/>调用方 SECRET :' + business_secret + '<br/>备注:' + business_remark,
|
||||
type: 'green',
|
||||
animation: 'scale',
|
||||
draggable: true,
|
||||
});
|
||||
});
|
||||
|
||||
// 启用/禁用
|
||||
$(document).on('click', '.btn-option', function () {
|
||||
const id = $(this).attr('data-id');
|
||||
const isUsed = $(this).attr('data-is-used');
|
||||
|
||||
var tipMessage = "";
|
||||
var wantUsed = 0;
|
||||
if (isUsed === "1") { // 1=当前为启用状态,需要改成禁用
|
||||
tipMessage = "禁用";
|
||||
wantUsed = -1;
|
||||
}
|
||||
if (isUsed === "-1") { // -1=当前为禁用状态,需要改成启用
|
||||
tipMessage = "启用";
|
||||
wantUsed = 1;
|
||||
}
|
||||
|
||||
const patchData = {
|
||||
id: id,
|
||||
used: wantUsed,
|
||||
};
|
||||
|
||||
$.confirm({
|
||||
title: '谨慎操作',
|
||||
content: '确认要 <strong style="color: red">' + tipMessage + '</strong> 吗?',
|
||||
icon: 'mdi mdi-alert',
|
||||
animation: 'scale',
|
||||
closeAnimation: 'zoom',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '确认',
|
||||
keys: ['enter'],
|
||||
btnClass: 'btn-orange',
|
||||
action: function () {
|
||||
AjaxForm(
|
||||
"PATCH",
|
||||
"/api/authorized/used",
|
||||
patchData,
|
||||
function () {},
|
||||
function (data) {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 已' + tipMessage + '。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
keys: ['ctrl', 'shift'],
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 删除
|
||||
$(document).on('click', '.btn-confirm', function () {
|
||||
const id = $(this).attr('data-id');
|
||||
|
||||
$.confirm({
|
||||
title: '谨慎操作',
|
||||
content: '确认要 <strong style="color: red">删除</strong> 吗?',
|
||||
icon: 'mdi mdi-alert',
|
||||
animation: 'scale',
|
||||
closeAnimation: 'zoom',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '确认',
|
||||
keys: ['enter'],
|
||||
btnClass: 'btn-orange',
|
||||
action: function () {
|
||||
AjaxForm(
|
||||
"DELETE",
|
||||
'/api/authorized/' + id,
|
||||
"",
|
||||
function () {},
|
||||
function (data) {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '编号:' + data.id + ' 已删除。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '关闭',
|
||||
action: function () {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: '取消',
|
||||
keys: ['ctrl', 'shift'],
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title"> 配置信息</div></header>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">MySQL</h5>
|
||||
<p>主库信息:{{.MySQL.Write.Addr}},账号:{{.MySQL.Write.User}},数据库:{{.MySQL.Write.Name}}</p>
|
||||
<p>从库信息:{{.MySQL.Read.Addr}},账号:{{.MySQL.Read.User}},数据库:{{.MySQL.Read.Name}}</p>
|
||||
<p>最大连接数:{{ .MySQL.Base.MaxOpenConn }}</p>
|
||||
<p>空闲连接数:{{ .MySQL.Base.MaxIdleConn }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Redis</h5>
|
||||
<p>地址:{{ .Redis.Addr }} </p>
|
||||
<p>最大连接数:{{ .Redis.PoolSize }} </p>
|
||||
<p>空闲连接数:{{ .Redis.MinIdleConns }} </p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Mail</h5>
|
||||
<p>邮箱服务器:{{ .Mail.Host }} </p>
|
||||
<p>发件人邮箱地址:{{ .Mail.User }} </p>
|
||||
<p>收件人邮箱地址:{{ .Mail.To }} </p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card border-secondary">
|
||||
<header class="card-header">
|
||||
<div class="card-title">内存信息</div>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<p>总量:{{ .MemTotal }}</p>
|
||||
<p>已使用:{{ .MemUsed }}</p>
|
||||
<p>使用率:<span class="badge
|
||||
{{if gt .MemUsedPercent 95.0}} badge-danger
|
||||
{{else if gt .MemUsedPercent 90.0 }} badge-warning
|
||||
{{else}} badge-success
|
||||
{{end}} ">{{ .MemUsedPercent }}%</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card border-secondary">
|
||||
<header class="card-header">
|
||||
<div class="card-title">硬盘信息</div>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<p>总量:{{ .DiskTotal }}</p>
|
||||
<p>已使用:{{ .DiskUsed }}</p>
|
||||
<p>使用率:<span class="badge
|
||||
{{if gt .DiskUsedPercent 95.0}} badge-danger
|
||||
{{else if gt .DiskUsedPercent 90.0 }} badge-warning
|
||||
{{else}} badge-success
|
||||
{{end}}">{{ .DiskUsedPercent }}%</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card border-secondary">
|
||||
<header class="card-header">
|
||||
<div class="card-title">CPU 信息</div>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<p>CPU:{{ .CpuName }}</p>
|
||||
<p>核数:{{ .CpuCores }}</p>
|
||||
<p>CPU 使用率:<span class="badge
|
||||
{{if gt .CpuUsedPercent 95.0}} badge-danger
|
||||
{{else if gt .CpuUsedPercent 90.0 }} badge-warning
|
||||
{{else}} badge-success
|
||||
{{end}}">{{ .CpuUsedPercent }}%</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card border-secondary">
|
||||
<header class="card-header">
|
||||
<div class="card-title">项目信息</div>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<p>操作系统:{{ .GoOS }} <span class="badge badge-brown"> {{ .GoArch }} </span></p>
|
||||
<p>项目地址:{{ .ProjectPath }}</p>
|
||||
<p>项目域名:{{ .Host }} <span class="badge badge-secondary"> {{ .Env }} </span></p>
|
||||
<p>GOPATH:{{ .GoPath }}</p>
|
||||
<p>Version:{{ .GoVersion }}</p>
|
||||
<p>Goroutine:{{ .Goroutine }}</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title"> <code>gormgen</code> 代码生成工具</div></header>
|
||||
<div class="card-body">
|
||||
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code> 或 <code>Linux</code> 环境执行。</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tableSelect">选择数据表,可进行多选:</label>
|
||||
<select multiple="" class="form-control" id="tableSelect" style="height: 260px;">
|
||||
{{range .}}
|
||||
<option value="{{ .Name }}">{{ .Name }} -- 备注:{{ .Comment }}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title">执行结果</div></header>
|
||||
<div class="card-body">
|
||||
<pre id="resultDiv"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("#btnOk").click(function(){
|
||||
var tables = [];
|
||||
var options = $("#tableSelect").find("option:selected");
|
||||
|
||||
if (options.length < 1) {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请选择数据表。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#resultDiv").text("");
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
tables.push(options.eq(i).val()); // 将所有的值赋给数组
|
||||
}
|
||||
|
||||
$.post("/gormgen_exec",{tables:tables.join(',')},function (data) {
|
||||
$("#resultDiv").text(data);
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title"> <code>handlergen</code> 代码生成工具</div></header>
|
||||
<div class="card-body">
|
||||
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code> 或 <code>Linux</code> 环境执行。</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="exampleFormControlInput1">handler 名称,例如:<code>user_handler</code></label>
|
||||
<input type="text" class="form-control" id="handlerName" placeholder="请输入 handler 名称">
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title">执行结果</div></header>
|
||||
<div class="card-body">
|
||||
<pre id="resultDiv"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("#btnOk").click(function(){
|
||||
var handlerName = $("#handlerName").val();
|
||||
if (!handlerName) {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入 handler 名称。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#resultDiv").text("");
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
|
||||
$.post("/handlergen_exec",{name:handlerName},function (data) {
|
||||
$("#resultDiv").text(data);
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<link href="bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title">初始化</div></header>
|
||||
<div class="card-body">
|
||||
<div class="callout callout-warning mb-3">注意:请在 <code>Mac</code> 或 <code>Linux</code> 环境执行。</div>
|
||||
<p>初始化项目所需信息:</p>
|
||||
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>user_demo</code> ;</span></p>
|
||||
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>authorized</code> ;</span></p>
|
||||
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>authorized_api</code> ;</span></p>
|
||||
<p><i class="mdi mdi-checkbox-marked-circle"></i> <span>MySQL 数据表:<code>admin</code> ;</span></p>
|
||||
<p>
|
||||
<button type="button" id="btnOk" class="btn btn-primary">确认</button>
|
||||
<button type="button" id="btnLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header"><div class="card-title">执行结果</div></header>
|
||||
<div class="card-body">
|
||||
<pre id="resultDiv"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("#btnOk").click(function(){
|
||||
|
||||
$("#resultDiv").text("");
|
||||
$(this).hide();
|
||||
$("#btnLoading").show();
|
||||
|
||||
$.post("/init_exec","",function (data) {
|
||||
$("#resultDiv").text(data);
|
||||
$("#btnLoading").hide();
|
||||
$("#btnOk").show();
|
||||
|
||||
if (getQueryString("init")) {
|
||||
$.alert({
|
||||
title: '操作成功',
|
||||
icon: 'mdi mdi-check-decagram',
|
||||
type: 'green',
|
||||
content: '初始化完成。',
|
||||
buttons: {
|
||||
okay: {
|
||||
text: '系统首页',
|
||||
action: function () {
|
||||
location.href = "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
function getQueryString(name) {
|
||||
let reg = new RegExp('(?:(?:&|\\?)' + name + '=([^&]*))|(?:/' + name + '/([^/]*))', 'i');
|
||||
let r = window.location.href.match(reg);
|
||||
if (r != null)
|
||||
return decodeURI(r[1] || r[2]);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,299 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimal-ui">
|
||||
<title>管理面板</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="bootstrap/favicon.ico">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-touch-fullscreen" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap/js/jquery-confirm/jquery-confirm.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap/js/bootstrap-multitabs/multitabs.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap/css/animate.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="bootstrap/css/style.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="lyear-layout-web">
|
||||
<div class="lyear-layout-container">
|
||||
<!--左侧导航-->
|
||||
<aside class="lyear-layout-sidebar">
|
||||
|
||||
<!-- logo -->
|
||||
<div id="logo" class="sidebar-header">
|
||||
<a href="/">
|
||||
<img src="bootstrap/images/logo-sidebar.png"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="lyear-layout-sidebar-info lyear-scroll">
|
||||
|
||||
<nav class="sidebar-main">
|
||||
<ul class="nav-drawer">
|
||||
<li class="nav-item active"> <a class="multitabs" href="/dashboard"><i class="mdi mdi-home"></i> <span>仪表盘</span></a> </li>
|
||||
<li class="nav-item"> <a class="multitabs" href="/configinfo"><i class="mdi mdi-settings-box"></i> <span>配置信息</span></a> </li>
|
||||
|
||||
<li class="nav-item nav-item-has-subnav">
|
||||
<a href="javascript:void(0)"><i class="mdi mdi-code-not-equal-variant"></i> <span>代码生成器</span></a>
|
||||
<ul class="nav nav-subnav">
|
||||
<li> <a class="multitabs" href="/init">初始化</a> </li>
|
||||
<li> <a class="multitabs" href="/gormgen">gormgen</a> </li>
|
||||
<li> <a class="multitabs" href="/handlergen">handlergen</a> </li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-has-subnav">
|
||||
<a href="javascript:void(0)"><i class="mdi mdi-playlist-check"></i> <span>授权调用方</span></a>
|
||||
<ul class="nav nav-subnav">
|
||||
<li> <a class="multitabs" href="/authorized/list">调用方</a> </li>
|
||||
<li> <a class="multitabs" href="/authorized/demo">使用说明</a> </li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-has-subnav">
|
||||
<a href="javascript:void(0)"><i class="mdi mdi-account"></i> <span>系统管理员</span></a>
|
||||
<ul class="nav nav-subnav">
|
||||
<li> <a class="multitabs" href="/admin/list">管理员</a> </li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-item-has-subnav">
|
||||
<a href="javascript:void(0)"><i class="mdi mdi-tools"></i> <span>实用工具箱</span></a>
|
||||
<ul class="nav nav-subnav">
|
||||
<li> <a class="multitabs" href="/tool/hashids">Hashids</a> </li>
|
||||
<li> <a class="multitabs" href="/tool/logs">调用日志</a> </li>
|
||||
<li> <a target="_blank" href="/swagger/index.html">接口文档</a> </li>
|
||||
<li> <a target="_blank" href="/graphql">GraphQL</a> </li>
|
||||
<li> <a target="_blank" href="/metrics">接口指标</a> </li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
<!--End 左侧导航-->
|
||||
|
||||
<!--头部信息-->
|
||||
<header class="lyear-layout-header">
|
||||
|
||||
<nav class="navbar">
|
||||
|
||||
<div class="navbar-left">
|
||||
<div class="lyear-aside-toggler">
|
||||
<span class="lyear-toggler-bar"></span>
|
||||
<span class="lyear-toggler-bar"></span>
|
||||
<span class="lyear-toggler-bar"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="navbar-right d-flex align-items-center">
|
||||
|
||||
<!--切换主题配色-->
|
||||
<li class="dropdown dropdown-skin">
|
||||
<span data-toggle="dropdown" class="icon-item"><i class="mdi mdi-palette"></i></span>
|
||||
<ul class="dropdown-menu dropdown-menu-right" data-stopPropagation="true">
|
||||
<li class="drop-title"><p>LOGO</p></li>
|
||||
<li class="drop-skin-li clearfix">
|
||||
<span class="inverse">
|
||||
<input type="radio" name="logo_bg" value="default" id="logo_bg_1" checked>
|
||||
<label for="logo_bg_1"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_2" id="logo_bg_2">
|
||||
<label for="logo_bg_2"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_3" id="logo_bg_3">
|
||||
<label for="logo_bg_3"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_4" id="logo_bg_4">
|
||||
<label for="logo_bg_4"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_5" id="logo_bg_5">
|
||||
<label for="logo_bg_5"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_6" id="logo_bg_6">
|
||||
<label for="logo_bg_6"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_7" id="logo_bg_7">
|
||||
<label for="logo_bg_7"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="logo_bg" value="color_8" id="logo_bg_8">
|
||||
<label for="logo_bg_8"></label>
|
||||
</span>
|
||||
</li>
|
||||
<li class="drop-title"><p>头部</p></li>
|
||||
<li class="drop-skin-li clearfix">
|
||||
<span class="inverse">
|
||||
<input type="radio" name="header_bg" value="default" id="header_bg_1" checked>
|
||||
<label for="header_bg_1"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_2" id="header_bg_2">
|
||||
<label for="header_bg_2"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_3" id="header_bg_3">
|
||||
<label for="header_bg_3"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_4" id="header_bg_4">
|
||||
<label for="header_bg_4"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_5" id="header_bg_5">
|
||||
<label for="header_bg_5"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_6" id="header_bg_6">
|
||||
<label for="header_bg_6"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_7" id="header_bg_7">
|
||||
<label for="header_bg_7"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="header_bg" value="color_8" id="header_bg_8">
|
||||
<label for="header_bg_8"></label>
|
||||
</span>
|
||||
</li>
|
||||
<li class="drop-title"><p>侧边栏</p></li>
|
||||
<li class="drop-skin-li clearfix">
|
||||
<span class="inverse">
|
||||
<input type="radio" name="sidebar_bg" value="default" id="sidebar_bg_1" checked>
|
||||
<label for="sidebar_bg_1"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_2" id="sidebar_bg_2">
|
||||
<label for="sidebar_bg_2"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_3" id="sidebar_bg_3">
|
||||
<label for="sidebar_bg_3"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_4" id="sidebar_bg_4">
|
||||
<label for="sidebar_bg_4"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_5" id="sidebar_bg_5">
|
||||
<label for="sidebar_bg_5"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_6" id="sidebar_bg_6">
|
||||
<label for="sidebar_bg_6"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_7" id="sidebar_bg_7">
|
||||
<label for="sidebar_bg_7"></label>
|
||||
</span>
|
||||
<span>
|
||||
<input type="radio" name="sidebar_bg" value="color_8" id="sidebar_bg_8">
|
||||
<label for="sidebar_bg_8"></label>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<!--切换主题配色-->
|
||||
|
||||
<li class="dropdown dropdown-profile">
|
||||
<a href="javascript:void(0)" data-toggle="dropdown" class="dropdown-toggle">
|
||||
<img class="img-avatar img-avatar-48 m-r-10" src="bootstrap/images/users/avatar.png">
|
||||
<span id="nickname"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a class="multitabs dropdown-item" data-url="/admin/modify_info" href="javascript:void(0)">
|
||||
<i class="mdi mdi-account"></i> 个人信息
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="multitabs dropdown-item" data-url="/admin/modify_password" href="javascript:void(0)">
|
||||
<i class="mdi mdi-lock-outline"></i> 修改密码
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="javascript:void(0)" id="logout">
|
||||
<i class="mdi mdi-logout-variant"></i> 退出登录
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</header>
|
||||
<!--End 头部信息-->
|
||||
|
||||
<!--页面主要内容-->
|
||||
<main class="lyear-layout-content">
|
||||
|
||||
<div id="iframe-content"></div>
|
||||
|
||||
</main>
|
||||
<!--End 页面主要内容-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/popper.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/perfect-scrollbar.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap-multitabs/multitabs.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/jquery.cookie.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/index.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/admin/info",
|
||||
"",
|
||||
function () {},
|
||||
function (data) {
|
||||
$("#nickname").html(data.nickname);
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
|
||||
$("#logout").on('click', function () {
|
||||
AjaxForm(
|
||||
"POST",
|
||||
"/api/admin/logout",
|
||||
"",
|
||||
function () {},
|
||||
function () {
|
||||
// 清空 cookie
|
||||
$.cookie('_nav_url_', '');
|
||||
$.cookie('_nav_title_', '');
|
||||
$.cookie('_login_token_', '');
|
||||
|
||||
parent.window.close();
|
||||
window.open("/login");
|
||||
},
|
||||
function (response) {
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/js/jquery-confirm/jquery-confirm.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">hashids 加密</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">数字</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="number" maxlength="10" placeholder="需加密的数字">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" >密文</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" disabled id="NumberToEncodeValue">
|
||||
</div>
|
||||
|
||||
<button type="button" id="btnEncode" class="btn btn-primary">执行</button>
|
||||
<button type="button" id="btnEncodeLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<div class="card-title">hashids 解密</div>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">密文</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="encodeValue" placeholder="需解密的密文">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" >数字</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" disabled id="DecodeValueToNumber">
|
||||
</div>
|
||||
|
||||
<button type="button" id="btnDecode" class="btn btn-primary">执行</button>
|
||||
<button type="button" id="btnDecodeLoading" class="btn btn-primary" disabled style="display: none">
|
||||
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
|
||||
执行中...
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap-maxlength/bootstrap-maxlength.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery-confirm/jquery-confirm.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/httpclient/httpclient.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
$("input#number").maxlength({
|
||||
warningClass: "badge badge-info",
|
||||
limitReachedClass: "badge badge-warning"
|
||||
});
|
||||
|
||||
$('#btnEncode').on('click', function () {
|
||||
const number = $("#number").val();
|
||||
if (number === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入需加密的数字。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/tool/hashids/encode/" + number,
|
||||
"",
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnEncodeLoading").show();
|
||||
},
|
||||
function (data) {
|
||||
$("#btnEncodeLoading").hide();
|
||||
$("#btnEncode").show();
|
||||
$("#NumberToEncodeValue").val(data.val)
|
||||
},
|
||||
function (response) {
|
||||
$("#btnEncodeLoading").hide();
|
||||
$("#btnEncode").show();
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('#btnDecode').on('click', function () {
|
||||
const encodeValue = $("#encodeValue").val();
|
||||
if (encodeValue === "") {
|
||||
$.alert({
|
||||
title: '温馨提示',
|
||||
icon: 'mdi mdi-alert',
|
||||
type: 'orange',
|
||||
content: '请输入需解密的密文。',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
AjaxForm(
|
||||
"GET",
|
||||
"/api/tool/hashids/decode/" + encodeValue,
|
||||
"",
|
||||
function () {
|
||||
$(this).hide();
|
||||
$("#btnDecodeLoading").show();
|
||||
},
|
||||
function (data) {
|
||||
$("#btnDecodeLoading").hide();
|
||||
$("#btnDecode").show();
|
||||
$("#DecodeValueToNumber").val(data.val)
|
||||
},
|
||||
function (response) {
|
||||
$("#btnDecodeLoading").hide();
|
||||
$("#btnDecode").show();
|
||||
AjaxError(response);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link href="../../bootstrap/css/style.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid p-t-15">
|
||||
|
||||
<div class="alert alert-info alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>推荐使用: <code>ELK 组件</code> ,本功能仅仅是读取文本进行展示。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<div class="card-title">日志列表 <code>仅展示最新的 100 条日志。</code></div>
|
||||
<ul class="card-actions">
|
||||
<li>
|
||||
<a href="#!" onclick="location.reload();" data-toggle="tooltip" title="" data-original-title="刷新"><i class="mdi mdi-refresh"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<div class="accordion">
|
||||
{{range $key, $value := .Logs}}
|
||||
{{$badgeLevelClass := ""}}
|
||||
{{$badgeMethodClass := ""}}
|
||||
{{$badgeCodeClass := ""}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
{{if eq $value.Level "info"}}
|
||||
{{$badgeLevelClass = "badge-info"}}
|
||||
{{else if eq $value.Level "error"}}
|
||||
{{$badgeLevelClass = "badge-danger"}}
|
||||
{{else if eq $value.Level "warn"}}
|
||||
{{$badgeLevelClass = "badge-warning"}}
|
||||
{{else}}
|
||||
{{$badgeLevelClass = "badge-dark"}}
|
||||
{{end}}
|
||||
|
||||
{{if eq $value.Method "GET"}}
|
||||
{{$badgeMethodClass = "badge-primary"}}
|
||||
{{else if eq $value.Method "POST"}}
|
||||
{{$badgeMethodClass = "badge-success"}}
|
||||
{{else if eq $value.Method "DELETE"}}
|
||||
{{$badgeMethodClass = "badge-danger"}}
|
||||
{{else if eq $value.Method "PUT"}}
|
||||
{{$badgeMethodClass = "badge-yellow"}}
|
||||
{{else if eq $value.Method "PATCH"}}
|
||||
{{$badgeMethodClass = "badge-cyan"}}
|
||||
{{else}}
|
||||
{{$badgeMethodClass = "badge-dark"}}
|
||||
{{end}}
|
||||
|
||||
{{if eq $value.HTTPCode 200}}
|
||||
{{$badgeCodeClass = "badge-success"}}
|
||||
{{else}}
|
||||
{{$badgeCodeClass = "badge-dark"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{if eq $value.HTTPCode 0}}
|
||||
<a data-toggle="collapse" data-target="#collapse{{$key}}" aria-expanded="false" href="#!" class="collapsed">
|
||||
<span class="badge {{$badgeLevelClass}}">{{$value.Level}}</span>
|
||||
<span class="badge badge-brown">{{$value.Time}}</span>
|
||||
<code>{{$value.Msg}}</code>
|
||||
</a>
|
||||
{{else}}
|
||||
<a data-toggle="collapse" data-target="#collapse{{$key}}" aria-expanded="false" href="#!" class="collapsed">
|
||||
<span class="badge {{$badgeLevelClass}}">{{$value.Level}}</span>
|
||||
<span class="badge badge-brown">{{$value.Time}}</span>
|
||||
<span class="badge {{$badgeMethodClass}}">{{$value.Method}}</span>
|
||||
<span class="badge {{$badgeCodeClass}}">{{$value.HTTPCode}}</span>
|
||||
<span class="badge badge-muted">{{$value.TraceID}}</span>
|
||||
<span class="badge badge-purple-light">{{$value.CostSeconds}} s</span>
|
||||
<code>{{$value.Path}}</code>
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="collapse{{$key}}" class="collapse">
|
||||
<div class="card-body">
|
||||
<pre>{{$value.Content}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../../bootstrap/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/popper.min.js"></script>
|
||||
<script type="text/javascript" src="../../bootstrap/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
## 执行命令
|
||||
在根目录下执行脚本:`./scripts/gormgen.sh addr user pass name tables`;
|
||||
- addr:数据库地址,例如:127.0.0.1:3306
|
||||
- user:账号,例如:root
|
||||
- pass:密码,例如:root
|
||||
- name:数据库名称,例如:go_gin_api
|
||||
- tables:表名,默认为 *,多个表名可用“,”分割,例如:user_demo
|
||||
|
||||
例如:
|
||||
```
|
||||
./scripts/gormgen.sh 127.0.0.1:3306 root root go_gin_api user_demo
|
||||
```
|
||||
|
||||
## 参考
|
||||
- https://github.com/MohamedBassem/gormgen
|
||||
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/cmd/gormgen/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
input string
|
||||
structs []string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flagStructs := flag.String("structs", "", "[Required] The name of schema structs to generate structs for, comma seperated\n")
|
||||
flagInput := flag.String("input", "", "[Required] The name of the input file dir\n")
|
||||
flag.Parse()
|
||||
|
||||
if *flagStructs == "" || *flagInput == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
structs = strings.Split(*flagStructs, ",")
|
||||
input = *flagInput
|
||||
}
|
||||
|
||||
func main() {
|
||||
gen := pkg.NewGenerator(input)
|
||||
p := pkg.NewParser(input)
|
||||
if err := gen.ParserAST(p, structs).Generate().Format().Flush(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
// fieldConfig
|
||||
type fieldConfig struct {
|
||||
FieldName string
|
||||
ColumnName string
|
||||
FieldType string
|
||||
HumpName string
|
||||
}
|
||||
|
||||
// structConfig
|
||||
type structConfig struct {
|
||||
config
|
||||
StructName string
|
||||
OnlyFields []fieldConfig
|
||||
OptionFields []fieldConfig
|
||||
}
|
||||
|
||||
type ImportPkg struct {
|
||||
Pkg string
|
||||
}
|
||||
|
||||
type structHelpers struct {
|
||||
Titelize func(string) string
|
||||
}
|
||||
|
||||
type config struct {
|
||||
PkgName string
|
||||
Helpers structHelpers
|
||||
QueryBuilderName string
|
||||
}
|
||||
|
||||
// The Generator is the one responsible for generating the code, adding the imports, formating, and writing it to the file.
|
||||
type Generator struct {
|
||||
buf map[string]*bytes.Buffer
|
||||
inputFile string
|
||||
config config
|
||||
structConfigs []structConfig
|
||||
}
|
||||
|
||||
// NewGenerator function creates an instance of the generator given the name of the output file as an argument.
|
||||
func NewGenerator(outputFile string) *Generator {
|
||||
return &Generator{
|
||||
buf: map[string]*bytes.Buffer{},
|
||||
inputFile: outputFile,
|
||||
}
|
||||
}
|
||||
|
||||
// ParserAST parse by go file
|
||||
func (g *Generator) ParserAST(p *Parser, structs []string) (ret *Generator) {
|
||||
for _, v := range structs {
|
||||
g.buf[gorm.ToDBName(v)] = new(bytes.Buffer)
|
||||
}
|
||||
g.structConfigs = p.Parse()
|
||||
g.config.PkgName = p.pkg.Name
|
||||
g.config.Helpers = structHelpers{
|
||||
Titelize: strings.Title,
|
||||
}
|
||||
g.config.QueryBuilderName = SQLColumnToHumpStyle(p.pkg.Name) + "QueryBuilder"
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Generator) checkConfig() (err error) {
|
||||
if len(g.config.PkgName) == 0 {
|
||||
err = errors.New("package name dose'n set")
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(g.structConfigs); i++ {
|
||||
g.structConfigs[i].config = g.config
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Generate executes the template and store it in an internal buffer.
|
||||
func (g *Generator) Generate() *Generator {
|
||||
if err := g.checkConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, v := range g.structConfigs {
|
||||
if _, ok := g.buf[gorm.ToDBName(v.StructName)]; !ok {
|
||||
continue
|
||||
}
|
||||
if err := outputTemplate.Execute(g.buf[gorm.ToDBName(v.StructName)], v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// Format function formats the output of the generation.
|
||||
func (g *Generator) Format() *Generator {
|
||||
for k := range g.buf {
|
||||
formattedOutput, err := format.Source(g.buf[k].Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
g.buf[k] = bytes.NewBuffer(formattedOutput)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// Flush function writes the output to the output file.
|
||||
func (g *Generator) Flush() error {
|
||||
for k := range g.buf {
|
||||
filename := g.inputFile + "/gen_" + strings.ToLower(k) + ".go"
|
||||
if err := ioutil.WriteFile(filename, g.buf[k].Bytes(), 0777); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Println(" └── file : ", fmt.Sprintf("%s_repo/gen_%s.go", strings.ToLower(k), strings.ToLower(k)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
// The Parser is used to parse a directory and expose information about the structs defined in the files of this directory.
|
||||
type Parser struct {
|
||||
dir string
|
||||
pkg *build.Package
|
||||
parsedFiles []*ast.File
|
||||
}
|
||||
|
||||
// NewParser create a new parser instance.
|
||||
func NewParser(dir string) *Parser {
|
||||
return &Parser{
|
||||
dir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
// getPackage parse dir get go file and package
|
||||
func (p *Parser) getPackage() {
|
||||
pkg, err := build.Default.ImportDir(p.dir, build.ImportComment)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot process directory %s: %s", p.dir, err)
|
||||
}
|
||||
p.pkg = pkg
|
||||
|
||||
}
|
||||
|
||||
// parseGoFiles parse go file
|
||||
func (p *Parser) parseGoFiles() {
|
||||
var parsedFiles []*ast.File
|
||||
fs := token.NewFileSet()
|
||||
for _, file := range p.pkg.GoFiles {
|
||||
file = p.dir + "/" + file
|
||||
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing package: %s: %s\n", file, err)
|
||||
}
|
||||
parsedFiles = append(parsedFiles, parsedFile)
|
||||
}
|
||||
p.parsedFiles = parsedFiles
|
||||
}
|
||||
|
||||
// parseTypes parse type of struct
|
||||
func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) {
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
decl, ok := n.(*ast.GenDecl)
|
||||
if !ok || decl.Tok != token.TYPE {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, spec := range decl.Specs {
|
||||
var (
|
||||
data structConfig
|
||||
)
|
||||
typeSpec, _ok := spec.(*ast.TypeSpec)
|
||||
if !_ok {
|
||||
continue
|
||||
}
|
||||
// We only care about struct declaration (for now)
|
||||
var structType *ast.StructType
|
||||
if structType, ok = typeSpec.Type.(*ast.StructType); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
data.StructName = typeSpec.Name.Name
|
||||
for _, v := range structType.Fields.List {
|
||||
var (
|
||||
optionField fieldConfig
|
||||
)
|
||||
|
||||
if t, _ok := v.Type.(*ast.Ident); _ok {
|
||||
optionField.FieldType = t.String()
|
||||
} else {
|
||||
if v.Tag != nil {
|
||||
if strings.Contains(v.Tag.Value, "gorm") && strings.Contains(v.Tag.Value, "time") {
|
||||
optionField.FieldType = "time.Time"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(v.Names) > 0 {
|
||||
optionField.FieldName = v.Names[0].String()
|
||||
optionField.ColumnName = gorm.ToDBName(optionField.FieldName)
|
||||
optionField.HumpName = SQLColumnToHumpStyle(optionField.ColumnName)
|
||||
}
|
||||
|
||||
data.OptionFields = append(data.OptionFields, optionField)
|
||||
}
|
||||
|
||||
ret = append(ret, data)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Parse should be called before any type querying for the parser. It takes the directory to be parsed and extracts all the structs defined in this directory.
|
||||
func (p *Parser) Parse() (ret []structConfig) {
|
||||
var (
|
||||
data []structConfig
|
||||
)
|
||||
p.getPackage()
|
||||
p.parseGoFiles()
|
||||
for _, f := range p.parsedFiles {
|
||||
data = append(data, p.parseTypes(f)...)
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package pkg
|
||||
|
||||
import "text/template"
|
||||
|
||||
// Make sure that the template compiles during package initialization
|
||||
func parseTemplateOrPanic(t string) *template.Template {
|
||||
tpl, err := template.New("output_template").Parse(t)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tpl
|
||||
}
|
||||
|
||||
var outputTemplate = parseTemplateOrPanic(`
|
||||
///////////////////////////////////////////////////////////
|
||||
// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT //
|
||||
// ANY CHANGES DONE HERE WILL BE LOST //
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
package {{.PkgName}}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func NewModel() *{{.StructName}} {
|
||||
return new({{.StructName}})
|
||||
}
|
||||
|
||||
func NewQueryBuilder() *{{.QueryBuilderName}} {
|
||||
return new({{.QueryBuilderName}})
|
||||
}
|
||||
|
||||
func (t *{{.StructName}}) Create(db *gorm.DB) (id int32, err error) {
|
||||
if err = db.Create(t).Error; err != nil {
|
||||
return 0, errors.Wrap(err, "create err")
|
||||
}
|
||||
return t.Id, nil
|
||||
}
|
||||
|
||||
func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) {
|
||||
if err = db.Delete(t).Error; err != nil {
|
||||
return errors.Wrap(err, "delete err")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) {
|
||||
if err = db.Model(&{{.StructName}}{}).Where("id = ?", t.Id).Updates(m).Error; err != nil {
|
||||
return errors.Wrap(err, "updates err")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type {{.QueryBuilderName}} struct {
|
||||
order []string
|
||||
where []struct {
|
||||
prefix string
|
||||
value interface{}
|
||||
}
|
||||
limit int
|
||||
offset int
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) buildQuery(db *gorm.DB) *gorm.DB {
|
||||
ret := db
|
||||
for _, where := range qb.where {
|
||||
ret = ret.Where(where.prefix, where.value)
|
||||
}
|
||||
for _, order := range qb.order {
|
||||
ret = ret.Order(order)
|
||||
}
|
||||
ret = ret.Limit(qb.limit).Offset(qb.offset)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) Count(db *gorm.DB) (int64, error) {
|
||||
var c int64
|
||||
res := qb.buildQuery(db).Model(&{{.StructName}}{}).Count(&c)
|
||||
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
|
||||
c = 0
|
||||
}
|
||||
return c, res.Error
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) First(db *gorm.DB) (*{{.StructName}}, error) {
|
||||
ret := &{{.StructName}}{}
|
||||
res := qb.buildQuery(db).First(ret)
|
||||
if res.Error != nil && res.Error == gorm.ErrRecordNotFound {
|
||||
ret = nil
|
||||
}
|
||||
return ret, res.Error
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) QueryOne(db *gorm.DB) (*{{.StructName}}, error) {
|
||||
qb.limit = 1
|
||||
ret, err := qb.QueryAll(db)
|
||||
if len(ret) > 0 {
|
||||
return ret[0], err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) QueryAll(db *gorm.DB) ([]*{{.StructName}}, error) {
|
||||
var ret []*{{.StructName}}
|
||||
err := qb.buildQuery(db).Find(&ret).Error
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) Limit(limit int) *{{.QueryBuilderName}} {
|
||||
qb.limit = limit
|
||||
return qb
|
||||
}
|
||||
|
||||
func (qb *{{.QueryBuilderName}}) Offset(offset int) *{{.QueryBuilderName}} {
|
||||
qb.offset = offset
|
||||
return qb
|
||||
}
|
||||
|
||||
{{$queryBuilderName := .QueryBuilderName}}
|
||||
{{range .OptionFields}}
|
||||
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p db_repo.Predicate, value {{.FieldType}}) *{{$queryBuilderName}} {
|
||||
qb.where = append(qb.where, struct {
|
||||
prefix string
|
||||
value interface{}
|
||||
}{
|
||||
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", p),
|
||||
value,
|
||||
})
|
||||
return qb
|
||||
}
|
||||
|
||||
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}In(value []{{.FieldType}}) *{{$queryBuilderName}} {
|
||||
qb.where = append(qb.where, struct {
|
||||
prefix string
|
||||
value interface{}
|
||||
}{
|
||||
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", "IN"),
|
||||
value,
|
||||
})
|
||||
return qb
|
||||
}
|
||||
|
||||
func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}NotIn(value []{{.FieldType}}) *{{$queryBuilderName}} {
|
||||
qb.where = append(qb.where, struct {
|
||||
prefix string
|
||||
value interface{}
|
||||
}{
|
||||
fmt.Sprintf("%v %v ?", "{{.ColumnName}}", "NOT IN"),
|
||||
value,
|
||||
})
|
||||
return qb
|
||||
}
|
||||
|
||||
func (qb *{{$queryBuilderName}}) OrderBy{{call $.Helpers.Titelize .FieldName}}(asc bool) *{{$queryBuilderName}} {
|
||||
order := "DESC"
|
||||
if asc {
|
||||
order = "ASC"
|
||||
}
|
||||
|
||||
qb.order = append(qb.order, "{{.ColumnName}} " + order)
|
||||
return qb
|
||||
}
|
||||
{{end}}
|
||||
`)
|
||||
@@ -0,0 +1,18 @@
|
||||
package pkg
|
||||
|
||||
import "strings"
|
||||
|
||||
// SQLColumnToHumpStyle sql转换成驼峰模式
|
||||
func SQLColumnToHumpStyle(in string) (ret string) {
|
||||
for i := 0; i < len(in); i++ {
|
||||
if i > 0 && in[i-1] == '_' && in[i] != '_' {
|
||||
s := strings.ToUpper(string(in[i]))
|
||||
ret += s
|
||||
} else if in[i] == '_' {
|
||||
continue
|
||||
} else {
|
||||
ret += string(in[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
## 执行命令
|
||||
|
||||
```$xslt
|
||||
// test_handler 为 ./internal/api/controller/ 中的包名
|
||||
./scripts/handlergen.sh test_handler
|
||||
```
|
||||
|
||||
## 模板文件参考
|
||||
|
||||
```go
|
||||
package test_handler
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/db"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ Handler = (*handler)(nil)
|
||||
|
||||
type Handler interface {
|
||||
// i 为了避免被其他包实现
|
||||
i()
|
||||
|
||||
// Create 创建用户
|
||||
// @Tags Test
|
||||
// @Router /test/create [post]
|
||||
Create() core.HandlerFunc
|
||||
|
||||
// Update 编辑用户
|
||||
// @Tags Test
|
||||
// @Router /test/update [post]
|
||||
Update() core.HandlerFunc
|
||||
|
||||
// Delete 删除用户
|
||||
// @Tags Test
|
||||
// @Router /test/delete [post]
|
||||
Delete() core.HandlerFunc
|
||||
|
||||
// Detail 用户详情
|
||||
// @Tags Test
|
||||
// @Router /test/detail [post]
|
||||
Detail() core.HandlerFunc
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
logger *zap.Logger
|
||||
cache cache.Repo
|
||||
userService user_service.UserService
|
||||
}
|
||||
|
||||
func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler {
|
||||
return &handler{
|
||||
logger: logger,
|
||||
cache: cache,
|
||||
userService: user_service.NewUserService(db, cache),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) i() {}
|
||||
|
||||
```
|
||||
|
||||
以上会生成 4 个文件
|
||||
- func_create.go
|
||||
- func_update.go
|
||||
- func_delete.go
|
||||
- func_detail.go
|
||||
|
||||
## func_create.go 参考
|
||||
|
||||
```go
|
||||
package test_handler
|
||||
|
||||
import (
|
||||
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
|
||||
)
|
||||
|
||||
type createRequest struct{}
|
||||
|
||||
type createResponse struct{}
|
||||
|
||||
// Create 创建用户
|
||||
// @Summary 创建用户
|
||||
// @Description 创建用户
|
||||
// @Tags Test
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Request body createRequest true "请求信息"
|
||||
// @Success 200 {object} createResponse
|
||||
// @Failure 400 {object} code.Failure
|
||||
// @Router /test/create [post]
|
||||
func (h *handler) Create() core.HandlerFunc {
|
||||
return func(c core.Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/dave/dst"
|
||||
"github.com/dave/dst/decorator"
|
||||
)
|
||||
|
||||
var handlerName string
|
||||
|
||||
func init() {
|
||||
handler := flag.String("handler", "", "请输入需要生成的 handler 名称\n")
|
||||
flag.Parse()
|
||||
|
||||
handlerName = strings.ToLower(*handler)
|
||||
}
|
||||
|
||||
func main() {
|
||||
fs := token.NewFileSet()
|
||||
filePath := fmt.Sprintf("./internal/api/controller/%s", handlerName)
|
||||
parsedFile, err := decorator.ParseFile(fs, filePath+"/handler.go", nil, 0)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing package: %s: %s\n", filePath, err)
|
||||
}
|
||||
|
||||
files, _ := ioutil.ReadDir(filePath)
|
||||
if len(files) > 1 {
|
||||
log.Fatalf("请先确保 %s 目录中,有且仅有 handler.go 一个文件。", filePath)
|
||||
}
|
||||
|
||||
dst.Inspect(parsedFile, func(n dst.Node) bool {
|
||||
decl, ok := n.(*dst.GenDecl)
|
||||
if !ok || decl.Tok != token.TYPE {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, spec := range decl.Specs {
|
||||
typeSpec, _ok := spec.(*dst.TypeSpec)
|
||||
if !_ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var interfaceType *dst.InterfaceType
|
||||
if interfaceType, ok = typeSpec.Type.(*dst.InterfaceType); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range interfaceType.Methods.List {
|
||||
if len(v.Names) > 0 {
|
||||
if v.Names[0].String() == "i" {
|
||||
continue
|
||||
}
|
||||
|
||||
filepath := "./internal/api/controller/" + handlerName
|
||||
filename := fmt.Sprintf("%s/func_%s.go", filepath, strings.ToLower(v.Names[0].String()))
|
||||
funcFile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
|
||||
if err != nil {
|
||||
fmt.Printf("create and open func file error %v\n", err.Error())
|
||||
}
|
||||
fmt.Println(" └── file : ", filename)
|
||||
|
||||
funcContent := fmt.Sprintf("package %s\n\n", handlerName)
|
||||
funcContent += "import (\n"
|
||||
funcContent += `"github.com/xinliangnote/go-gin-api/internal/pkg/core"`
|
||||
funcContent += "\n)\n\n"
|
||||
funcContent += fmt.Sprintf("\n\ntype %sRequest struct {}\n\n", Lcfirst(v.Names[0].String()))
|
||||
funcContent += fmt.Sprintf("type %sResponse struct {}\n\n", Lcfirst(v.Names[0].String()))
|
||||
|
||||
// 首行注释
|
||||
funcContent += fmt.Sprintf("%s\n", v.Decorations().Start.All()[0])
|
||||
|
||||
nameArr := strings.Split(v.Decorations().Start.All()[0], v.Names[0].String())
|
||||
funcContent += fmt.Sprintf("// @Summary%s \n", nameArr[1])
|
||||
funcContent += fmt.Sprintf("// @Description%s \n", nameArr[1])
|
||||
// Tags
|
||||
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[1])
|
||||
funcContent += fmt.Sprintf("// @Accept json \n")
|
||||
funcContent += fmt.Sprintf("// @Produce json \n")
|
||||
funcContent += fmt.Sprintf("// @Param Request body %sRequest true \"请求信息\" \n", Lcfirst(v.Names[0].String()))
|
||||
funcContent += fmt.Sprintf("// @Success 200 {object} %sResponse \n", Lcfirst(v.Names[0].String()))
|
||||
funcContent += fmt.Sprintf("// @Failure 400 {object} code.Failure \n")
|
||||
// Router
|
||||
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[2])
|
||||
funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String())
|
||||
|
||||
funcFile.WriteString(funcContent)
|
||||
funcFile.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func Lcfirst(str string) string {
|
||||
for i, v := range str {
|
||||
return string(unicode.ToLower(v)) + str[i+1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
## 执行命令
|
||||
在根目录下执行脚本:`./scripts/init.sh addr user pass name`;
|
||||
- addr:数据库地址,例如:127.0.0.1:3306
|
||||
- user:账号,例如:root
|
||||
- pass:密码,例如:root
|
||||
- name:数据库名称,例如:go_gin_api
|
||||
|
||||
例如:
|
||||
```
|
||||
./scripts/init.sh 127.0.0.1:3306 root root go_gin_api
|
||||
```
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/cmd/init/db/mysql"
|
||||
)
|
||||
|
||||
var (
|
||||
dbAddr string
|
||||
dbUser string
|
||||
dbPass string
|
||||
dbName string
|
||||
)
|
||||
|
||||
func init() {
|
||||
addr := flag.String("addr", "", "请输入 db 地址,例如:127.0.0.1:3306\n")
|
||||
user := flag.String("user", "", "请输入 db 用户名\n")
|
||||
pass := flag.String("pass", "", "请输入 db 密码\n")
|
||||
name := flag.String("name", "", "请输入 db 名称\n")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
dbAddr = *addr
|
||||
dbUser = *user
|
||||
dbPass = *pass
|
||||
dbName = strings.ToLower(*name)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 初始化 DB
|
||||
db, err := mysql.New(dbAddr, dbUser, dbPass, dbName)
|
||||
if err != nil {
|
||||
log.Fatal("new db err: ", err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := db.DbClose(); err != nil {
|
||||
log.Fatal("db close err: ", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// 开启事务
|
||||
tx := db.GetDb().Begin()
|
||||
|
||||
// 创建 user_demo 表
|
||||
err = tx.Exec(mysql.CreateUserDemoTableSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create user_demo table err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create user_demo table success")
|
||||
|
||||
// 创建 authorized 表
|
||||
err = tx.Exec(mysql.CreateAuthorizedTableSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create authorized table err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create authorized table success")
|
||||
|
||||
err = tx.Exec(mysql.CreateAuthorizedTableDataSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create authorized table data err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create authorized table data success")
|
||||
|
||||
// 创建 authorized_api 表
|
||||
err = tx.Exec(mysql.CreateAuthorizedAPITableSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create authorized_api table err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create authorized_api table success")
|
||||
|
||||
err = tx.Exec(mysql.CreateAuthorizedAPITableDataSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create authorized_api table data err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create authorized_api table data success")
|
||||
|
||||
// 创建 admin 表
|
||||
err = tx.Exec(mysql.CreateAdminTableSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create admin table err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create admin table success")
|
||||
|
||||
err = tx.Exec(mysql.CreateAdminTableDataSql()).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Fatal("create admin table data err: ", err.Error())
|
||||
}
|
||||
fmt.Println("create admin table data success")
|
||||
|
||||
// 生成已完成 init 的标识,生成 init_db.lock
|
||||
f, err := os.Create("cmd/init/db/init_db.lock")
|
||||
if err != nil {
|
||||
log.Fatal("create init_db.lock err: ", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// 完成事务
|
||||
tx.Commit()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
var _ Repo = (*dbRepo)(nil)
|
||||
|
||||
type Repo interface {
|
||||
i()
|
||||
GetDb() *gorm.DB
|
||||
DbClose() error
|
||||
}
|
||||
|
||||
type dbRepo struct {
|
||||
DbConn *gorm.DB
|
||||
}
|
||||
|
||||
func New(dbAddr, dbUser, dbPass, dbName string) (Repo, error) {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
|
||||
dbUser,
|
||||
dbPass,
|
||||
dbAddr,
|
||||
dbName,
|
||||
true,
|
||||
"Local")
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true,
|
||||
},
|
||||
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
|
||||
}
|
||||
|
||||
db.Set("gorm:table_options", "CHARSET=utf8mb4")
|
||||
|
||||
return &dbRepo{
|
||||
DbConn: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dbRepo) i() {}
|
||||
|
||||
func (d *dbRepo) GetDb() *gorm.DB {
|
||||
return d.DbConn
|
||||
}
|
||||
|
||||
func (d *dbRepo) DbClose() error {
|
||||
sqlDB, err := d.DbConn.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package mysql
|
||||
|
||||
//CREATE TABLE `admin` (
|
||||
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
//`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
|
||||
//`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
|
||||
//`nickname` varchar(60) NOT NULL DEFAULT '' COMMENT '昵称',
|
||||
//`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
|
||||
//`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否',
|
||||
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
|
||||
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',
|
||||
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',
|
||||
//PRIMARY KEY (`id`),
|
||||
//UNIQUE KEY `unique_username` (`username`)
|
||||
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
|
||||
|
||||
func CreateAdminTableSql() (sql string) {
|
||||
sql = "CREATE TABLE `admin` ("
|
||||
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
|
||||
sql += "`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',"
|
||||
sql += "`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',"
|
||||
sql += "`nickname` varchar(60) NOT NULL DEFAULT '' COMMENT '昵称',"
|
||||
sql += "`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',"
|
||||
sql += "`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否',"
|
||||
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
|
||||
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
|
||||
sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',"
|
||||
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
|
||||
sql += "`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',"
|
||||
sql += "PRIMARY KEY (`id`),"
|
||||
sql += "UNIQUE KEY `unique_username` (`username`)"
|
||||
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CreateAdminTableDataSql() (sql string) {
|
||||
sql = "INSERT INTO `admin` (`id`, `username`, `password`, `nickname`, `mobile`, `created_user`) VALUES"
|
||||
sql += "(1, 'admin', 'f78382de80cf583cf854bbac0b6e796fbde36fe2739ca4ae072637010f179cb0', '管理员', '13888888888', 'init');"
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mysql
|
||||
|
||||
//CREATE TABLE `authorized` (
|
||||
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
//`business_key` varchar(32) NOT NULL DEFAULT '' COMMENT '调用方key',
|
||||
//`business_secret` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方secret',
|
||||
//`business_developer` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方对接人',
|
||||
//`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
|
||||
//`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否',
|
||||
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
|
||||
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',
|
||||
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',
|
||||
//PRIMARY KEY (`id`),
|
||||
//UNIQUE KEY `unique_business_key` (`business_key`)
|
||||
//) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权的调用方表';
|
||||
|
||||
func CreateAuthorizedTableSql() (sql string) {
|
||||
sql = "CREATE TABLE `authorized` ("
|
||||
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
|
||||
sql += "`business_key` varchar(32) NOT NULL DEFAULT '' COMMENT '调用方key',"
|
||||
sql += "`business_secret` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方secret',"
|
||||
sql += "`business_developer` varchar(60) NOT NULL DEFAULT '' COMMENT '调用方对接人',"
|
||||
sql += "`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',"
|
||||
sql += "`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否',"
|
||||
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
|
||||
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
|
||||
sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',"
|
||||
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
|
||||
sql += "`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',"
|
||||
sql += "PRIMARY KEY (`id`),"
|
||||
sql += "UNIQUE KEY `unique_business_key` (`business_key`)"
|
||||
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权的调用方表';"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CreateAuthorizedTableDataSql() (sql string) {
|
||||
sql = "INSERT INTO `authorized` (`id`, `business_key`, `business_secret`, `business_developer`, `remark`, `created_user`) VALUES (1, 'admin', '12878dd962115106db6d', '管理员', '管理面板调用', 'init');"
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mysql
|
||||
|
||||
//CREATE TABLE `authorized_api` (
|
||||
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
//`business_key` varchar(30) NOT NULL DEFAULT '' COMMENT '调用方key',
|
||||
//`method` varchar(30) NOT NULL DEFAULT '' COMMENT '请求方式',
|
||||
//`api` varchar(100) NOT NULL DEFAULT '' COMMENT '请求地址',
|
||||
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
|
||||
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',
|
||||
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',
|
||||
//PRIMARY KEY (`id`)
|
||||
//) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权接口地址';
|
||||
|
||||
func CreateAuthorizedAPITableSql() (sql string) {
|
||||
sql = "CREATE TABLE `authorized_api` ("
|
||||
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
|
||||
sql += "`business_key` varchar(30) NOT NULL DEFAULT '' COMMENT '调用方key',"
|
||||
sql += "`method` varchar(30) NOT NULL DEFAULT '' COMMENT '请求方式',"
|
||||
sql += "`api` varchar(100) NOT NULL DEFAULT '' COMMENT '请求地址',"
|
||||
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
|
||||
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
|
||||
sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人',"
|
||||
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
|
||||
sql += "`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人',"
|
||||
sql += "PRIMARY KEY (`id`)"
|
||||
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='已授权的调用方表';"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CreateAuthorizedAPITableDataSql() (sql string) {
|
||||
sql = "INSERT INTO `authorized_api` (`id`, `business_key`, `method`, `api`,`created_user`) VALUES"
|
||||
sql += "(1, 'admin', 'GET', '/api/**', 'init'),"
|
||||
sql += "(2, 'admin', 'POST', '/api/**', 'init'),"
|
||||
sql += "(3, 'admin', 'PUT', '/api/**', 'init'),"
|
||||
sql += "(4, 'admin', 'DELETE', '/api/**', 'init'),"
|
||||
sql += "(5, 'admin', 'PATCH', '/api/**', 'init');"
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package mysql
|
||||
|
||||
//CREATE TABLE `user_demo` (
|
||||
//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
//`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
|
||||
//`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',
|
||||
//`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
|
||||
//`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',
|
||||
//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
//PRIMARY KEY (`id`)
|
||||
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';
|
||||
|
||||
func CreateUserDemoTableSql() (sql string) {
|
||||
sql = "CREATE TABLE `user_demo` ("
|
||||
sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',"
|
||||
sql += "`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',"
|
||||
sql += "`nick_name` varchar(100) NOT NULL DEFAULT '' COMMENT '昵称',"
|
||||
sql += "`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',"
|
||||
sql += "`is_deleted` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '是否删除 1:是 -1:否',"
|
||||
sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',"
|
||||
sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',"
|
||||
sql += "PRIMARY KEY (`id`)"
|
||||
sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Demo表';"
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/koketama/errors"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var stdlib = make(map[string]bool)
|
||||
|
||||
func init() {
|
||||
pkgs, err := packages.Load(nil, "std")
|
||||
if err != nil {
|
||||
log.Fatal("get go stdlib err", zap.Error(err))
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
if !strings.HasPrefix(pkg.ID, "vendor") {
|
||||
stdlib[pkg.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var module string
|
||||
|
||||
func init() {
|
||||
file, err := os.Open("./go.mod")
|
||||
if err != nil {
|
||||
log.Fatal("no go.mod file found", zap.Error(err))
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, "module ") {
|
||||
module = strings.TrimSpace(line[7:])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if module == "" {
|
||||
log.Fatal("go.mod illegal")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() || strings.HasPrefix(path, ".") || strings.HasPrefix(path, "vendor") || strings.HasSuffix(path, ".pb.go") || filepath.Ext(path) != ".go" {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file %s err", path)
|
||||
}
|
||||
|
||||
digest0 := sha256.Sum256(raw)
|
||||
if raw, err = format.Source(raw); err != nil {
|
||||
return errors.Wrapf(err, "format file %s err", path)
|
||||
}
|
||||
|
||||
file, err := parser.ParseFile(token.NewFileSet(), "", raw, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parse file %s err", path)
|
||||
}
|
||||
|
||||
var first, last int
|
||||
var imports []*ast.ImportSpec
|
||||
comments := make(map[string]string)
|
||||
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
switch spec := n.(type) {
|
||||
case *ast.ImportSpec:
|
||||
if first == 0 {
|
||||
first = int(spec.Pos())
|
||||
}
|
||||
last = int(spec.End())
|
||||
|
||||
imports = append(imports, spec)
|
||||
|
||||
k := last - 1
|
||||
for ; k < len(raw); k++ {
|
||||
if raw[k] == '\r' || raw[k] == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
comment := string(raw[last-1 : k])
|
||||
if index := strings.Index(comment, "//"); index != -1 {
|
||||
comments[spec.Path.Value] = strings.TrimSpace(comment[index+2:])
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if imports != nil {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Write(raw[:first-2])
|
||||
buf.WriteString(sort(imports, comments))
|
||||
buf.Write(raw[last-1:])
|
||||
|
||||
if raw, err = format.Source(buf.Bytes()); err != nil {
|
||||
return errors.Wrapf(err, "double format file %s err", path)
|
||||
}
|
||||
}
|
||||
|
||||
digest1 := sha256.Sum256(raw)
|
||||
if !bytes.Equal(digest0[:], digest1[:]) {
|
||||
fmt.Println(path)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(path, raw, info.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "write file %s err", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("scan project err", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func sort(imports []*ast.ImportSpec, comments map[string]string) string {
|
||||
system := bytes.NewBuffer(nil)
|
||||
group := bytes.NewBuffer(nil)
|
||||
others := bytes.NewBuffer(nil)
|
||||
|
||||
for _, pkg := range imports {
|
||||
value := strings.Trim(pkg.Path.Value, `"`)
|
||||
switch {
|
||||
case stdlib[value]:
|
||||
if pkg.Name != nil {
|
||||
system.WriteString(pkg.Name.String())
|
||||
system.WriteString(" ")
|
||||
}
|
||||
|
||||
system.WriteString(pkg.Path.Value)
|
||||
if comment, ok := comments[pkg.Path.Value]; ok {
|
||||
system.WriteString(" ")
|
||||
system.WriteString("// ")
|
||||
system.WriteString(comment)
|
||||
}
|
||||
system.WriteString("\n")
|
||||
|
||||
case strings.HasPrefix(value, module):
|
||||
if pkg.Name != nil {
|
||||
group.WriteString(pkg.Name.String())
|
||||
group.WriteString(" ")
|
||||
}
|
||||
|
||||
group.WriteString(pkg.Path.Value)
|
||||
if comment, ok := comments[pkg.Path.Value]; ok {
|
||||
group.WriteString(" ")
|
||||
group.WriteString("// ")
|
||||
group.WriteString(comment)
|
||||
}
|
||||
group.WriteString("\n")
|
||||
|
||||
default:
|
||||
if pkg.Name != nil {
|
||||
others.WriteString(pkg.Name.String())
|
||||
others.WriteString(" ")
|
||||
}
|
||||
|
||||
others.WriteString(pkg.Path.Value)
|
||||
if comment, ok := comments[pkg.Path.Value]; ok {
|
||||
others.WriteString(" ")
|
||||
others.WriteString("// ")
|
||||
others.WriteString(comment)
|
||||
}
|
||||
others.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s", system.String(), group.String(), others.String())
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/cmd/mysqlmd/mysql"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type tableInfo struct {
|
||||
Name string `db:"table_name"` // name
|
||||
Comment sql.NullString `db:"table_comment"` // comment
|
||||
}
|
||||
|
||||
type tableColumn struct {
|
||||
OrdinalPosition uint16 `db:"ORDINAL_POSITION"` // position
|
||||
ColumnName string `db:"COLUMN_NAME"` // name
|
||||
ColumnType string `db:"COLUMN_TYPE"` // column_type
|
||||
DataType string `db:"DATA_TYPE"` // data_type
|
||||
ColumnKey sql.NullString `db:"COLUMN_KEY"` // key
|
||||
IsNullable string `db:"IS_NULLABLE"` // nullable
|
||||
Extra sql.NullString `db:"EXTRA"` // extra
|
||||
ColumnComment sql.NullString `db:"COLUMN_COMMENT"` // comment
|
||||
ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value
|
||||
}
|
||||
|
||||
var (
|
||||
dbAddr string
|
||||
dbUser string
|
||||
dbPass string
|
||||
dbName string
|
||||
genTables string
|
||||
)
|
||||
|
||||
func init() {
|
||||
addr := flag.String("addr", "", "请输入 db 地址,例如:127.0.0.1:3306\n")
|
||||
user := flag.String("user", "", "请输入 db 用户名\n")
|
||||
pass := flag.String("pass", "", "请输入 db 密码\n")
|
||||
name := flag.String("name", "", "请输入 db 名称\n")
|
||||
table := flag.String("tables", "*", "请输入 table 名称,默认为“*”,多个可用“,”分割\n")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
dbAddr = *addr
|
||||
dbUser = *user
|
||||
dbPass = *pass
|
||||
dbName = strings.ToLower(*name)
|
||||
genTables = strings.ToLower(*table)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 初始化 DB
|
||||
db, err := mysql.New(dbAddr, dbUser, dbPass, dbName)
|
||||
if err != nil {
|
||||
log.Fatal("new db err", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := db.DbClose(); err != nil {
|
||||
log.Println("db close err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tables, err := queryTables(db.GetDb(), dbName, genTables)
|
||||
if err != nil {
|
||||
log.Println("query tables of database err", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
|
||||
filepath := "./internal/api/repository/db_repo/" + table.Name + "_repo"
|
||||
_ = os.Mkdir(filepath, 0766)
|
||||
fmt.Println("create dir : ", filepath)
|
||||
|
||||
mdName := fmt.Sprintf("%s/gen_table.md", filepath)
|
||||
mdFile, err := os.OpenFile(mdName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
|
||||
if err != nil {
|
||||
fmt.Printf("markdown file error %v\n", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println(" └── file : ", table.Name+"_repo/gen_table.md")
|
||||
|
||||
modelName := fmt.Sprintf("%s/gen_model.go", filepath)
|
||||
modelFile, err := os.OpenFile(modelName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766)
|
||||
if err != nil {
|
||||
fmt.Printf("create and open model file error %v\n", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println(" └── file : ", table.Name+"_repo/gen_model.go")
|
||||
|
||||
modelContent := fmt.Sprintf("package %s%s\n", table.Name, "_repo")
|
||||
modelContent += fmt.Sprintf(`import "time"`)
|
||||
modelContent += fmt.Sprintf("\n\n// %s \n", table.Comment.String)
|
||||
modelContent += fmt.Sprintf("//go:generate gormgen -structs %s -input . \n", capitalize(table.Name))
|
||||
modelContent += fmt.Sprintf("type %s struct {\n", capitalize(table.Name))
|
||||
|
||||
tableContent := fmt.Sprintf("#### %s.%s \n", dbName, table.Name)
|
||||
if table.Comment.String != "" {
|
||||
tableContent += table.Comment.String + "\n"
|
||||
}
|
||||
tableContent += "\n" +
|
||||
"| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" +
|
||||
"| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n"
|
||||
|
||||
columnInfo, columnInfoErr := queryTableColumn(db.GetDb(), dbName, table.Name)
|
||||
if columnInfoErr != nil {
|
||||
continue
|
||||
}
|
||||
for _, info := range columnInfo {
|
||||
tableContent += fmt.Sprintf(
|
||||
"| %d | %s | %s | %s | %s | %s | %s | %s |\n",
|
||||
info.OrdinalPosition,
|
||||
info.ColumnName,
|
||||
strings.ReplaceAll(strings.ReplaceAll(info.ColumnComment.String, "|", "\\|"), "\n", ""),
|
||||
info.ColumnType,
|
||||
info.ColumnKey.String,
|
||||
info.IsNullable,
|
||||
info.Extra.String,
|
||||
info.ColumnDefault.String,
|
||||
)
|
||||
|
||||
if textType(info.DataType) == "time.Time" {
|
||||
modelContent += fmt.Sprintf("%s %s `%s` // %s\n", capitalize(info.ColumnName), textType(info.DataType), "gorm:\"time\"", info.ColumnComment.String)
|
||||
} else {
|
||||
modelContent += fmt.Sprintf("%s %s // %s\n", capitalize(info.ColumnName), textType(info.DataType), info.ColumnComment.String)
|
||||
}
|
||||
}
|
||||
|
||||
mdFile.WriteString(tableContent)
|
||||
mdFile.Close()
|
||||
|
||||
modelContent += "}\n"
|
||||
modelFile.WriteString(modelContent)
|
||||
modelFile.Close()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, error) {
|
||||
var tableCollect []tableInfo
|
||||
var tableArray []string
|
||||
var commentArray []sql.NullString
|
||||
|
||||
sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", dbName)
|
||||
rows, err := db.Raw(sqlTables).Rows()
|
||||
if err != nil {
|
||||
return tableCollect, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var info tableInfo
|
||||
err = rows.Scan(&info.Name, &info.Comment)
|
||||
if err != nil {
|
||||
fmt.Printf("execute query tables action error,had ignored, detail is [%v]\n", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
tableCollect = append(tableCollect, info)
|
||||
tableArray = append(tableArray, info.Name)
|
||||
commentArray = append(commentArray, info.Comment)
|
||||
}
|
||||
|
||||
// filter tables when specified tables params
|
||||
if tableName != "*" {
|
||||
tableCollect = nil
|
||||
chooseTables := strings.Split(tableName, ",")
|
||||
indexMap := make(map[int]int)
|
||||
for _, item := range chooseTables {
|
||||
subIndexMap := getTargetIndexMap(tableArray, item)
|
||||
for k, v := range subIndexMap {
|
||||
if _, ok := indexMap[k]; ok {
|
||||
continue
|
||||
}
|
||||
indexMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(indexMap) != 0 {
|
||||
for _, v := range indexMap {
|
||||
var info tableInfo
|
||||
info.Name = tableArray[v]
|
||||
info.Comment = commentArray[v]
|
||||
tableCollect = append(tableCollect, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tableCollect, err
|
||||
}
|
||||
|
||||
func queryTableColumn(db *gorm.DB, dbName string, tableName string) ([]tableColumn, error) {
|
||||
// 定义承载列信息的切片
|
||||
var columns []tableColumn
|
||||
|
||||
sqlTableColumn := fmt.Sprintf("SELECT `ORDINAL_POSITION`,`COLUMN_NAME`,`COLUMN_TYPE`,`DATA_TYPE`,`COLUMN_KEY`,`IS_NULLABLE`,`EXTRA`,`COLUMN_COMMENT`,`COLUMN_DEFAULT` FROM `information_schema`.`columns` WHERE `table_schema`= '%s' AND `table_name`= '%s' ORDER BY `ORDINAL_POSITION` ASC",
|
||||
dbName, tableName)
|
||||
|
||||
rows, err := db.Raw(sqlTableColumn).Rows()
|
||||
if err != nil {
|
||||
fmt.Printf("execute query table column action error, detail is [%v]\n", err.Error())
|
||||
return columns, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var column tableColumn
|
||||
err = rows.Scan(
|
||||
&column.OrdinalPosition,
|
||||
&column.ColumnName,
|
||||
&column.ColumnType,
|
||||
&column.DataType,
|
||||
&column.ColumnKey,
|
||||
&column.IsNullable,
|
||||
&column.Extra,
|
||||
&column.ColumnComment,
|
||||
&column.ColumnDefault)
|
||||
if err != nil {
|
||||
fmt.Printf("query table column scan error, detail is [%v]\n", err.Error())
|
||||
return columns, err
|
||||
}
|
||||
columns = append(columns, column)
|
||||
}
|
||||
|
||||
return columns, err
|
||||
}
|
||||
|
||||
func getTargetIndexMap(tableNameArr []string, item string) map[int]int {
|
||||
indexMap := make(map[int]int)
|
||||
for i := 0; i < len(tableNameArr); i++ {
|
||||
if match, _ := regexp.MatchString(item, tableNameArr[i]); match {
|
||||
if _, ok := indexMap[i]; ok {
|
||||
continue
|
||||
}
|
||||
indexMap[i] = i
|
||||
}
|
||||
}
|
||||
return indexMap
|
||||
}
|
||||
|
||||
func capitalize(s string) string {
|
||||
var upperStr string
|
||||
chars := strings.Split(s, "_")
|
||||
for _, val := range chars {
|
||||
vv := []rune(val)
|
||||
for i := 0; i < len(vv); i++ {
|
||||
if i == 0 {
|
||||
if vv[i] >= 97 && vv[i] <= 122 {
|
||||
vv[i] -= 32
|
||||
upperStr += string(vv[i])
|
||||
}
|
||||
} else {
|
||||
upperStr += string(vv[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return upperStr
|
||||
}
|
||||
|
||||
func textType(s string) string {
|
||||
var mysqlTypeToGoType = map[string]string{
|
||||
"tinyint": "int32",
|
||||
"smallint": "int32",
|
||||
"mediumint": "int32",
|
||||
"int": "int32",
|
||||
"integer": "int64",
|
||||
"bigint": "int64",
|
||||
"float": "float64",
|
||||
"double": "float64",
|
||||
"decimal": "float64",
|
||||
"date": "string",
|
||||
"time": "string",
|
||||
"year": "string",
|
||||
"datetime": "time.Time",
|
||||
"timestamp": "time.Time",
|
||||
"char": "string",
|
||||
"varchar": "string",
|
||||
"tinyblob": "string",
|
||||
"tinytext": "string",
|
||||
"blob": "string",
|
||||
"text": "string",
|
||||
"mediumblob": "string",
|
||||
"mediumtext": "string",
|
||||
"longblob": "string",
|
||||
"longtext": "string",
|
||||
}
|
||||
return mysqlTypeToGoType[s]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
var _ Repo = (*dbRepo)(nil)
|
||||
|
||||
type Repo interface {
|
||||
i()
|
||||
GetDb() *gorm.DB
|
||||
DbClose() error
|
||||
}
|
||||
|
||||
type dbRepo struct {
|
||||
DbConn *gorm.DB
|
||||
}
|
||||
|
||||
func New(dbAddr, dbUser, dbPass, dbName string) (Repo, error) {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s",
|
||||
dbUser,
|
||||
dbPass,
|
||||
dbAddr,
|
||||
dbName,
|
||||
true,
|
||||
"Local")
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true,
|
||||
},
|
||||
//Logger: logger.Default.LogMode(logger.Info), // 日志配置
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("[db connection failed] Database name: %s", dbName))
|
||||
}
|
||||
|
||||
db.Set("gorm:table_options", "CHARSET=utf8mb4")
|
||||
|
||||
return &dbRepo{
|
||||
DbConn: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dbRepo) i() {}
|
||||
|
||||
func (d *dbRepo) GetDb() *gorm.DB {
|
||||
return d.DbConn
|
||||
}
|
||||
|
||||
func (d *dbRepo) DbClose() error {
|
||||
sqlDB, err := d.DbConn.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/xinliangnote/go-gin-api/pkg/env"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var config = new(Config)
|
||||
|
||||
type Config struct {
|
||||
MySQL struct {
|
||||
Read struct {
|
||||
Addr string `toml:"addr"`
|
||||
User string `toml:"user"`
|
||||
Pass string `toml:"pass"`
|
||||
Name string `toml:"name"`
|
||||
} `toml:"read"`
|
||||
Write struct {
|
||||
Addr string `toml:"addr"`
|
||||
User string `toml:"user"`
|
||||
Pass string `toml:"pass"`
|
||||
Name string `toml:"name"`
|
||||
} `toml:"write"`
|
||||
Base struct {
|
||||
MaxOpenConn int `toml:"maxOpenConn"`
|
||||
MaxIdleConn int `toml:"maxIdleConn"`
|
||||
ConnMaxLifeTime time.Duration `toml:"connMaxLifeTime"`
|
||||
} `toml:"base"`
|
||||
} `toml:"mysql"`
|
||||
|
||||
Redis struct {
|
||||
Addr string `toml:"addr"`
|
||||
Pass string `toml:"pass"`
|
||||
Db int `toml:"db"`
|
||||
MaxRetries int `toml:"maxRetries"`
|
||||
PoolSize int `toml:"poolSize"`
|
||||
MinIdleConns int `toml:"minIdleConns"`
|
||||
} `toml:"redis"`
|
||||
|
||||
Mail struct {
|
||||
Host string `toml:"host"`
|
||||
Port int `toml:"port"`
|
||||
User string `toml:"user"`
|
||||
Pass string `toml:"pass"`
|
||||
To string `toml:"to"`
|
||||
} `toml:"mail"`
|
||||
|
||||
JWT struct {
|
||||
Secret string `toml:"secret"`
|
||||
ExpireDuration time.Duration `toml:"expireDuration"`
|
||||
} `toml:"jwt"`
|
||||
|
||||
URLToken struct {
|
||||
Secret string `toml:"secret"`
|
||||
ExpireDuration time.Duration `toml:"expireDuration"`
|
||||
} `toml:"urlToken"`
|
||||
|
||||
HashIds struct {
|
||||
Secret string `toml:"secret"`
|
||||
Length int `toml:"length"`
|
||||
} `toml:"hashids"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
viper.SetConfigName(env.Active().Value() + "_configs")
|
||||
viper.SetConfigType("toml")
|
||||
viper.AddConfigPath("./configs")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Get() Config {
|
||||
return *config
|
||||
}
|
||||
|
||||
func ProjectName() string {
|
||||
return "go-gin-api"
|
||||
}
|
||||
|
||||
func ProjectPort() string {
|
||||
return ":9999"
|
||||
}
|
||||
|
||||
func ProjectLogFile() string {
|
||||
return fmt.Sprintf("./logs/%s-access.log", ProjectName())
|
||||
}
|
||||
|
||||
func InitDBLockFile() string {
|
||||
return "cmd/init/db/init_db.lock"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
[db]
|
||||
host = '127.0.0.1'
|
||||
port = 3306
|
||||
@@ -0,0 +1,43 @@
|
||||
[mysql]
|
||||
|
||||
[mysql.read] # 从库信息,可读
|
||||
addr = '127.0.0.1:3306' # MySQL 地址:端口
|
||||
user = 'root' # 用户名
|
||||
pass = 'root' # 密码
|
||||
name = 'go_gin_api' # 数据库名称
|
||||
[mysql.write] # 主库信息,可读写
|
||||
addr = '127.0.0.1:3306' # MySQL 地址:端口
|
||||
user = 'root' # 用户名
|
||||
pass = 'root' # 密码
|
||||
name = 'go_gin_api' # 数据库名称
|
||||
[mysql.base] # 基础配置
|
||||
maxOpenConn = 10 # 最大打开的连接数
|
||||
maxIdleConn = 60 # 闲置的连接数
|
||||
connMaxLifeTime = 60 # 最大连接超时(单位:分)
|
||||
|
||||
[redis]
|
||||
addr = '127.0.0.1:6379' # Redis 地址:端口
|
||||
pass = '' # 密码
|
||||
db = 0 # 序号从 0 开始,默认是0,可以不用设置
|
||||
maxRetries = 3 # 命令执行失败时,最多重试多少次,默认为 0 即不重试
|
||||
poolSize = 10 # 连接池最大连接数,默认为 CPU 数 * 10
|
||||
minIdleConns = 5 # 最小空闲连接数
|
||||
|
||||
[mail]
|
||||
host = 'smtp.163.com' # 邮箱服务器,比如 smtp.163.com
|
||||
port = 465 # 端口
|
||||
user = "" # 发件人邮箱
|
||||
pass = "" # 发件人邮箱密码或授权码,根据邮箱服务器而定
|
||||
to = "" # 收件人邮箱,多个可以逗号(,)分割
|
||||
|
||||
[jwt]
|
||||
secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret
|
||||
expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)
|
||||
|
||||
[urlToken]
|
||||
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
|
||||
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)
|
||||
|
||||
[hashids]
|
||||
secret = '6ab6122836cfef95f8db' # hashids secret
|
||||
length = 12 # hashids length
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user