diff --git a/bin/main.go b/bin/main.go index 09d544a..6666929 100644 --- a/bin/main.go +++ b/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) })