1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-05-04 19:17:37 +08:00

feat(config): separate config & server (#1212)

* feat(server): add RegisterCustom

* feat(config): seprate config & server

* fix: make lint happy

* fix: make lint happy

* fix: ParseEnv nil pointer error

* typo(config): generateConfig hint

* fix(config): panic on range overflow
This commit is contained in:
源文雨 2021-12-07 22:39:30 +08:00 committed by GitHub
parent 3eade331bf
commit 26a7a1f0b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 300 additions and 206 deletions

View File

@ -8,11 +8,12 @@ import (
"os"
"strconv"
"strings"
"github.com/Mrs4s/go-cqhttp/internal/param"
"sync"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"github.com/Mrs4s/go-cqhttp/internal/param"
)
// defaultConfig 默认配置文件
@ -69,68 +70,11 @@ type Config struct {
Database map[string]yaml.Node `yaml:"database"`
}
// MiddleWares 通信中间件
type MiddleWares struct {
AccessToken string `yaml:"access-token"`
Filter string `yaml:"filter"`
RateLimit struct {
Enabled bool `yaml:"enabled"`
Frequency float64 `yaml:"frequency"`
Bucket int `yaml:"bucket"`
} `yaml:"rate-limit"`
}
// HTTPServer HTTP通信相关配置
type HTTPServer struct {
Disabled bool `yaml:"disabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Timeout int32 `yaml:"timeout"`
LongPolling struct {
Enabled bool `yaml:"enabled"`
MaxQueueSize int `yaml:"max-queue-size"`
} `yaml:"long-polling"`
Post []struct {
URL string `yaml:"url"`
Secret string `yaml:"secret"`
}
MiddleWares `yaml:"middlewares"`
}
// PprofServer pprof性能分析服务器相关配置
type PprofServer struct {
Disabled bool `yaml:"disabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
}
// WebsocketServer 正向WS相关配置
type WebsocketServer struct {
Disabled bool `yaml:"disabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
MiddleWares `yaml:"middlewares"`
}
// WebsocketReverse 反向WS相关配置
type WebsocketReverse struct {
Disabled bool `yaml:"disabled"`
Universal string `yaml:"universal"`
API string `yaml:"api"`
Event string `yaml:"event"`
ReconnectInterval int `yaml:"reconnect-interval"`
MiddleWares `yaml:"middlewares"`
}
// LambdaServer 云函数配置
type LambdaServer struct {
Disabled bool `yaml:"disabled"`
Type string `yaml:"type"`
MiddleWares `yaml:"middlewares"`
// Server 的简介和初始配置
type Server struct {
Brief string
Default string
ParseEnv func() (string, *yaml.Node)
}
// LevelDBConfig leveldb 相关配置
@ -183,161 +127,58 @@ func Parse(path string) *Config {
_ = n.Encode(dbConf)
return *n
}()
accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN")
if os.Getenv("GCQ_HTTP_PORT") != "" {
node := &yaml.Node{}
httpConf := &HTTPServer{
Host: "0.0.0.0",
Port: 5700,
MiddleWares: MiddleWares{
AccessToken: accessTokenEnv,
},
for _, s := range serverconfs {
if s.ParseEnv != nil {
name, node := s.ParseEnv()
if node != nil {
config.Servers = append(config.Servers, map[string]yaml.Node{name: *node})
}
}
param.SetExcludeDefault(&httpConf.Disabled, param.EnsureBool(os.Getenv("GCQ_HTTP_DISABLE"), false), false)
param.SetExcludeDefault(&httpConf.Host, os.Getenv("GCQ_HTTP_HOST"), "")
param.SetExcludeDefault(&httpConf.Port, int(toInt64(os.Getenv("GCQ_HTTP_PORT"))), 0)
if os.Getenv("GCQ_HTTP_POST_URL") != "" {
httpConf.Post = append(httpConf.Post, struct {
URL string `yaml:"url"`
Secret string `yaml:"secret"`
}{os.Getenv("GCQ_HTTP_POST_URL"), os.Getenv("GCQ_HTTP_POST_SECRET")})
}
_ = node.Encode(httpConf)
config.Servers = append(config.Servers, map[string]yaml.Node{"http": *node})
}
if os.Getenv("GCQ_WS_PORT") != "" {
node := &yaml.Node{}
wsServerConf := &WebsocketServer{
Host: "0.0.0.0",
Port: 6700,
MiddleWares: MiddleWares{
AccessToken: accessTokenEnv,
},
}
param.SetExcludeDefault(&wsServerConf.Disabled, param.EnsureBool(os.Getenv("GCQ_WS_DISABLE"), false), false)
param.SetExcludeDefault(&wsServerConf.Host, os.Getenv("GCQ_WS_HOST"), "")
param.SetExcludeDefault(&wsServerConf.Port, int(toInt64(os.Getenv("GCQ_WS_PORT"))), 0)
_ = node.Encode(wsServerConf)
config.Servers = append(config.Servers, map[string]yaml.Node{"ws": *node})
}
if os.Getenv("GCQ_RWS_API") != "" || os.Getenv("GCQ_RWS_EVENT") != "" || os.Getenv("GCQ_RWS_UNIVERSAL") != "" {
node := &yaml.Node{}
rwsConf := &WebsocketReverse{
MiddleWares: MiddleWares{
AccessToken: accessTokenEnv,
},
}
param.SetExcludeDefault(&rwsConf.Disabled, param.EnsureBool(os.Getenv("GCQ_RWS_DISABLE"), false), false)
param.SetExcludeDefault(&rwsConf.API, os.Getenv("GCQ_RWS_API"), "")
param.SetExcludeDefault(&rwsConf.Event, os.Getenv("GCQ_RWS_EVENT"), "")
param.SetExcludeDefault(&rwsConf.Universal, os.Getenv("GCQ_RWS_UNIVERSAL"), "")
_ = node.Encode(rwsConf)
config.Servers = append(config.Servers, map[string]yaml.Node{"ws-reverse": *node})
}
}
return config
}
var serverconfs []*Server
var mu sync.Mutex
// AddServer 添加该服务的简介和默认配置
func AddServer(s *Server) {
mu.Lock()
serverconfs = append(serverconfs, s)
mu.Unlock()
}
// generateConfig 生成配置文件
func generateConfig() {
fmt.Println("未找到配置文件,正在为您生成配置文件中!")
sb := strings.Builder{}
sb.WriteString(defaultConfig)
fmt.Print(`请选择你需要的通信方式:
> 1: HTTP通信
> 2: 正向 Websocket 通信
> 3: 反向 Websocket 通信
> 4: pprof 性能分析服务器
> 5: 云函数服务
请输入你需要的编号可输入多个同一编号也可输入多个(: 233)
您的选择是:`)
hint := "请选择你需要的通信方式:"
for i, s := range serverconfs {
hint += fmt.Sprintf("\n> %d: %s", i, s.Brief)
}
hint += `
请输入你需要的编号(0-9)可输入多个同一编号也可输入多个(: 233)
您的选择是:`
fmt.Print(hint)
input := bufio.NewReader(os.Stdin)
readString, err := input.ReadString('\n')
if err != nil {
log.Fatal("输入不合法: ", err)
}
rmax := len(serverconfs)
if rmax > 10 {
rmax = 10
}
for _, r := range readString {
switch r {
case '1':
sb.WriteString(httpDefault)
case '2':
sb.WriteString(wsDefault)
case '3':
sb.WriteString(wsReverseDefault)
case '4':
sb.WriteString(pprofDefault)
case '5':
sb.WriteString(lambdaDefault)
r -= '0'
if r >= 0 && r < rune(rmax) {
sb.WriteString(serverconfs[r].Default)
}
}
_ = os.WriteFile("config.yml", []byte(sb.String()), 0o644)
fmt.Println("默认配置文件已生成,请修改 config.yml 后重新启动!")
_, _ = input.ReadString('\n')
}
const httpDefault = ` # HTTP 通信设置
- http:
# 服务端监听地址
host: 127.0.0.1
# 服务端监听端口
port: 5700
# 反向HTTP超时时间, 单位秒
# 最小值为5小于5将会忽略本项设置
timeout: 5
# 长轮询拓展
long-polling:
# 是否开启
enabled: false
# 消息队列大小0 表示不限制队列大小谨慎使用
max-queue-size: 2000
middlewares:
<<: *default # 引用默认中间件
# 反向HTTP POST地址列表
post:
#- url: '' # 地址
# secret: '' # 密钥
#- url: http://127.0.0.1:5701/ # 地址
# secret: '' # 密钥
`
const lambdaDefault = ` # LambdaServer 配置
- lambda:
type: scf # scf: 腾讯云函数 aws: aws Lambda
middlewares:
<<: *default # 引用默认中间件
`
const wsDefault = ` # 正向WS设置
- ws:
# 正向WS服务器监听地址
host: 127.0.0.1
# 正向WS服务器监听端口
port: 6700
middlewares:
<<: *default # 引用默认中间件
`
const wsReverseDefault = ` # 反向WS设置
- ws-reverse:
# 反向WS Universal 地址
# 注意 设置了此项地址后下面两项将会被忽略
universal: ws://your_websocket_universal.server
# 反向WS API 地址
api: ws://your_websocket_api.server
# 反向WS Event 地址
event: ws://your_websocket_event.server
# 重连间隔 单位毫秒
reconnect-interval: 3000
middlewares:
<<: *default # 引用默认中间件
`
const pprofDefault = ` # pprof 性能分析服务器, 一般情况下不需要启用.
# 如果遇到性能问题请上传报告给开发者处理
# 注意: pprof服务不支持中间件不支持鉴权. 请不要开放到公网
- pprof:
# pprof服务器监听地址
host: 127.0.0.1
# pprof服务器监听端口
port: 7700
`

View File

@ -16,9 +16,33 @@ import (
"github.com/Mrs4s/go-cqhttp/modules/servers"
)
const pprofDefault = ` # pprof 性能分析服务器, 一般情况下不需要启用.
# 如果遇到性能问题请上传报告给开发者处理
# 注意: pprof服务不支持中间件不支持鉴权. 请不要开放到公网
- pprof:
# pprof服务器监听地址
host: 127.0.0.1
# pprof服务器监听端口
port: 7700
`
// pprofServer pprof性能分析服务器相关配置
type pprofServer struct {
Disabled bool `yaml:"disabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
}
func init() {
config.AddServer(&config.Server{
Brief: "pprof 性能分析服务器",
Default: pprofDefault,
})
}
// runPprof 启动 pprof 性能分析服务器
func runPprof(_ *coolq.CQBot, node yaml.Node) {
var conf config.PprofServer
var conf pprofServer
switch err := node.Decode(&conf); {
case err != nil:
log.Warn("读取pprof配置失败 :", err)

View File

@ -8,7 +8,10 @@ import (
"github.com/Mrs4s/go-cqhttp/internal/base"
)
var svr = make(map[string]func(*coolq.CQBot, yaml.Node))
var (
svr = make(map[string]func(*coolq.CQBot, yaml.Node))
nocfgsvr = make(map[string]func(*coolq.CQBot))
)
// Register 注册 Server
func Register(name string, proc func(*coolq.CQBot, yaml.Node)) {
@ -19,6 +22,15 @@ func Register(name string, proc func(*coolq.CQBot, yaml.Node)) {
svr[name] = proc
}
// RegisterCustom 注册无需 config 的自定义 Server
func RegisterCustom(name string, proc func(*coolq.CQBot)) {
_, ok := nocfgsvr[name]
if ok {
panic(name + " server has existed")
}
nocfgsvr[name] = proc
}
// Run 运行所有svr
func Run(bot *coolq.CQBot) {
for _, l := range base.Servers {
@ -28,4 +40,7 @@ func Run(bot *coolq.CQBot) {
}
}
}
for _, fn := range nocfgsvr {
go fn(bot)
}
}

View File

@ -24,11 +24,30 @@ import (
"github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/param"
"github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/modules/config"
"github.com/Mrs4s/go-cqhttp/modules/filter"
)
// HTTPServer HTTP通信相关配置
type HTTPServer struct {
Disabled bool `yaml:"disabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Timeout int32 `yaml:"timeout"`
LongPolling struct {
Enabled bool `yaml:"enabled"`
MaxQueueSize int `yaml:"max-queue-size"`
} `yaml:"long-polling"`
Post []struct {
URL string `yaml:"url"`
Secret string `yaml:"secret"`
}
MiddleWares `yaml:"middlewares"`
}
type httpServer struct {
HTTP *http.Server
api *api.Caller
@ -51,6 +70,68 @@ type httpCtx struct {
postForm url.Values
}
const httpDefault = ` # HTTP 通信设置
- http:
# 服务端监听地址
host: 127.0.0.1
# 服务端监听端口
port: 5700
# 反向HTTP超时时间, 单位秒
# 最小值为5小于5将会忽略本项设置
timeout: 5
# 长轮询拓展
long-polling:
# 是否开启
enabled: false
# 消息队列大小0 表示不限制队列大小谨慎使用
max-queue-size: 2000
middlewares:
<<: *default # 引用默认中间件
# 反向HTTP POST地址列表
post:
#- url: '' # 地址
# secret: '' # 密钥
#- url: http://127.0.0.1:5701/ # 地址
# secret: '' # 密钥
`
func init() {
config.AddServer(&config.Server{
Brief: "HTTP通信",
Default: httpDefault,
ParseEnv: func() (string, *yaml.Node) {
if os.Getenv("GCQ_HTTP_PORT") != "" {
// type convert tools
toInt64 := func(str string) int64 {
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN")
node := &yaml.Node{}
httpConf := &HTTPServer{
Host: "0.0.0.0",
Port: 5700,
MiddleWares: MiddleWares{
AccessToken: accessTokenEnv,
},
}
param.SetExcludeDefault(&httpConf.Disabled, param.EnsureBool(os.Getenv("GCQ_HTTP_DISABLE"), false), false)
param.SetExcludeDefault(&httpConf.Host, os.Getenv("GCQ_HTTP_HOST"), "")
param.SetExcludeDefault(&httpConf.Port, int(toInt64(os.Getenv("GCQ_HTTP_PORT"))), 0)
if os.Getenv("GCQ_HTTP_POST_URL") != "" {
httpConf.Post = append(httpConf.Post, struct {
URL string `yaml:"url"`
Secret string `yaml:"secret"`
}{os.Getenv("GCQ_HTTP_POST_URL"), os.Getenv("GCQ_HTTP_POST_SECRET")})
}
_ = node.Encode(httpConf)
return "http", node
}
return "", nil
},
})
}
func (h *httpCtx) Get(s string) gjson.Result {
j := h.json.Get(s)
if j.Exists() {
@ -163,7 +244,7 @@ func checkAuth(req *http.Request, token string) int {
// runHTTP 启动HTTP服务器与HTTP上报客户端
func runHTTP(bot *coolq.CQBot, node yaml.Node) {
var conf config.HTTPServer
var conf HTTPServer
switch err := node.Decode(&conf); {
case err != nil:
log.Warn("读取http配置失败 :", err)

View File

@ -13,6 +13,17 @@ import (
"golang.org/x/time/rate"
)
// MiddleWares 通信中间件
type MiddleWares struct {
AccessToken string `yaml:"access-token"`
Filter string `yaml:"filter"`
RateLimit struct {
Enabled bool `yaml:"enabled"`
Frequency float64 `yaml:"frequency"`
Bucket int `yaml:"bucket"`
} `yaml:"rate-limit"`
}
func rateLimit(frequency float64, bucketSize int) api.Handler {
limiter := rate.NewLimiter(rate.Limit(frequency), bucketSize)
return func(_ string, _ api.Getter) global.MSG {

View File

@ -81,7 +81,7 @@ var cli *lambdaClient
// runLambda type: [scf,aws]
func runLambda(bot *coolq.CQBot, node yaml.Node) {
var conf config.LambdaServer
var conf LambdaServer
switch err := node.Decode(&conf); {
case err != nil:
log.Warn("读取lambda配置失败 :", err)
@ -155,6 +155,28 @@ type lambdaInvoke struct {
} `json:"requestContext"`
}
const lambdaDefault = ` # LambdaServer 配置
- lambda:
type: scf # scf: 腾讯云函数 aws: aws Lambda
middlewares:
<<: *default # 引用默认中间件
`
// LambdaServer 云函数配置
type LambdaServer struct {
Disabled bool `yaml:"disabled"`
Type string `yaml:"type"`
MiddleWares `yaml:"middlewares"`
}
func init() {
config.AddServer(&config.Server{
Brief: "云函数服务",
Default: lambdaDefault,
})
}
func (c *lambdaClient) next() *http.Request {
r, err := http.NewRequest(http.MethodGet, c.nextURL, nil)
if err != nil {

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"runtime/debug"
"strconv"
"strings"
@ -19,6 +20,7 @@ import (
"github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/param"
"github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/modules/config"
"github.com/Mrs4s/go-cqhttp/modules/filter"
@ -26,7 +28,7 @@ import (
type webSocketServer struct {
bot *coolq.CQBot
conf *config.WebsocketServer
conf *WebsocketServer
mu sync.Mutex
eventConn []*wsConn
@ -71,9 +73,107 @@ var upgrader = websocket.Upgrader{
},
}
const wsDefault = ` # 正向WS设置
- ws:
# 正向WS服务器监听地址
host: 127.0.0.1
# 正向WS服务器监听端口
port: 6700
middlewares:
<<: *default # 引用默认中间件
`
const wsReverseDefault = ` # 反向WS设置
- ws-reverse:
# 反向WS Universal 地址
# 注意 设置了此项地址后下面两项将会被忽略
universal: ws://your_websocket_universal.server
# 反向WS API 地址
api: ws://your_websocket_api.server
# 反向WS Event 地址
event: ws://your_websocket_event.server
# 重连间隔 单位毫秒
reconnect-interval: 3000
middlewares:
<<: *default # 引用默认中间件
`
// WebsocketServer 正向WS相关配置
type WebsocketServer struct {
Disabled bool `yaml:"disabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
MiddleWares `yaml:"middlewares"`
}
// WebsocketReverse 反向WS相关配置
type WebsocketReverse struct {
Disabled bool `yaml:"disabled"`
Universal string `yaml:"universal"`
API string `yaml:"api"`
Event string `yaml:"event"`
ReconnectInterval int `yaml:"reconnect-interval"`
MiddleWares `yaml:"middlewares"`
}
func init() {
config.AddServer(&config.Server{
Brief: "正向 Websocket 通信",
Default: wsDefault,
ParseEnv: func() (string, *yaml.Node) {
if os.Getenv("GCQ_WS_PORT") != "" {
// type convert tools
toInt64 := func(str string) int64 {
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN")
node := &yaml.Node{}
wsServerConf := &WebsocketServer{
Host: "0.0.0.0",
Port: 6700,
MiddleWares: MiddleWares{
AccessToken: accessTokenEnv,
},
}
param.SetExcludeDefault(&wsServerConf.Disabled, param.EnsureBool(os.Getenv("GCQ_WS_DISABLE"), false), false)
param.SetExcludeDefault(&wsServerConf.Host, os.Getenv("GCQ_WS_HOST"), "")
param.SetExcludeDefault(&wsServerConf.Port, int(toInt64(os.Getenv("GCQ_WS_PORT"))), 0)
_ = node.Encode(wsServerConf)
return "ws", node
}
return "", nil
},
})
config.AddServer(&config.Server{
Brief: "反向 Websocket 通信",
Default: wsReverseDefault,
ParseEnv: func() (string, *yaml.Node) {
if os.Getenv("GCQ_RWS_API") != "" || os.Getenv("GCQ_RWS_EVENT") != "" || os.Getenv("GCQ_RWS_UNIVERSAL") != "" {
accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN")
node := &yaml.Node{}
rwsConf := &WebsocketReverse{
MiddleWares: MiddleWares{
AccessToken: accessTokenEnv,
},
}
param.SetExcludeDefault(&rwsConf.Disabled, param.EnsureBool(os.Getenv("GCQ_RWS_DISABLE"), false), false)
param.SetExcludeDefault(&rwsConf.API, os.Getenv("GCQ_RWS_API"), "")
param.SetExcludeDefault(&rwsConf.Event, os.Getenv("GCQ_RWS_EVENT"), "")
param.SetExcludeDefault(&rwsConf.Universal, os.Getenv("GCQ_RWS_UNIVERSAL"), "")
_ = node.Encode(rwsConf)
return "ws-reverse", node
}
return "", nil
},
})
}
// runWSServer 运行一个正向WS server
func runWSServer(b *coolq.CQBot, node yaml.Node) {
var conf config.WebsocketServer
var conf WebsocketServer
switch err := node.Decode(&conf); {
case err != nil:
log.Warn("读取正向Websocket配置失败 :", err)
@ -103,7 +203,7 @@ func runWSServer(b *coolq.CQBot, node yaml.Node) {
// runWSClient 运行一个反向向WS client
func runWSClient(b *coolq.CQBot, node yaml.Node) {
var conf config.WebsocketReverse
var conf WebsocketReverse
switch err := node.Decode(&conf); {
case err != nil:
log.Warn("读取反向Websocket配置失败 :", err)