package main import ( "database/sql" "image" "log" "net/http" "os" "runtime" "regexp" "strconv" "github.com/chai2010/webp" "github.com/disintegration/imaging" _ "github.com/go-sql-driver/mysql" giftowebp "github.com/sizeofint/gif-to-webp" "github.com/spf13/viper" ) func init() { // 设置日志格式 log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) } // 实现一个 web api 服务(获取指定尺寸的图片) func main() { runtime.GOMAXPROCS(runtime.NumCPU()) // 读取配置文件yaml viper.SetConfigName("config") // 设置配置文件的名字 viper.SetConfigType("yaml") // 设置配置文件的格式 viper.AddConfigPath("./data") // 设置配置文件所在的路径 if err := viper.ReadInConfig(); err != nil { log.Println("读取配置文件失败", err) return } user := viper.Get("mysql.user").(string) password := viper.Get("mysql.password").(string) host := viper.Get("mysql.host").(string) port := viper.Get("mysql.port").(int) database := viper.Get("mysql.database").(string) sqlconf := user + ":" + password + "@tcp(" + host + ":" + strconv.Itoa(port) + ")/" + database + "?charset=utf8mb4&parseTime=True&loc=Local" // 连接数据库 db, err := sql.Open("mysql", sqlconf) if err != nil { log.Println("连接数据库失败", err) return } // 从 web_images 表读取图片 id, version, width, height, format rows, err := db.Query("SELECT id, width, height, content FROM web_images WHERE id = 8888") if err != nil { log.Println("查询数据库失败", err) return } defer rows.Close() // 遍历查询结果 for rows.Next() { var id, width, height, content string if err = rows.Scan(&id, &width, &height, &content); err != nil { log.Println("读取数据库失败", err) return } log.Println(id, width, height, content) } http.HandleFunc("/img/", func(w http.ResponseWriter, r *http.Request) { // URL 格式: /img/{id}-{version}@{倍图}{宽度}.{格式} reg := regexp.MustCompile(`^/img/([0-9a-zA-Z]+)-([0-9a-zA-Z]+)@([0-9]{1,4})x([0-9]{1,4}).(jpg|jpeg|png|webp)$`) matches := reg.FindStringSubmatch(r.URL.Path) if len(matches) != 6 { log.Println("URL 格式错误", matches) w.WriteHeader(http.StatusBadRequest) return } // 正则表达式获取参数 id, version, multiple_str, width_str, format := matches[1], matches[2], matches[3], matches[4], matches[5] multiple, err := strconv.Atoi(multiple_str) if err != nil { log.Println("倍图参数错误", multiple_str) w.WriteHeader(http.StatusBadRequest) return } width, err := strconv.Atoi(width_str) if err != nil { log.Println("宽度参数错误", width_str) w.WriteHeader(http.StatusBadRequest) return } log.Println(id, version, multiple, width, format) // 打開輸入文件 file, err := os.Open("data/test.gif") if err != nil { log.Println("打开文件失败", err) w.WriteHeader(http.StatusBadRequest) return } defer file.Close() // 將輸入文件解碼為 image.Image img, ext, err := image.Decode(file) if err != nil { log.Println("解码图像失败", err) w.WriteHeader(http.StatusBadRequest) return } // 如果是 GIF 格式的动态图片,直接返回 if ext == "gif" { var gifBin, webpBuf []byte if gifBin, err = os.ReadFile("data/test.gif"); err != nil { log.Println("读取文件失败", err) w.WriteHeader(http.StatusBadRequest) return } converter := giftowebp.NewConverter() converter.LoopCompatibility = false // 是否兼容循环动画 converter.WebPConfig.SetLossless(0) // 0 有损压缩 1无损压缩 converter.WebPConfig.SetMethod(6) // 压缩速度 0-6 0最快 6质量最好 converter.WebPConfig.SetQuality(10) // 压缩质量 0-100 converter.WebPAnimEncoderOptions.SetKmin(9) converter.WebPAnimEncoderOptions.SetKmax(17) if webpBuf, err = converter.Convert(gifBin); err != nil { log.Println("编码图像失败", err.Error()) w.WriteHeader(http.StatusBadRequest) return } w.Write(webpBuf) return } // 將圖像轉換為 RGBA 格式(如果尚未採用該格式) if _, ok := img.(*image.RGBA); !ok { img = imaging.Clone(img) } // 將圖像調整為指定宽度的 WebP 格式並將其寫入輸出到瀏覽器(高度自适应) if err = webp.Encode(w, imaging.Resize(img, multiple*width, 0, imaging.Lanczos), &webp.Options{Quality: 80}); err != nil { log.Println("编码图像失败", err.Error()) w.WriteHeader(http.StatusBadRequest) return } }) log.Println("Server is running at http://localhost:6001") http.ListenAndServe(":6001", nil) }