清理旧API, 更改图像输出路径格式
This commit is contained in:
284
bin/main.go
284
bin/main.go
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
@@ -225,11 +224,6 @@ func main() {
|
||||
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") {
|
||||
fmt.Println("开启图像色调计算")
|
||||
go api.CheckColorNullRows(0)
|
||||
@@ -261,266 +255,8 @@ func main() {
|
||||
Pretty: false,
|
||||
})))
|
||||
|
||||
// 获取图片信息列表(分页)
|
||||
http.HandleFunc("/api/images", 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) {
|
||||
// URL 格式: /image/{type}-{id}-{width}x{height}-{fit}.{format}?version
|
||||
http.HandleFunc("/image", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
|
||||
|
||||
// 如果本地文件存在,直接输出
|
||||
@@ -530,23 +266,23 @@ func main() {
|
||||
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)
|
||||
if len(matches) != 8 {
|
||||
if len(matches) != 7 {
|
||||
log.Println("URL 格式错误", matches)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Println("获取图片失败", version, format, err)
|
||||
log.Println("获取图片失败", format, err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
var img models.Image
|
||||
if err := img.Init(content); err != nil {
|
||||
log.Println("初始化图片失败", version, format, err)
|
||||
log.Println("初始化图片失败", format, err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
@@ -556,8 +292,6 @@ func main() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 将生成的 WebP 文件保存到本地
|
||||
err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
|
||||
if err != nil {
|
||||
log.Println("创建文件目录失败:", err)
|
||||
@@ -570,10 +304,8 @@ func main() {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回 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)
|
||||
})
|
||||
|
||||
|
Reference in New Issue
Block a user