清理旧API, 更改图像输出路径格式

This commit is contained in:
2025-01-14 04:05:13 +08:00
parent 3fbc9a3eae
commit 46c2fce7d5

View File

@@ -10,7 +10,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"time" "time"
"regexp" "regexp"
@@ -225,11 +224,6 @@ func main() {
return return
} }
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
w.Write([]byte("Hello World!"))
})
if config.GetBool("oss.local") { if config.GetBool("oss.local") {
fmt.Println("开启图像色调计算") fmt.Println("开启图像色调计算")
go api.CheckColorNullRows(0) go api.CheckColorNullRows(0)
@@ -261,266 +255,8 @@ func main() {
Pretty: false, Pretty: false,
}))) })))
// 获取图片信息列表(分页) // URL 格式: /image/{type}-{id}-{width}x{height}-{fit}.{format}?version
http.HandleFunc("/api/images", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/image", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
// 获取查询条件(忽略空值)
QueryConditions := func(key string) (list []string) {
for _, item := range strings.Split(r.URL.Query().Get(key), ",") {
if item != "" {
list = append(list, fmt.Sprintf("'%s'", item))
}
}
return list
}
var conditions strings.Builder
// 获取图片列表
var images ListView
var image_list []Image
images.Page, images.PageSize = stringToInt(r.URL.Query().Get("page"), 1), stringToInt(r.URL.Query().Get("pageSize"), 20)
var ids []int64
if similar := QueryConditions("similar"); len(similar) > 0 {
id, err := strconv.Atoi(strings.Trim(similar[0], "'"))
if err != nil {
log.Println("strconv.Atoi failed:", err)
return
}
// 如果指定以某个图片为基准的相似图片列表范围, 获取相似图片ID的列表
ids = (&Image{Id: id}).GetSimilarImagesIdList("default")
images.Total = len(ids)
// 按照分页取相应的图片ID
if len(ids) > images.Page*images.PageSize {
ids = ids[(images.Page-1)*images.PageSize : images.Page*images.PageSize]
} else {
ids = ids[(images.Page-1)*images.PageSize:]
}
idsStr := make([]string, len(ids))
for i, v := range ids {
idsStr[i] = strconv.FormatInt(v, 10)
}
if len(idsStr) > 0 {
if conditions.Len() > 0 {
conditions.WriteString(" AND")
} else {
conditions.WriteString(" WHERE")
}
conditions.WriteString(fmt.Sprintf(" id IN (%s)", strings.Join(idsStr, ","))) // 拼接查询条件
}
}
sql := fmt.Sprintf("SELECT id, width, height, content, update_time, create_time, user_id, article_id, article_category_top_id, praise_count, collect_count FROM web_images %s", conditions.String())
rows, err := mysqlConnection.Database.Query(sql)
if err != nil {
log.Println("获取图片列表失败", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer rows.Close()
for rows.Next() {
var image Image
rows.Scan(&image.Id, &image.Width, &image.Height, &image.Content, &image.UpdateTime, &image.CreateTime, &image.User.ID, &image.Article.Id, &image.ArticleCategoryTopId, &image.PraiseCount, &image.CollectCount)
image.UpdateTime = image.UpdateTime.UTC()
image.CreateTime = image.CreateTime.UTC()
image.Content = regexp.MustCompile(`http:`).ReplaceAllString(image.Content, "https:")
image_list = append(image_list, image)
}
// 如果使用了相似图片列表范围, 需要按照相似图片ID原本的顺序重新排序, 需要注意的是, 相似图片ID列表中可能会包含不在当前页的图片ID
if similar := QueryConditions("similar"); len(similar) > 0 {
var image_list_sorted []Image
for _, id := range ids {
for _, image := range image_list {
if image.Id == int(id) {
image_list_sorted = append(image_list_sorted, image)
}
}
}
image_list = image_list_sorted
}
// 用户ID, 文章ID
var user_ids []int
var article_ids []int
for _, image := range image_list {
user_ids = append(user_ids, image.User.ID)
article_ids = append(article_ids, image.Article.Id)
}
// 附加用户信息
users := models.QueryUserList(user_ids)
for i, image := range image_list {
for _, user := range users {
if image.User.ID == user.ID {
image_list[i].User = user
}
}
}
// 附加文章信息
articles := models.QueryArticleList(article_ids)
for i, image := range image_list {
for _, article := range articles {
if image.Article.Id == article.Id {
image_list[i].Article = article
}
}
}
// 将 []Image 转换为 []interface{}
images.List = make([]interface{}, len(image_list))
for i, v := range image_list {
images.List[i] = v
}
// 如果不是获取相似图像固定数量, 则从mysql获取总数
if similar := QueryConditions("similar"); len(similar) > 0 {
// 固定数量
} else {
// 获取总数
err = mysqlConnection.Database.QueryRow("SELECT COUNT(*) FROM web_images" + conditions.String()).Scan(&images.Total)
if err != nil {
log.Println("获取图片总数失败", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
// 是否有下一页
images.Next = images.Total > images.Page*images.PageSize
// 将对象转换为有缩进的JSON输出
data, err := json.MarshalIndent(images, "", " ")
if err != nil {
log.Println("转换图片列表失败", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(data)
})
// 获取标签列表
http.HandleFunc("/tags", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
// 标签的原理
// 1. 通过文章的 tag 字段, 获取所有的标签
// 2. 通过标签的 id, 获取标签的名称
// 热门权重指数的标签排序
// 1. 标签的权重指数 = (标签的文章数 * 标签的文章数) * 近期增幅
// 2. 标签的近期增幅 = (标签的文章数 - 标签的文章数) / 标签的文章数
// 标签是一个虚拟表, ORC 提取的数据都带有多个维度的比重概率(分布概率, 对比度概率, 文字大小, 文字重量, 词频概率, 词性概率, 词长概率, 词序概率)
// 经过规则过滤后, 用动态调参的指数计算乘积作为权重, 权重仍达到某个阈值的数据才会被视为标签
// 获取查询条件(忽略空值), 超级简洁写法
QueryConditions := func(key string) (list []string) {
if r.FormValue(key) != "" {
list = strings.Split(r.FormValue(key), ",")
}
return
}
// 拼接查询条件, 超级简洁写法
conditions := ""
if authors := QueryConditions("authors"); len(authors) > 0 {
conditions += fmt.Sprintf(" AND author IN (%s)", strings.Join(authors, ","))
}
if tags := QueryConditions("tags"); len(tags) > 0 {
conditions += fmt.Sprintf(" AND tag IN (%s)", strings.Join(tags, ","))
}
if categories := QueryConditions("categories"); len(categories) > 0 {
conditions += fmt.Sprintf(" AND categorie IN (%s)", strings.Join(categories, ","))
}
// 获取标签列表
var tags ListView
tags.Page, tags.PageSize = stringToInt(r.FormValue("page"), 1), stringToInt(r.FormValue("pageSize"), 20)
rows, err := mysqlConnection.Database.Query("SELECT id, name, update_time, create_time FROM web_tags"+conditions+" ORDER BY id DESC LIMIT ?, ?", (tags.Page-1)*tags.PageSize, tags.PageSize)
if err != nil {
log.Println(err)
return
}
defer rows.Close()
for rows.Next() {
var tag Tag
if err := rows.Scan(&tag.Id, &tag.Name, &tag.UpdateTime, &tag.CreateTime); err != nil {
log.Println(err)
continue
}
tags.List = append(tags.List, tag)
}
if err := rows.Err(); err != nil {
log.Println(err)
return
}
// 获取总数
if err := mysqlConnection.Database.QueryRow("SELECT COUNT(*) FROM web_tags" + conditions).Scan(&tags.Total); err != nil {
log.Println(err)
return
}
// 是否有下一页
tags.Next = tags.Total > tags.Page*tags.PageSize
// 将对象转换为有缩进的JSON输出
json, err := json.MarshalIndent(tags, "", " ")
if err != nil {
log.Println(err)
return
}
// 输出JSON
w.Header().Set("Content-Type", "application/json")
w.Write(json)
})
// URL 格式: /img/{type}-{id}.{format}?width=320&height=320&fit=cover
http.HandleFunc("/img/", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
reg := regexp.MustCompile(`^/img/([0-9a-zA-Z]+)-([0-9a-zA-Z]+).(jpg|jpeg|png|webp)$`)
matches := reg.FindStringSubmatch(r.URL.Path)
if len(matches) != 4 {
log.Println("URL 格式错误", r.URL.Path)
http.Error(w, "URL 格式错误", http.StatusNotFound)
return
}
group, id, format, width, height, fit := matches[1], matches[2], matches[3], stringToInt(r.URL.Query().Get("width"), 0), stringToInt(r.URL.Query().Get("height"), 0), r.URL.Query().Get("fit")
content, err := mysqlConnection.GetImageContent(group, id)
if err != nil {
log.Println("获取图片失败", format, err)
http.Error(w, err.Error(), http.StatusNotFound)
return
}
var img models.Image
if err := img.Init(content); err != nil {
log.Println("初始化图片失败", format, err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
data, err := img.ToWebP(width, height, fit)
if err != nil {
log.Println("转换图片失败", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "image/webp")
w.Header().Set("Cache-Control", "max-age=31536000")
w.Write(data)
})
// URL 格式: /webp/{type}-{id}-{version}-{width}-{height}-{fit}.{format}
http.HandleFunc("/webp/", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志 defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
// 如果本地文件存在,直接输出 // 如果本地文件存在,直接输出
@@ -530,23 +266,23 @@ func main() {
return return
} }
reg := regexp.MustCompile(`^/webp/([0-9a-zA-Z]+)-([0-9a-zA-Z]+)-([0-9a-zA-Z]+)-([0-9]+)-([0-9]+)-([a-zA-Z]+).(jpg|jpeg|png|webp)$`) reg := regexp.MustCompile(`^/image/([a-z]+)-([0-9]+)-([0-9]+)x([0-9]+)-([a-z]+).(jpg|jpeg|png|webp)$`)
matches := reg.FindStringSubmatch(r.URL.Path) matches := reg.FindStringSubmatch(r.URL.Path)
if len(matches) != 8 { if len(matches) != 7 {
log.Println("URL 格式错误", matches) log.Println("URL 格式错误", matches)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
group, id, version, width, height, fit, format := matches[1], matches[2], matches[3], stringToInt(matches[4], 0), stringToInt(matches[5], 0), matches[6], matches[7] group, id, width, height, fit, format := matches[1], matches[2], stringToInt(matches[3], 0), stringToInt(matches[4], 0), matches[5], matches[6]
content, err := mysqlConnection.GetImageContent(group, id) content, err := mysqlConnection.GetImageContent(group, id)
if err != nil { if err != nil {
log.Println("获取图片失败", version, format, err) log.Println("获取图片失败", format, err)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
var img models.Image var img models.Image
if err := img.Init(content); err != nil { if err := img.Init(content); err != nil {
log.Println("初始化图片失败", version, format, err) log.Println("初始化图片失败", format, err)
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
@@ -556,8 +292,6 @@ func main() {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
// 将生成的 WebP 文件保存到本地
err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm) err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
if err != nil { if err != nil {
log.Println("创建文件目录失败:", err) log.Println("创建文件目录失败:", err)
@@ -570,10 +304,8 @@ func main() {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
// 返回 WebP 图片数据
w.Header().Set("Content-Type", "image/webp") w.Header().Set("Content-Type", "image/webp")
w.Header().Set("Cache-Control", "max-age=31536000") w.Header().Set("Cache-Control", "max-age=604800")
w.Write(data) w.Write(data)
}) })