UP MDTApi
parent
4b29709791
commit
352666f059
|
@ -1,11 +1,9 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
import "net/http"
|
||||
|
||||
func InitApi() {
|
||||
BilibiliInit()
|
||||
http.HandleFunc("/api/mdt", GetInfo)
|
||||
http.HandleFunc("/api/memos", GetMemosJson)
|
||||
http.HandleFunc(Mindustry.Url, GetMindustryInfo)
|
||||
http.HandleFunc(Memos.Url, GetMemosJson)
|
||||
}
|
||||
|
|
|
@ -6,12 +6,18 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
func GetInfo(w http.ResponseWriter, r *http.Request) {
|
||||
var Mindustry = Api{
|
||||
Mode: "GET",
|
||||
Url: "/api/mdt",
|
||||
Args: []Args{{Name: "host", Required: true, Description: "服务器地址"}},
|
||||
SampleResponse: "{\n \"host\": \"p4.simpfun.cn\",\n \"port\": 8952,\n \"status\": \"Online\",\n \"name\": \"[#00ff00]镜影若滴の低配备用服\",\n \"maps\": \"未知\",\n \"players\": 0,\n \"version\": 146,\n \"wave\": 1,\n \"vertype\": \"official\",\n \"gamemode\": {\n \"name\": \"生存\",\n \"id\": 0\n },\n \"description\": \"[#00ff00]低配但稳定的备用服,[#ffaaff]欢迎加入QQ群:726525226\",\n \"modename\": \"\",\n \"limit\": 0,\n \"ping\": 40\n}",
|
||||
}
|
||||
|
||||
func GetMindustryInfo(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
err error
|
||||
info utils.ServerInfo
|
||||
)
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -25,7 +31,8 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Type", "application/json;charset=utf-8")
|
||||
err = json.NewEncoder(w).Encode(info)
|
||||
} else {
|
||||
http.Error(w, "参数为空", http.StatusInternalServerError)
|
||||
Mindustry.ErrorInfoView(w, "host为空")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, "无法解析服务器数据", http.StatusInternalServerError)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"blog/config"
|
||||
"blog/models"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Api struct {
|
||||
Mode string
|
||||
Url string
|
||||
Args []Args
|
||||
SampleResponse string
|
||||
}
|
||||
type Args struct {
|
||||
Name string
|
||||
Required bool
|
||||
Description string
|
||||
}
|
||||
|
||||
func ApiViewBuild(api Api, err interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"Api": api,
|
||||
"Err": err,
|
||||
"Config": config.Cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (a Api) ErrorInfoView(w io.Writer, err interface{}) {
|
||||
models.Api.Info.WriteData(w, ApiViewBuild(a, err))
|
||||
}
|
61
api/memos.go
61
api/memos.go
|
@ -25,37 +25,52 @@ type MemoInfo struct {
|
|||
RelationList []interface{} `json:"relationList"`
|
||||
}
|
||||
|
||||
var Memos = Api{
|
||||
Url: "/api/memos",
|
||||
Mode: "GET",
|
||||
SampleResponse: "[{\n \"bookSourceName\": \"69书吧自制\",\n \"bookSourceType\": 0,\n \"bookSourceUrl\": \"https://www.69shu.cc\",\n \"customOrder\": 45,\n \"enabled\": true,\n \"enabledCookieJar\": true,\n \"enabledExplore\": true,\n \"lastUpdateTime\": 1713454345818,\n \"respondTime\": 6224,\n \"ruleBookInfo\": {\n \"author\": \"h1@text##(.*) / (.*)##$2\",\n \"coverUrl\": \"img@src\",\n \"intro\": \".bookinfo_intro@text\",\n \"kind\": \".nav-mbx > a:nth-child(3)@text\",\n \"lastChapter\": \".update > a@text\",\n \"name\": \"h1@text##(.*) / (.*)##$1\"\n },\n \"ruleContent\": {\n \"content\": \"id.htmlContent@html##.*最快更新.*\"\n },\n \"ruleExplore\": {},\n \"ruleReview\": {},\n \"ruleSearch\": {\n \"author\": \"td.2@text\",\n \"bookList\": \".grid@tbody@tr!0\",\n \"bookUrl\": \"td.0@a@href\",\n \"checkKeyWord\": \"我的\",\n \"lastChapter\": \"td.1@text\",\n \"name\": \"td.0@text\",\n \"wordCount\": \"td.3@text\"\n },\n \"ruleToc\": {\n \"chapterList\": \"class.chapterlist.1@tag.li!0\",\n \"chapterName\": \"a.0@text\",\n \"chapterUrl\": \"a.0@href\"\n },\n \"searchUrl\": \"/modules/article/search.php?searchkey={{key}},{\\n\\\"charset\\\": \\\"gbk\\\"}\",\n \"weight\": 0\n}\n]",
|
||||
Args: []Args{{
|
||||
Name: "id",
|
||||
Required: true,
|
||||
Description: "有json代码块的文章id",
|
||||
}},
|
||||
}
|
||||
|
||||
func GetMemosJson(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||
}
|
||||
id := r.Form.Get("id")
|
||||
resp, err := http.Get(config.Cfg.MemosURL + "/api/v1/memo/" + id)
|
||||
if err != nil {
|
||||
http.Error(w, "无法获取文章数据", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var info MemoInfo
|
||||
err = json.NewDecoder(resp.Body).Decode(&info) // 使用json.NewDecoder解码JSON数据
|
||||
if err != nil {
|
||||
http.Error(w, "无法解析文章数据", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
compileRegex := regexp.MustCompile("(?s)```json\n(.*?)```")
|
||||
matchArr := compileRegex.FindStringSubmatch(info.Content)
|
||||
if len(matchArr) > 0 {
|
||||
source := make([]byte, 4096)
|
||||
source = []byte(fmt.Sprintf("[%v]", matchArr[len(matchArr)-1]))
|
||||
w.Header().Add("Content-Type", "application/octet-stream;charset=utf-8")
|
||||
w.Header().Add("Content-Disposition", "attachment;filename="+info.Name+".json")
|
||||
_, err = w.Write(source)
|
||||
if id != "" {
|
||||
resp, err := http.Get(config.Cfg.MemosURL + "/api/v1/memo/" + id)
|
||||
if err != nil {
|
||||
http.Error(w, "无法解析源数据", http.StatusInternalServerError)
|
||||
http.Error(w, "无法获取文章数据", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
defer resp.Body.Close()
|
||||
var info MemoInfo
|
||||
err = json.NewDecoder(resp.Body).Decode(&info) // 使用json.NewDecoder解码JSON数据
|
||||
if err != nil {
|
||||
http.Error(w, "无法解析文章数据", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
compileRegex := regexp.MustCompile("(?s)```json\n(.*?)```")
|
||||
matchArr := compileRegex.FindStringSubmatch(info.Content)
|
||||
if len(matchArr) > 0 {
|
||||
source := make([]byte, 4096)
|
||||
source = []byte(fmt.Sprintf("[%v]", matchArr[len(matchArr)-1]))
|
||||
w.Header().Add("Content-Type", "application/octet-stream;charset=utf-8")
|
||||
w.Header().Add("Content-Disposition", "attachment;filename="+info.Name+".json")
|
||||
_, err = w.Write(source)
|
||||
if err != nil {
|
||||
http.Error(w, "无法解析源数据", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
Memos.ErrorInfoView(w, "无法解析页面请求"+id)
|
||||
} else {
|
||||
Memos.ErrorInfoView(w, "id为空")
|
||||
}
|
||||
http.Error(w, "无法解析页面请求"+id, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ var Navigation Navs
|
|||
var ArticleList Articles
|
||||
var ArticleShortUrlMap map[string]string //用来保证文章 shortUrl 唯一和快速定位文章
|
||||
var Template HtmlTemplate
|
||||
var Api ApiTemplate
|
||||
|
||||
func CompiledContent() {
|
||||
config.Initial() //克隆或者更新文档库
|
||||
|
@ -35,6 +36,16 @@ func CompiledContent() {
|
|||
wg.Done()
|
||||
}()
|
||||
|
||||
//加载Api信息模板
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Api, err = initApiTemplate(config.Cfg.ThemesDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
//文章
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
|
|
@ -22,6 +22,11 @@ type HtmlTemplate struct {
|
|||
Index TemplatePointer
|
||||
}
|
||||
|
||||
type ApiTemplate struct {
|
||||
Info TemplatePointer
|
||||
Mindustry TemplatePointer
|
||||
}
|
||||
|
||||
func (t TemplatePointer) WriteData(w io.Writer, data interface{}) {
|
||||
|
||||
err := t.Execute(w, data)
|
||||
|
@ -125,3 +130,32 @@ func readHtmlTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer,
|
|||
|
||||
return htmlTemplate, nil
|
||||
}
|
||||
|
||||
func initApiTemplate(viewDir string) (ApiTemplate, error) {
|
||||
tp, err := readApiTemplate(
|
||||
[]string{"info", "mdt"},
|
||||
viewDir+"/api")
|
||||
if err != nil {
|
||||
return ApiTemplate{}, err
|
||||
}
|
||||
|
||||
return ApiTemplate{
|
||||
Info: tp[0],
|
||||
Mindustry: tp[1],
|
||||
}, nil
|
||||
}
|
||||
func readApiTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer, error) {
|
||||
var apiTemplate []TemplatePointer
|
||||
head := viewDir + "/head.gohtml"
|
||||
footer := viewDir + "/footer.gohtml"
|
||||
for _, name := range htmlFileName {
|
||||
tp, err := template.New(name+".gohtml").
|
||||
Funcs(template.FuncMap{"SpreadDigit": SpreadDigit}).
|
||||
ParseFiles(viewDir+"/"+name+".gohtml", head, footer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiTemplate = append(apiTemplate, TemplatePointer{tp})
|
||||
}
|
||||
return apiTemplate, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{{define "footer"}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
|
@ -0,0 +1,67 @@
|
|||
{{template "header" .}}
|
||||
<div class="layui-body">
|
||||
<!-- 内容主体区域 -->
|
||||
<div style="padding: 15px;">
|
||||
{{if ne .Err ""}}
|
||||
<blockquote class="layui-elem-quote layui-text" style="border-left-color:#ff5722">
|
||||
<span class="layui-badge layui-bg-red">ERR</span>
|
||||
{{ .Err }}
|
||||
</blockquote>
|
||||
<hr class="layui-border-blue">
|
||||
|
||||
{{ end }}
|
||||
<blockquote class="layui-elem-quote layui-text">
|
||||
<span class="layui-badge layui-bg-green">{{ .Api.Mode }}</span>
|
||||
{{ .Api.Url }}
|
||||
</blockquote>
|
||||
<div class="layui-collapse" lay-filter="filter-collapse" lay-accordion>
|
||||
<div class="layui-colla-item">
|
||||
<div class="layui-colla-title">请求参数</div>
|
||||
<div class="layui-colla-content">
|
||||
<table lay-filter="parse-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th lay-data="{field:'name', width:150}">参数名</th>
|
||||
<th lay-data="{field:'required', width:50}">必填</th>
|
||||
<th lay-data="{field:'description', minWidth: 180}">介绍</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Api.Args }}
|
||||
<tr>
|
||||
<td>{{ .Name }}</td>
|
||||
<td>{{ if .Required }}√ {{ else }}× {{ end }}</td>
|
||||
<td>{{ .Description }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="layui-border-orange">
|
||||
<div class="layui-card layui-panel">
|
||||
<div class="layui-card-header">
|
||||
响应示例
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<pre class="layui-code code-resp" lay-options="{lang:'json'}">{{ .Api.SampleResponse }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
layui.use(function () {
|
||||
var table = layui.table;
|
||||
var element = layui.element;
|
||||
element.on('collapse(filter-collapse)', function (data) {
|
||||
if (data.show) {
|
||||
table.init('parse-table', {});
|
||||
}
|
||||
});
|
||||
layui.code({
|
||||
elem: '.code-resp'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{template "footer" .}}
|
|
@ -2,9 +2,7 @@ package utils
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
@ -87,43 +85,26 @@ func (r *InfoBuffer) get() byte {
|
|||
return b
|
||||
}
|
||||
|
||||
func connectServer(host string) (buf InfoBuffer, err error) {
|
||||
func connectServer(host string) (buf InfoBuffer, ping int64, err error) {
|
||||
socket, err := net.Dial("udp", host)
|
||||
if err != nil {
|
||||
return InfoBuffer{}, err
|
||||
return InfoBuffer{}, -1, err
|
||||
}
|
||||
defer socket.Close()
|
||||
sendData, _ := hex.DecodeString("FE01")
|
||||
_, err = socket.Write(sendData)
|
||||
if err != nil {
|
||||
return InfoBuffer{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
|
||||
defer cancel()
|
||||
workDone := make(chan struct{}, 1)
|
||||
data := make([]byte, 2048)
|
||||
go func() {
|
||||
_, err = socket.Read(data)
|
||||
workDone <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-workDone:
|
||||
buf.New(data)
|
||||
case <-ctx.Done():
|
||||
return InfoBuffer{}, fmt.Errorf("超时:%v", err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
func ping(host string) (int64, error) {
|
||||
startTime := time.Now()
|
||||
conn, err := net.DialTimeout("tcp", host, time.Second*2)
|
||||
_, err = socket.Write([]byte{0xFE, 0x01})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return InfoBuffer{}, -1, err
|
||||
}
|
||||
conn.Close()
|
||||
conn.RemoteAddr()
|
||||
elapsedTime := time.Since(startTime).Milliseconds()
|
||||
return elapsedTime, nil
|
||||
data := make([]byte, 500)
|
||||
socket.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
_, err = socket.Read(data)
|
||||
if err != nil {
|
||||
return InfoBuffer{}, -1, err
|
||||
}
|
||||
ping = time.Since(startTime).Milliseconds()
|
||||
buf.New(data)
|
||||
return buf, ping, nil
|
||||
}
|
||||
func GetServerInfo(host string) (ServerInfo, error) {
|
||||
var info ServerInfo
|
||||
|
@ -140,17 +121,12 @@ func GetServerInfo(host string) (ServerInfo, error) {
|
|||
info.Port = port
|
||||
}
|
||||
add := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
||||
d, err := ping(add)
|
||||
if err != nil {
|
||||
info.Status = "Offline"
|
||||
return info, err
|
||||
}
|
||||
info.Ping = int(d)
|
||||
buf, err := connectServer(add)
|
||||
buf, ping, err := connectServer(add)
|
||||
if err != nil {
|
||||
info.Status = "Offline"
|
||||
return info, err
|
||||
}
|
||||
info.Ping = int(ping)
|
||||
info.Status = "Online"
|
||||
info.Name = buf.readString()
|
||||
info.Maps = buf.readString()
|
||||
|
|
Loading…
Reference in New Issue