-
-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: V2版本API重构-登录/base模块 #1096
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package v2 | ||
|
||
import ( | ||
"runtime" | ||
|
||
"github.com/labstack/echo/v4" | ||
|
||
"sealdice-core/api/v2/middleware" | ||
"sealdice-core/dice" | ||
) | ||
|
||
var ApiGroupApp *ApiGroup | ||
|
||
// Dice 由于现在还不能将配置拆出,导致middleware中间件没法正经做,只能在这里保存一下Dice进行控制。 | ||
var diceInstance *dice.Dice | ||
|
||
// 统一格式:前带/后不带/。 | ||
|
||
// InitBaseRouter 初始化基础路由 | ||
func InitBaseRouter(router *echo.Group) { | ||
publicRouter := router.Group("/base") | ||
baseApi := ApiGroupApp.SystemApiGroup.BaseApi | ||
{ | ||
publicRouter.GET("/preinfo", baseApi.PreInfo) | ||
publicRouter.GET("/baseInfo", baseApi.BaseInfo, middleware.AuthMiddleware(diceInstance)) | ||
publicRouter.GET("/heartbeat", baseApi.HeartBeat, middleware.AuthMiddleware(diceInstance)) | ||
publicRouter.GET("/checkSecurity", baseApi.CheckSecurity, middleware.AuthMiddleware(diceInstance)) | ||
// 安卓专属停止代码 | ||
if runtime.GOOS == "android" { | ||
publicRouter.GET("/force_stop", baseApi.ForceStop, middleware.AuthMiddleware(diceInstance)) | ||
} | ||
} | ||
} | ||
|
||
func InitLoginRouter(router *echo.Group) { | ||
publicRouter := router.Group("/login") | ||
loginApi := ApiGroupApp.SystemApiGroup.LoginApi | ||
{ | ||
publicRouter.POST("/signin", loginApi.DoSignIn) | ||
publicRouter.GET("/salt", loginApi.DoSignInGetSalt) | ||
} | ||
} | ||
|
||
func InitRouter(router *echo.Echo, dice *dice.Dice) { | ||
diceInstance = dice | ||
ApiGroupApp = InitSealdiceAPIV2(dice) | ||
group := router.Group("/v2/sd-api") | ||
InitBaseRouter(group) | ||
InitLoginRouter(group) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package v2 | ||
|
||
import ( | ||
"sealdice-core/api/v2/ui" | ||
"sealdice-core/dice" | ||
) | ||
|
||
// 有概率以后支持WEBHOOK等等和UI无关代码 | ||
type ApiGroup struct { | ||
SystemApiGroup ui.WebUIDiceGroup | ||
} | ||
|
||
// InitSealdiceAPIV2 初始化SealdiceAPI V2 | ||
// 先初始化,然后再执行router绑定的代码,这样的好处是任何一块代码都可插拔。 | ||
func InitSealdiceAPIV2(d *dice.Dice) *ApiGroup { | ||
a := new(ApiGroup) | ||
a.SystemApiGroup.Init(d) | ||
return a | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package middleware | ||
|
||
import ( | ||
"github.com/labstack/echo/v4" | ||
|
||
"sealdice-core/dice" | ||
"sealdice-core/utils/web/response" | ||
) | ||
|
||
// AuthMiddleware 鉴权中间件,接收Dice参数,仅允许可用Token. | ||
func AuthMiddleware(d *dice.Dice) echo.MiddlewareFunc { | ||
return func(next echo.HandlerFunc) echo.HandlerFunc { | ||
return func(c echo.Context) error { | ||
authToken := c.Request().Header.Get("Authorization") | ||
// 这里统一使用Dice的目的是,以后考虑直接把DiceManager扬了,改单例模式 | ||
if d.Parent.AccessTokens[authToken] { | ||
return next(c) | ||
} | ||
return response.NoAuth(c) | ||
} | ||
} | ||
} | ||
|
||
func TestModeMiddleware(d *dice.Dice) echo.MiddlewareFunc { | ||
return func(next echo.HandlerFunc) echo.HandlerFunc { | ||
return func(c echo.Context) error { | ||
if d.Parent.JustForTest { | ||
// TODO:补充更多细节,比如”展示模式不能进行xxx操作“ | ||
return response.FailWithMessage("展示模式,无法进行……", c) | ||
} | ||
return next(c) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# model | ||
|
||
此处存放可能用在API里的所有对应结构。 | ||
|
||
目前考量:除非有必要,否则尽量不把复杂返回结构体写在函数里,否则根本没法看。 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package model | ||
|
||
// VersionDetail 版本号详细信息 | ||
type VersionDetail struct { | ||
Major uint64 `json:"major"` | ||
Minor uint64 `json:"minor"` | ||
Patch uint64 `json:"patch"` | ||
Prerelease string `json:"prerelease"` | ||
BuildMetaData string `json:"buildMetaData"` | ||
} | ||
|
||
type BaseInfoResponse struct { | ||
AppName string `json:"appName"` | ||
AppChannel string `json:"appChannel"` | ||
Version string `json:"version"` | ||
VersionSimple string `json:"versionSimple"` | ||
VersionDetail VersionDetail `json:"versionDetail"` | ||
VersionNew string `json:"versionNew"` | ||
VersionNewNote string `json:"versionNewNote"` | ||
VersionCode int64 `json:"versionCode"` | ||
VersionNewCode int64 `json:"versionNewCode"` | ||
MemoryAlloc uint64 `json:"memoryAlloc"` | ||
Uptime int64 `json:"uptime"` | ||
MemoryUsedSys uint64 `json:"memoryUsedSys"` | ||
ExtraTitle string `json:"extraTitle"` | ||
OS string `json:"OS"` | ||
Arch string `json:"arch"` | ||
JustForTest bool `json:"justForTest"` | ||
ContainerMode bool `json:"containerMode"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
package ui | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 |
||
|
||
import ( | ||
"os" | ||
"runtime" | ||
"runtime/debug" | ||
"strings" | ||
"time" | ||
|
||
"github.com/jmoiron/sqlx" | ||
"github.com/labstack/echo/v4" | ||
|
||
"sealdice-core/api/v2/model" | ||
"sealdice-core/dice" | ||
"sealdice-core/utils/web/response" | ||
) | ||
|
||
// TODO: 注释现在不一定对的上地址,这个还得接着改喵 | ||
|
||
var startTime = time.Now().Unix() | ||
|
||
type fStopEcho struct { | ||
Key string `json:"key"` | ||
} | ||
|
||
// BaseApi | ||
// 曾经这里直接嵌入了Dice,但是后来考虑到这个东西应该是和BaseApi分开 | ||
// BaseApi不应该能用Dice的方法(而是通过方法控制Dice) | ||
type BaseApi struct { | ||
dice *dice.Dice | ||
} | ||
|
||
// PreInfo | ||
// @Tags Base | ||
// @Summary 获取测试模式信息 | ||
// @Security ApiKeyAuth | ||
// @Accept application/json | ||
// @Produce application/json | ||
// @Success 200 {object} response.Result{data=map[string]interface{},msg=string} "返回测试模式信息" | ||
// @Router /base/preInfo [post] | ||
func (b *BaseApi) PreInfo(c echo.Context) error { | ||
return response.OkWithData(map[string]interface{}{ | ||
"testMode": b.dice.Parent.JustForTest, | ||
}, c) | ||
} | ||
|
||
// BaseInfo | ||
// @Tags Base | ||
// @Summary 获取基础信息 | ||
// @Security ApiKeyAuth | ||
// @Accept application/json | ||
// @Produce application/json | ||
// @Success 200 {object} response.Result{data=model.BaseInfoResponse,msg=string} "返回基础信息,包括应用名称、版本、内存使用等" | ||
// @Router /base/baseinfo [get] | ||
func (b *BaseApi) BaseInfo(c echo.Context) error { | ||
// 鉴权后使用 | ||
dm := b.dice.Parent | ||
var m runtime.MemStats | ||
runtime.ReadMemStats(&m) | ||
|
||
var versionNew string | ||
var versionNewNote string | ||
var versionNewCode int64 | ||
if dm.AppVersionOnline != nil { | ||
versionNew = dm.AppVersionOnline.VersionLatestDetail | ||
versionNewNote = dm.AppVersionOnline.VersionLatestNote | ||
versionNewCode = dm.AppVersionOnline.VersionLatestCode | ||
} | ||
|
||
extraTitle := b.getName() | ||
|
||
versionDetail := model.VersionDetail{ | ||
Major: dice.VERSION.Major(), | ||
Minor: dice.VERSION.Minor(), | ||
Patch: dice.VERSION.Patch(), | ||
Prerelease: dice.VERSION.Prerelease(), | ||
BuildMetaData: dice.VERSION.Metadata(), | ||
} | ||
infoResponse := model.BaseInfoResponse{ | ||
AppName: dice.APPNAME, | ||
AppChannel: dice.APP_CHANNEL, | ||
Version: dice.VERSION.String(), | ||
VersionSimple: dice.VERSION_MAIN + dice.VERSION_PRERELEASE, | ||
VersionDetail: versionDetail, | ||
VersionNew: versionNew, | ||
VersionNewNote: versionNewNote, | ||
VersionCode: dice.VERSION_CODE, | ||
VersionNewCode: versionNewCode, | ||
MemoryAlloc: m.Alloc, | ||
MemoryUsedSys: m.Sys - m.HeapReleased, | ||
Uptime: time.Now().Unix() - startTime, | ||
ExtraTitle: extraTitle, | ||
OS: runtime.GOOS, | ||
Arch: runtime.GOARCH, | ||
JustForTest: dm.JustForTest, | ||
ContainerMode: dm.ContainerMode, | ||
} | ||
|
||
return response.OkWithData(infoResponse, c) | ||
} | ||
|
||
// ForceStop | ||
// @Tags Base | ||
// @Summary 安卓专属:强制停止程序 | ||
// @Security ApiKeyAuth | ||
// @Accept application/json | ||
// @Produce application/json | ||
// @Param key body fStopEcho true "强制停止的密钥" | ||
// @Success 200 {object} response.Result{msg=string} "执行成功" | ||
// @Failure 400 {object} response.Result{msg=string} "参数绑定错误/执行错误/Key不匹配等等" | ||
// @Router /base/force-stop [post] | ||
func (b *BaseApi) ForceStop(c echo.Context) error { | ||
// 此处不再判断是否为安卓,直接控制若是安卓再注册对应API即可 | ||
defer b.cleanupAndExit() | ||
// this is a dangerous api, so we need to check the key | ||
haskey := false | ||
for _, s := range os.Environ() { | ||
if strings.HasPrefix(s, "FSTOP_KEY=") { | ||
key := strings.Split(s, "=")[1] | ||
v := fStopEcho{} | ||
err := c.Bind(&v) | ||
if err != nil { | ||
return response.FailWithMessage(response.GetGenericErrorMsg(err), c) | ||
} | ||
if v.Key == key { | ||
haskey = true | ||
break | ||
} else { | ||
return response.FailWithMessage("检查到FSTOP_KEY不对应,停止执行", c) | ||
} | ||
} | ||
} | ||
if !haskey { | ||
return response.FailWithMessage("检查到环境中不含有FSTOP_KEY,停止执行", c) | ||
} | ||
return response.OkWithMessage("执行成功", c) | ||
} | ||
|
||
// HeartBeat | ||
// @Tags Base | ||
// @Summary 心跳检测 | ||
// @Security ApiKeyAuth | ||
// @Accept application/json | ||
// @Produce application/json | ||
// @Success 200 {object} response.Result{msg=string} "返回心跳检测信息" | ||
// @Router /base/heartbeat [get] | ||
func (b *BaseApi) HeartBeat(c echo.Context) error { | ||
// 需要鉴权 | ||
return response.OkWithMessage("HEARTBEATS", c) | ||
} | ||
|
||
// CheckSecurity | ||
// @Tags Base | ||
// @Summary 检查是否需要进行安全提醒 | ||
// @Security ApiKeyAuth | ||
// @Accept application/json | ||
// @Produce application/json | ||
// @Success 200 {object} response.Result{data=map[string]bool,msg=string} "返回安全检查结果" | ||
// @Router /base/security/check [get] | ||
func (b *BaseApi) CheckSecurity(c echo.Context) error { | ||
// 需要鉴权 | ||
isPublicService := strings.HasPrefix(b.dice.Parent.ServeAddress, "0.0.0.0") || b.dice.Parent.ServeAddress == ":3211" | ||
isEmptyPassword := b.dice.Parent.UIPasswordHash == "" | ||
return response.OkWithData(map[string]bool{ | ||
"isOk": !(isEmptyPassword && isPublicService), | ||
}, c) | ||
} | ||
|
||
// 工具函数 | ||
// cleanupAndExit 清理资源并退出程序 | ||
func (b *BaseApi) cleanupAndExit() { | ||
logger := b.dice.Logger | ||
logger.Info("程序即将退出,进行清理……") | ||
|
||
defer func() { | ||
if err := recover(); err != nil { | ||
logger.Errorf("异常: %v\n堆栈: %v", err, string(debug.Stack())) | ||
} | ||
}() | ||
// TODO:已经是单例模式了,这里其实不需要这么套娃 | ||
for _, i := range b.dice.Parent.Dice { | ||
if i.IsAlreadyLoadConfig { | ||
i.Config.BanList.SaveChanged(i) | ||
i.AttrsManager.CheckForSave() | ||
i.Save(true) | ||
|
||
// 关闭存储 | ||
for _, j := range i.ExtList { | ||
if j.Storage != nil { | ||
if err := j.StorageClose(); err != nil { | ||
logger.Errorf("异常: %v\n堆栈: %v", err, string(debug.Stack())) | ||
} | ||
} | ||
} | ||
i.IsAlreadyLoadConfig = false | ||
} | ||
|
||
b.closeDBConnections() | ||
} | ||
|
||
// 清理gocqhttp | ||
for _, i := range b.dice.Parent.Dice { | ||
if i.ImSession != nil && i.ImSession.EndPoints != nil { | ||
for _, j := range i.ImSession.EndPoints { | ||
dice.BuiltinQQServeProcessKill(i, j) | ||
} | ||
} | ||
} | ||
|
||
if b.dice.Parent.Help != nil { | ||
b.dice.Parent.Help.Close() | ||
} | ||
if b.dice.Parent.IsReady { | ||
b.dice.Parent.Save() | ||
} | ||
if b.dice.Parent.Cron != nil { | ||
b.dice.Parent.Cron.Stop() | ||
} | ||
logger.Info("清理完成,程序即将退出") | ||
os.Exit(0) //nolint:gocritic | ||
} | ||
|
||
// closeDBConnections 关闭数据库连接 | ||
func (b *BaseApi) closeDBConnections() { | ||
diceInstance := b.dice | ||
closeConnection := func(db *sqlx.DB) { | ||
if db != nil { | ||
_ = db.Close() | ||
} | ||
} | ||
|
||
closeConnection(diceInstance.DBData) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 |
||
closeConnection(diceInstance.DBLogs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 |
||
|
||
if cm := diceInstance.CensorManager; cm != nil { | ||
closeConnection(cm.DB) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [golangci-lint] reported by reviewdog 🐶 |
||
} | ||
} | ||
|
||
func (b *BaseApi) getName() string { | ||
defer func() { | ||
// 防止报错 | ||
_ = recover() | ||
}() | ||
|
||
ctx := &dice.MsgContext{Dice: b.dice, EndPoint: nil, Session: b.dice.ImSession} | ||
return dice.DiceFormatTmpl(ctx, "核心:骰子名字") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [golangci-lint] reported by reviewdog 🐶
could not import sealdice-core/api/v2/ui (-: # sealdice-core/api/v2/ui