This commit is contained in:
2026-04-02 15:16:34 +08:00
commit 4facf82956
58 changed files with 6630 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
package util
import (
"database/sql"
"errors"
"fmt"
"log"
"os"
"strings"
"sync"
)
func CreateLog(db *sql.DB, content string) error {
SqliteLock.Lock()
_, err := db.Exec(`INSERT INTO "log" ("content") VALUES (?)`, content)
SqliteLock.Unlock()
return err
}
func GetFields(db *sql.DB, names ...string) (map[string]string, error) {
if len(names) == 0 {
return nil, nil
}
placeholders := make([]string, len(names))
for i := 0; i < len(names); i++ {
placeholders[i] = "?"
}
query := fmt.Sprintf(`SELECT "name", "value" FROM "field" WHERE "name" IN (%s)`, strings.Join(placeholders, ","))
values := make([]interface{}, len(names))
for i := 0; i < len(names); i++ {
values[i] = names[i]
}
SqliteLock.Lock()
row, err := db.Query(query, values...)
SqliteLock.Unlock()
if err != nil {
return nil, err
}
defer row.Close()
var name, value string
fields := make(map[string]string)
for row.Next() {
if err := row.Scan(&name, &value); err != nil {
return nil, err
}
fields[name] = value
}
return fields, nil
}
func SaveFields(db *sql.DB, data [][2]string) error {
if len(data) == 0 {
return nil
}
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
stmt, err := tx.Prepare(`INSERT OR REPLACE INTO "field" ("name", "value") VALUES (?, ?)`)
if err != nil {
return err
}
defer stmt.Close()
for _, d := range data {
SqliteLock.Lock()
_, err = stmt.Exec(d[0], d[1])
SqliteLock.Unlock()
if err != nil {
return err
}
}
return nil
}
// GetCurrentFolder 获取数据库中的下载保存路径,如果不存在则将默认路径保存到数据库
func GetCurrentFolder(db *sql.DB) (string, error) {
var folder string
SqliteLock.Lock()
err := db.QueryRow(`SELECT "value" FROM "field" WHERE "name" = 'download_folder'`).Scan(&folder)
SqliteLock.Unlock()
if err != nil && err == sql.ErrNoRows {
folder, err = GetDefaultDownloadFolder()
if err != nil {
return "", err
}
err = os.MkdirAll(folder, os.ModePerm)
if err != nil {
return "", err
}
err = SaveDownloadFolder(db, folder)
if err != nil {
return "", err
}
return folder, nil
}
err = os.MkdirAll(folder, os.ModePerm)
if err != nil {
return "", err
}
return folder, nil
}
// SaveDownloadFolder 保存下载路径,不存在则自动创建
func SaveDownloadFolder(db *sql.DB, downloadFolder string) error {
_, err := os.Stat(downloadFolder)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(downloadFolder, os.ModePerm)
if err != nil {
return err
}
}
return err
}
SqliteLock.Lock()
_, err = db.Exec(`INSERT OR REPLACE INTO "field" ("name", "value") VALUES ('download_folder', ?)`, downloadFolder)
SqliteLock.Unlock()
return err
}
var SqliteLock sync.Mutex
func MustGetDB(path ...string) *sql.DB {
pathStr := ""
if len(path) == 0 {
pathStr = "./data.db"
} else if len(path) > 1 {
log.Fatalln(errors.New("len(path) <= 1"))
} else {
pathStr = path[0]
}
db, err := sql.Open("sqlite", pathStr)
if err != nil {
log.Fatalln("sql.Open:", err)
}
return db
}
+36
View File
@@ -0,0 +1,36 @@
package util
type FieldUtil struct{}
func (f FieldUtil) AllowSelect() []string {
return []string{
"download_folder",
}
}
func (f FieldUtil) AllowUpdate() []string {
return []string{
"download_folder",
}
}
func (f FieldUtil) IsAllow(allFields []string, names ...string) bool {
allowedFields := make(map[string]struct{})
for _, field := range allFields {
allowedFields[field] = struct{}{}
}
for _, name := range names {
if _, exists := allowedFields[name]; !exists {
return false
}
}
return true
}
func (f FieldUtil) IsAllowSelect(names ...string) bool {
return f.IsAllow(f.AllowSelect(), names...)
}
func (f FieldUtil) IsAllowUpdate(names ...string) bool {
return f.IsAllow(f.AllowUpdate(), names...)
}
+26
View File
@@ -0,0 +1,26 @@
package res_error
import (
"net/http"
"bilidown/util"
)
// Send 发送异常响应
func Send(w http.ResponseWriter, message string) {
util.Res{Message: message, Success: false}.Write(w)
}
const (
BvidFormatError = "错误的 Bvid 格式"
URLFormatError = "错误的 URL 格式"
MidFormatError = "错误的 Mid 格式"
SeasonIdFormatError = "错误的 SeasonId 格式"
ParamError = "参数错误"
MethodNotAllowError = "不允许的请求方式"
NoLocationError = "无重定向目标地址"
FileNotFountError = "文件不存在"
FileTypeNotAllowError = "不允许的文件类型"
SystemError = "系统错误"
NotLogin = "未登录"
)
+23
View File
@@ -0,0 +1,23 @@
package util
import (
"encoding/json"
"net/http"
)
// 统一的 JSON 响应结构
type Res struct {
Success bool `json:"success"`
Message string `json:"message"`
Data any `json:"data"`
}
// 发送响应
func (r Res) Write(w http.ResponseWriter) {
bs, err := json.Marshal(r)
if err != nil {
w.Write([]byte(`{"success":false,"message":"系统错误","data":null}`))
}
w.Header().Set("Content-Type", "application/json")
w.Write(bs)
}
+28
View File
@@ -0,0 +1,28 @@
package util
import "sync"
type Semaphore struct {
ch chan struct{}
wg sync.WaitGroup
}
func NewSemaphore(concurrency int) *Semaphore {
return &Semaphore{
ch: make(chan struct{}, concurrency),
}
}
func (s *Semaphore) Acquire() {
s.ch <- struct{}{}
s.wg.Add(1)
}
func (s *Semaphore) Release() {
<-s.ch
s.wg.Done()
}
func (s *Semaphore) Wait() {
s.wg.Wait()
}
+92
View File
@@ -0,0 +1,92 @@
package util
import (
"crypto/md5"
"encoding/hex"
"errors"
"net/http"
"net/url"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"bilidown/common"
)
func CheckBvidFormat(bvid string) bool {
return regexp.MustCompile("^BV1[a-zA-Z0-9]+").MatchString(bvid)
}
// GetDefaultDownloadFolder 获取默认下载路径
func GetDefaultDownloadFolder() (string, error) {
return filepath.Abs("./download")
}
func IsNumber(str string) bool {
_, err := strconv.Atoi(str)
return err == nil
}
// IsValidURL 判断字符串是否为合法的URL
func IsValidURL(u string) bool {
_, err := url.ParseRequestURI(u)
return err == nil
}
// IsValidFormatCode 判断格式码是否合法
func IsValidFormatCode(format common.MediaFormat) bool {
allowed := []common.MediaFormat{6, 16, 32, 64, 74, 80, 112, 116, 120, 125, 126, 127}
for _, v := range allowed {
if v == format {
return true
}
}
return false
}
// FilterFileName 过滤字符串中的特殊字符,使其允许作为文件名。
func FilterFileName(fileName string) string {
return regexp.MustCompile(`[\\/:*?"<>|\n]`).ReplaceAllString(fileName, "")
}
// GetFFmpegPath 获取可用的 FFmpeg 执行路径。
func GetFFmpegPath() (string, error) {
if err := exec.Command("ffmpeg", "-version").Run(); err == nil {
return "ffmpeg", nil
}
if err := exec.Command("bin/ffmpeg", "-version").Run(); err == nil {
return "bin/ffmpeg", nil
}
return "", errors.New("ffmpeg not found")
}
// GetRedirectedLocation 获取响应头中的 Location,不会自动跟随重定向。
func GetRedirectedLocation(url string) (string, error) {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
request, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return "", err
}
response, err := client.Do(request)
if err != nil {
return "", err
}
if locationURL, err := response.Location(); err != nil {
return "", err
} else {
return locationURL.String(), nil
}
}
func MD5Hash(str string) string {
hasher := md5.New()
hasher.Write([]byte(str))
hash := hasher.Sum(nil)
hashString := hex.EncodeToString(hash)
return hashString
}
+29
View File
@@ -0,0 +1,29 @@
package util_test
import (
"fmt"
"os"
"testing"
"bilidown/common"
"bilidown/util"
)
func TestRandomString(t *testing.T) {
for i := 4; i < 10; i++ {
for j := 0; j < 3; j++ {
str := common.RandomString(i)
t.Log(str)
}
t.Log("\n")
}
}
func TestGetRedirectedLocation(t *testing.T) {
os.Setenv("https_proxy", "http://192.168.1.5:9000")
if location, err := util.GetRedirectedLocation("https://b23.tv/Ga6sbzT"); err != nil {
t.Error(err)
} else {
fmt.Println(location)
}
}