161 lines
4.3 KiB
Go
161 lines
4.3 KiB
Go
package admin
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
type UpdateInfo struct {
|
|
Version string `json:"version"`
|
|
Platform string `json:"platform"`
|
|
FileName string `json:"filename"`
|
|
SHA256 string `json:"sha256"`
|
|
Size int64 `json:"size"`
|
|
ReleaseNotes string `json:"release_notes"`
|
|
Mandatory bool `json:"mandatory"`
|
|
PublishedAt string `json:"published_at"`
|
|
}
|
|
|
|
var updatesDir string
|
|
|
|
func initUpdatesDir() {
|
|
exe, _ := os.Executable()
|
|
updatesDir = filepath.Join(filepath.Dir(exe), "data", "updates")
|
|
os.MkdirAll(updatesDir, 0755)
|
|
}
|
|
|
|
func RegisterUpdateRoutes(mux *http.ServeMux) {
|
|
initUpdatesDir()
|
|
|
|
mux.HandleFunc("/api/update/check", handleUpdateCheck)
|
|
mux.Handle("/updates/", http.StripPrefix("/updates/",
|
|
http.FileServer(http.Dir(updatesDir))))
|
|
mux.HandleFunc("/api/admin/publish_update", authWrap(handlePublishUpdate))
|
|
mux.HandleFunc("/api/admin/update_info", handleGetUpdateInfo)
|
|
mux.HandleFunc("/api/admin/delete_update", authWrap(handleDeleteUpdate))
|
|
}
|
|
|
|
func getUpdateInfoPath() string {
|
|
return filepath.Join(updatesDir, "latest.json")
|
|
}
|
|
|
|
func loadUpdateInfo() *UpdateInfo {
|
|
data, err := os.ReadFile(getUpdateInfoPath())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var info UpdateInfo
|
|
if json.Unmarshal(data, &info) != nil {
|
|
return nil
|
|
}
|
|
return &info
|
|
}
|
|
|
|
func saveUpdateInfo(info *UpdateInfo) error {
|
|
data, _ := json.MarshalIndent(info, "", " ")
|
|
return os.WriteFile(getUpdateInfoPath(), data, 0644)
|
|
}
|
|
|
|
func handleUpdateCheck(w http.ResponseWriter, r *http.Request) {
|
|
clientVersion := r.URL.Query().Get("version")
|
|
info := loadUpdateInfo()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if info == nil || info.Version == clientVersion {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"update_available": false,
|
|
"current_version": clientVersion,
|
|
})
|
|
return
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"update_available": true,
|
|
"latest_version": info.Version,
|
|
"download_url": "/updates/" + info.FileName,
|
|
"sha256": info.SHA256,
|
|
"size": info.Size,
|
|
"release_notes": info.ReleaseNotes,
|
|
"mandatory": info.Mandatory,
|
|
})
|
|
}
|
|
|
|
func handleGetUpdateInfo(w http.ResponseWriter, r *http.Request) {
|
|
info := loadUpdateInfo()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if info == nil {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"published": false})
|
|
return
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"published": true, "info": info,
|
|
})
|
|
}
|
|
|
|
func handleDeleteUpdate(w http.ResponseWriter, r *http.Request) {
|
|
info := loadUpdateInfo()
|
|
if info != nil {
|
|
os.Remove(filepath.Join(updatesDir, info.FileName))
|
|
os.Remove(getUpdateInfoPath())
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"ok": true})
|
|
}
|
|
|
|
func handlePublishUpdate(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "POST only", 405)
|
|
return
|
|
}
|
|
r.ParseMultipartForm(200 << 20)
|
|
|
|
version := r.FormValue("version")
|
|
notes := r.FormValue("release_notes")
|
|
mandatory := r.FormValue("mandatory") == "true"
|
|
|
|
file, header, err := r.FormFile("file")
|
|
if err != nil {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "message": "文件上传失败"})
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
dstPath := filepath.Join(updatesDir, header.Filename)
|
|
dst, err := os.Create(dstPath)
|
|
if err != nil {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "message": "无法创建文件"})
|
|
return
|
|
}
|
|
|
|
hasher := sha256.New()
|
|
size, _ := io.Copy(io.MultiWriter(dst, hasher), file)
|
|
dst.Close()
|
|
hash := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
info := &UpdateInfo{
|
|
Version: version, Platform: "win64", FileName: header.Filename,
|
|
SHA256: hash, Size: size, ReleaseNotes: notes, Mandatory: mandatory,
|
|
PublishedAt: time.Now().Format("2006-01-02 15:04:05"),
|
|
}
|
|
saveUpdateInfo(info)
|
|
log.Printf("Update published: v%s (%s, %d bytes)", version, header.Filename, size)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "info": info})
|
|
}
|
|
|
|
func FormatSize(b int64) string {
|
|
if b < 1024 {
|
|
return fmt.Sprintf("%d B", b)
|
|
} else if b < 1024*1024 {
|
|
return fmt.Sprintf("%.1f KB", float64(b)/1024)
|
|
}
|
|
return fmt.Sprintf("%.1f MB", float64(b)/1024/1024)
|
|
}
|