refactor: restructure project to standard Go layout
- Reorganize code into cmd/bilidown and internal/ packages - Rename client/ to web/ for frontend source - Remove systray dependency for headless web service - Embed static files into binary using go:embed - Update import paths to use internal/ prefix - Update .gitignore with common patterns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"bilidown/internal/bilibili"
|
||||
"bilidown/internal/util"
|
||||
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
func getQRInfo(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
|
||||
client := bilibili.BiliClient{}
|
||||
qrInfo, err := client.NewQRInfo()
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
imageData, err := qrcode.Encode(qrInfo.URL, qrcode.Medium, 256)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
base64Str := base64.StdEncoding.EncodeToString(imageData)
|
||||
util.Res{
|
||||
Success: true,
|
||||
Message: "获取成功",
|
||||
Data: struct {
|
||||
Key string `json:"key"`
|
||||
Image string `json:"image"`
|
||||
}{
|
||||
Key: qrInfo.QrcodeKey,
|
||||
Image: "data:image/png;base64," + base64Str,
|
||||
}}.Write(w)
|
||||
}
|
||||
|
||||
func checkLogin(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
sessdata, err := bilibili.GetSessdata(db)
|
||||
if err != nil || sessdata == "" {
|
||||
util.Res{Success: false, Message: "未登录"}.Write(w)
|
||||
return
|
||||
}
|
||||
client := bilibili.BiliClient{SESSDATA: sessdata}
|
||||
check, err := client.CheckLogin()
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
if check {
|
||||
util.Res{Success: true, Message: "登录成功"}.Write(w)
|
||||
} else {
|
||||
util.Res{Success: false, Message: "登录失败"}.Write(w)
|
||||
}
|
||||
}
|
||||
|
||||
// getQRStatus 获取二维码状态
|
||||
func getQRStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ParseForm() != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
key := r.FormValue("key")
|
||||
if key == "" {
|
||||
util.Res{Success: false, Message: "key 不能为空"}.Write(w)
|
||||
return
|
||||
}
|
||||
client := bilibili.BiliClient{}
|
||||
qrStatus, sessdata, err := client.GetQRStatus(key)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
if qrStatus.Code != bilibili.QR_SUCCESS {
|
||||
util.Res{Success: false, Message: qrStatus.Message}.Write(w)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
err = bilibili.SaveSessdata(db, sessdata)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "登录成功"}.Write(w)
|
||||
}
|
||||
|
||||
func logout(w http.ResponseWriter, r *http.Request) {
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
err := bilibili.SaveSessdata(db, "")
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "退出成功"}.Write(w)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"bilidown/internal/util"
|
||||
"bilidown/internal/util/res_error"
|
||||
)
|
||||
|
||||
func API() *http.ServeMux {
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("/getVideoInfo", getVideoInfo)
|
||||
router.HandleFunc("/getSeasonInfo", getSeasonInfo)
|
||||
router.HandleFunc("/getQRInfo", getQRInfo)
|
||||
router.HandleFunc("/getQRStatus", getQRStatus)
|
||||
router.HandleFunc("/checkLogin", checkLogin)
|
||||
router.HandleFunc("/getPlayInfo", getPlayInfo)
|
||||
router.HandleFunc("/createTask", createTask)
|
||||
router.HandleFunc("/getActiveTask", getActiveTask)
|
||||
router.HandleFunc("/getTaskList", getTaskList)
|
||||
router.HandleFunc("/showFile", showFile)
|
||||
router.HandleFunc("/getFields", getFields)
|
||||
router.HandleFunc("/saveFields", saveFields)
|
||||
router.HandleFunc("/logout", logout)
|
||||
router.HandleFunc("/quit", quit)
|
||||
router.HandleFunc("/getPopularVideos", getPopularVideos)
|
||||
router.HandleFunc("/deleteTask", deleteTask)
|
||||
router.HandleFunc("/getRedirectedLocation", getRedirectedLocation)
|
||||
router.HandleFunc("/downloadVideo", downloadVideo)
|
||||
router.HandleFunc("/getSeasonsArchivesListFirstBvid", getSeasonsArchivesListFirstBvid)
|
||||
router.HandleFunc("/getFavList", getFavList)
|
||||
return router
|
||||
}
|
||||
|
||||
func getRedirectedLocation(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ParseForm() != nil {
|
||||
res_error.Send(w, res_error.ParamError)
|
||||
return
|
||||
}
|
||||
url := r.FormValue("url")
|
||||
if !util.IsValidURL(url) {
|
||||
res_error.Send(w, res_error.URLFormatError)
|
||||
return
|
||||
}
|
||||
if location, err := util.GetRedirectedLocation(url); err != nil {
|
||||
res_error.Send(w, res_error.NoLocationError)
|
||||
return
|
||||
} else {
|
||||
util.Res{Success: true, Message: "获取成功", Data: location}.Write(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func quit(w http.ResponseWriter, r *http.Request) {
|
||||
util.Res{Success: true, Message: "退出成功"}.Write(w)
|
||||
go func() {
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
func getFields(w http.ResponseWriter, r *http.Request) {
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
|
||||
fields, err := util.GetFields(db, util.FieldUtil{}.AllowSelect()...)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Data: fields}.Write(w)
|
||||
}
|
||||
|
||||
func saveFields(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
util.Res{Success: false, Message: "不支持的请求方法"}.Write(w)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
var body [][2]string
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&body)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
|
||||
fu := util.FieldUtil{}
|
||||
|
||||
for _, d := range body {
|
||||
if !fu.IsAllowUpdate(d[0]) {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("字段 %s 不允许修改", d[0])}.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
if d[0] == "download_folder" {
|
||||
if _, err := os.Stat(d[1]); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(d[1], os.ModePerm); err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("目录创建失败:%s", d[1])}.Write(w)
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("路径设置失败:%v", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = util.SaveFields(db, body)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "保存成功"}.Write(w)
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"bilidown/internal/task"
|
||||
"bilidown/internal/util"
|
||||
)
|
||||
|
||||
func createTask(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if r.Method != http.MethodPost {
|
||||
util.Res{Success: false, Message: "不支持的请求方法"}.Write(w)
|
||||
return
|
||||
}
|
||||
var body []task.TaskInDB
|
||||
err := json.NewDecoder(r.Body).Decode(&body)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
for _, item := range body {
|
||||
if !util.CheckBvidFormat(item.Bvid) {
|
||||
util.Res{Success: false, Message: "bvid 格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
if item.Cover == "" || item.Title == "" || item.Owner == "" {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
}
|
||||
|
||||
if !util.IsValidURL(item.Cover) {
|
||||
util.Res{Success: false, Message: "封面链接格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
if !util.IsValidURL(item.Audio) {
|
||||
util.Res{Success: false, Message: "音频链接格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
if !util.IsValidURL(item.Video) {
|
||||
util.Res{Success: false, Message: "视频链接格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
if !util.IsValidFormatCode(item.Format) {
|
||||
util.Res{Success: false, Message: "清晰度代码错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
item.Folder, err = util.GetCurrentFolder(db)
|
||||
item.Status = "waiting"
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("util.GetCurrentFolder: %v.", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
_task := task.Task{TaskInDB: item}
|
||||
_task.Title = util.FilterFileName(_task.Title)
|
||||
err = _task.Create(db)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("_task.Create: %v.", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
go _task.Start()
|
||||
}
|
||||
util.Res{Success: true, Message: "创建成功"}.Write(w)
|
||||
}
|
||||
|
||||
func getActiveTask(w http.ResponseWriter, r *http.Request) {
|
||||
util.Res{Success: true, Data: task.GlobalTaskList}.Write(w)
|
||||
}
|
||||
|
||||
func getTaskList(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
page, err := strconv.Atoi(r.FormValue("page"))
|
||||
if err != nil {
|
||||
page = 0
|
||||
}
|
||||
pageSize, err := strconv.Atoi(r.FormValue("pageSize"))
|
||||
if err != nil {
|
||||
pageSize = 360
|
||||
}
|
||||
tasks, err := task.GetTaskList(db, page, pageSize)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: tasks}.Write(w)
|
||||
}
|
||||
|
||||
// showFile 调用 Explorer 查看文件位置
|
||||
func showFile(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
filePath := r.FormValue("filePath")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
// 根据操作系统选择命令
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// Windows 使用 explorer
|
||||
cmd = exec.Command("explorer", "/select,", filePath)
|
||||
case "darwin":
|
||||
// macOS 使用 open
|
||||
cmd = exec.Command("open", "-R", filePath)
|
||||
case "linux":
|
||||
// Linux 使用 xdg-open
|
||||
cmd = exec.Command("xdg-open", filePath)
|
||||
default:
|
||||
util.Res{Success: false, Message: "不支持的操作系统"}.Write(w)
|
||||
return
|
||||
}
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "操作成功"}.Write(w)
|
||||
}
|
||||
|
||||
func deleteTask(w http.ResponseWriter, r *http.Request) {
|
||||
taskIDStr := r.FormValue("id")
|
||||
taskID, err := strconv.Atoi(taskIDStr)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
|
||||
_task, err := task.GetTask(db, taskID)
|
||||
if err == sql.ErrNoRows {
|
||||
util.Res{Success: true, Message: "数据库中没有该条记录,所以本次操作被忽略,可以算作成功。"}.Write(w)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("task.GetTask: %v", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
filePath := _task.FilePath()
|
||||
err = os.Remove(filePath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("文件删除失败 os.Remove: %v", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
err = task.DeleteTask(db, taskID)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("task.DeleteTask: %v", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "删除成功"}.Write(w)
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"bilidown/internal/bilibili"
|
||||
"bilidown/internal/util"
|
||||
"bilidown/internal/util/res_error"
|
||||
)
|
||||
|
||||
// getVideoInfo 通过 BV 号获取视频信息
|
||||
func getVideoInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ParseForm() != nil {
|
||||
res_error.Send(w, res_error.ParamError)
|
||||
return
|
||||
}
|
||||
bvid := r.FormValue("bvid")
|
||||
if !util.CheckBvidFormat(bvid) {
|
||||
res_error.Send(w, res_error.BvidFormatError)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
|
||||
sessdata, err := bilibili.GetSessdata(db)
|
||||
if err != nil || sessdata == "" {
|
||||
res_error.Send(w, res_error.NotLogin)
|
||||
return
|
||||
}
|
||||
client := bilibili.BiliClient{SESSDATA: sessdata}
|
||||
videoInfo, err := client.GetVideoInfo(bvid)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: videoInfo}.Write(w)
|
||||
}
|
||||
|
||||
// getSeasonInfo 通过 EP 号或 SS 号获取视频信息
|
||||
func getSeasonInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ParseForm() != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
var epid int
|
||||
epid, err := strconv.Atoi(r.FormValue("epid"))
|
||||
if r.FormValue("epid") != "" && err != nil {
|
||||
util.Res{Success: false, Message: "epid 格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
var ssid int
|
||||
if epid == 0 {
|
||||
ssid, err = strconv.Atoi(r.FormValue("ssid"))
|
||||
if r.FormValue("ssid") != "" && err != nil {
|
||||
util.Res{Success: false, Message: "ssid 格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
sessdata, err := bilibili.GetSessdata(db)
|
||||
if err != nil || sessdata == "" {
|
||||
res_error.Send(w, res_error.NotLogin)
|
||||
return
|
||||
}
|
||||
|
||||
client := bilibili.BiliClient{SESSDATA: sessdata}
|
||||
seasonInfo, err := client.GetSeasonInfo(epid, ssid)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: seasonInfo}.Write(w)
|
||||
}
|
||||
|
||||
// getPlayInfo 通过 BVID 和 CID 获取视频播放信息
|
||||
func getPlayInfo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ParseForm() != nil {
|
||||
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
|
||||
bvid := r.FormValue("bvid")
|
||||
if !util.CheckBvidFormat(bvid) {
|
||||
util.Res{Success: false, Message: "bvid 格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
cid, err := strconv.Atoi(r.FormValue("cid"))
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: "cid 格式错误"}.Write(w)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
sessdata, err := bilibili.GetSessdata(db)
|
||||
if err != nil || sessdata == "" {
|
||||
res_error.Send(w, res_error.NotLogin)
|
||||
return
|
||||
}
|
||||
client := bilibili.BiliClient{SESSDATA: sessdata}
|
||||
playInfo, err := client.GetPlayInfo(bvid, cid)
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: fmt.Sprintf("client.GetPlayInfo: %v", err)}.Write(w)
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: playInfo}.Write(w)
|
||||
}
|
||||
|
||||
func getPopularVideos(w http.ResponseWriter, r *http.Request) {
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
sessdata, err := bilibili.GetSessdata(db)
|
||||
if err != nil || sessdata == "" {
|
||||
res_error.Send(w, res_error.NotLogin)
|
||||
return
|
||||
}
|
||||
|
||||
client := bilibili.BiliClient{SESSDATA: sessdata}
|
||||
videos, err := client.GetPopularVideos()
|
||||
if err != nil {
|
||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||
return
|
||||
}
|
||||
bvidList := make([]string, 0)
|
||||
for _, v := range videos {
|
||||
bvidList = append(bvidList, v.Bvid)
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: bvidList}.Write(w)
|
||||
}
|
||||
|
||||
var downloadVideo = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Query().Get("path")
|
||||
safePath := filepath.Clean(path)
|
||||
safePath = strings.ReplaceAll(safePath, "\\", "/")
|
||||
http.ServeFile(w, r, safePath)
|
||||
})
|
||||
|
||||
var getSeasonsArchivesListFirstBvid = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var mid int
|
||||
var seasonId int
|
||||
var err error
|
||||
if mid, err = strconv.Atoi(r.URL.Query().Get("mid")); err != nil {
|
||||
res_error.Send(w, res_error.MidFormatError)
|
||||
return
|
||||
}
|
||||
if seasonId, err = strconv.Atoi(r.URL.Query().Get("seasonId")); err != nil {
|
||||
res_error.Send(w, res_error.SeasonIdFormatError)
|
||||
return
|
||||
}
|
||||
client := bilibili.BiliClient{}
|
||||
bvid, err := client.GetSeasonsArchivesListFirstBvid(mid, seasonId)
|
||||
if err != nil {
|
||||
res_error.Send(w, fmt.Sprintf("client.GetSeasonsArchivesList: %v", err))
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: bvid}.Write(w)
|
||||
})
|
||||
|
||||
var getFavList = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
mediaId, err := strconv.Atoi(r.URL.Query().Get("mediaId"))
|
||||
if err != nil {
|
||||
res_error.Send(w, res_error.ParamError)
|
||||
return
|
||||
}
|
||||
db := util.MustGetDB()
|
||||
defer db.Close()
|
||||
sessdata, err := bilibili.GetSessdata(db)
|
||||
if err != nil || sessdata == "" {
|
||||
res_error.Send(w, res_error.NotLogin)
|
||||
return
|
||||
}
|
||||
client := bilibili.BiliClient{SESSDATA: sessdata}
|
||||
favList, err := client.GetFavlist(mediaId)
|
||||
if err != nil {
|
||||
res_error.Send(w, err.Error())
|
||||
return
|
||||
}
|
||||
util.Res{Success: true, Message: "获取成功", Data: favList}.Write(w)
|
||||
})
|
||||
Reference in New Issue
Block a user