test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. // Package controllers - test.go
  2. //
  3. // 📝 NOTE (说明)
  4. // ================================================================================
  5. // 本文件包含批量数据处理和测试工具函数。
  6. // 这些函数仅用于开发和测试环境,不应在生产环境中调用。
  7. //
  8. // TODO: 建议将这些工具函数迁移到独立的CLI工具或脚本中。
  9. // ================================================================================
  10. package controllers
  11. import (
  12. "fmt"
  13. "image"
  14. "image/jpeg"
  15. "image/png"
  16. "io"
  17. "net/http"
  18. "os"
  19. "path/filepath"
  20. "regexp"
  21. "strconv"
  22. "strings"
  23. "time"
  24. "shudao-chat-go/models"
  25. "shudao-chat-go/utils"
  26. "github.com/aws/aws-sdk-go/aws"
  27. "github.com/aws/aws-sdk-go/aws/credentials"
  28. "github.com/aws/aws-sdk-go/aws/session"
  29. "github.com/aws/aws-sdk-go/service/s3"
  30. )
  31. // OSS配置信息
  32. // OSS配置信息
  33. var (
  34. testOssBucket string
  35. testOssAccessKey string
  36. testOssSecretKey string
  37. testOssEndpoint string
  38. testOssRegion = "us-east-1"
  39. )
  40. func init() {
  41. // 注意: 这里复用主配置的OSS设置
  42. // 如果测试需要独立的OSS配置,建议在app.conf中添加 test_oss_... 配置项
  43. // 目前为了简化,直接使用主OSS配置
  44. config := utils.GetOSSConfig()
  45. testOssBucket = config["bucket"]
  46. testOssAccessKey = config["access_key"]
  47. testOssSecretKey = config["secret_key"]
  48. testOssEndpoint = config["endpoint"]
  49. }
  50. // 批量上传文件到OSS
  51. func BatchUploadFilesToOSS() {
  52. fmt.Println("=== 开始批量上传文件到OSS ===")
  53. // 设置文件文件夹路径
  54. fileFolder := "C:/Users/allen/Desktop/分类文件/办法"
  55. fmt.Printf("目标文件夹: %s\n", fileFolder)
  56. // 检查文件夹是否存在
  57. if _, err := os.Stat(fileFolder); os.IsNotExist(err) {
  58. fmt.Printf("错误: 文件夹不存在: %s\n", fileFolder)
  59. return
  60. }
  61. fmt.Println("文件夹存在,开始扫描...")
  62. // 获取所有文件
  63. files, err := getDocumentFiles(fileFolder)
  64. if err != nil {
  65. fmt.Printf("错误: 获取文件失败: %v\n", err)
  66. return
  67. }
  68. fmt.Printf("找到 %d 个文件\n", len(files))
  69. // 创建S3会话
  70. sess, err := createS3Session()
  71. if err != nil {
  72. fmt.Printf("错误: 创建S3会话失败: %v\n", err)
  73. return
  74. }
  75. s3Client := s3.New(sess)
  76. // 处理每个文件
  77. successCount := 0
  78. errorCount := 0
  79. for i, filePath := range files {
  80. fmt.Printf("处理文件 %d/%d: %s\n", i+1, len(files), filepath.Base(filePath))
  81. // 获取文件名(不含路径)
  82. fileName := filepath.Base(filePath)
  83. // 上传文件到OSS
  84. fileURL, err := uploadFileToOSSTest(s3Client, filePath, fileName)
  85. if err != nil {
  86. fmt.Printf(" 错误: 上传文件失败 - %v\n", err)
  87. errorCount++
  88. continue
  89. }
  90. fmt.Printf(" 文件上传成功: %s\n", fileURL)
  91. //去除Filename点后面的内容
  92. fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
  93. //去掉fileName前面“(内部)”这两个字
  94. fileName = strings.TrimPrefix(fileName, "(内部)")
  95. // 保存到index_file表
  96. indexFile := models.PolicyFile{
  97. PolicyName: fileName,
  98. PolicyFileUrl: fileURL,
  99. FileType: 0,
  100. FileTag: "内部法规",
  101. PublishTime: time.Now().Unix(),
  102. PolicyType: 4,
  103. PolicyDepartment: "内部法规",
  104. ViewCount: 0,
  105. PolicyContent: "内部法规",
  106. }
  107. result := models.DB.Create(&indexFile)
  108. if result.Error != nil {
  109. fmt.Printf(" 错误: 保存到数据库失败 - %v\n", result.Error)
  110. errorCount++
  111. continue
  112. }
  113. fmt.Printf(" 数据库保存成功,ID: %d\n", indexFile.ID)
  114. successCount++
  115. // 添加延迟避免请求过于频繁
  116. // time.Sleep(100 * time.Millisecond)
  117. }
  118. fmt.Println("处理完成!")
  119. fmt.Printf("成功处理: %d 个文件\n", successCount)
  120. fmt.Printf("处理失败: %d 个文件\n", errorCount)
  121. }
  122. // 批量匹配图片
  123. func BatchMatchImages() {
  124. fmt.Println("=== 开始批量匹配图片 ===")
  125. // 获取test表中所有有title1的记录
  126. var testRecords []models.Test
  127. result := models.DB.Where("title1 != '' AND title1 IS NOT NULL").Find(&testRecords)
  128. if result.Error != nil {
  129. fmt.Printf("错误: 获取test表记录失败: %v\n", result.Error)
  130. return
  131. }
  132. fmt.Printf("找到 %d 条test表记录\n", len(testRecords))
  133. successCount := 0
  134. errorCount := 0
  135. for i, testRecord := range testRecords {
  136. fmt.Printf("处理记录 %d/%d: ID=%d, Title1=%s\n", i+1, len(testRecords), testRecord.ID, testRecord.Title1)
  137. // 根据title1在third_scene表中查找匹配的记录
  138. var thirdScene models.ThirdScene
  139. result := models.DB.Where("third_scene_name = ?", testRecord.Title1).First(&thirdScene)
  140. if result.Error != nil {
  141. fmt.Printf(" 错误: 在third_scene表中未找到匹配记录 - %v\n", result.Error)
  142. errorCount++
  143. continue
  144. }
  145. fmt.Printf(" 找到匹配的third_scene记录: ID=%d, Name=%s\n", thirdScene.ID, thirdScene.ThirdSceneName)
  146. // 更新third_scene表的correct_example_image和wrong_example_image
  147. updateData := map[string]interface{}{
  148. "correct_example_image": testRecord.Title2,
  149. "wrong_example_image": testRecord.Title3,
  150. }
  151. result = models.DB.Model(&thirdScene).Updates(updateData)
  152. if result.Error != nil {
  153. fmt.Printf(" 错误: 更新third_scene表失败 - %v\n", result.Error)
  154. errorCount++
  155. continue
  156. }
  157. fmt.Printf(" 更新成功: correct_example_image=%s, wrong_example_image=%s\n", testRecord.Title2, testRecord.Title3)
  158. successCount++
  159. }
  160. fmt.Println("匹配完成!")
  161. fmt.Printf("成功匹配: %d 条记录\n", successCount)
  162. fmt.Printf("匹配失败: %d 条记录\n", errorCount)
  163. }
  164. // BatchUploadImages 批量上传图片函数
  165. func BatchUploadImages() {
  166. fmt.Println("=== 开始批量上传图片 ===")
  167. // 设置图片文件夹路径
  168. imageFolder := "C:/Users/allen/Desktop/隐患识别图/错误图片"
  169. fmt.Printf("目标文件夹: %s\n", imageFolder)
  170. // 检查文件夹是否存在
  171. if _, err := os.Stat(imageFolder); os.IsNotExist(err) {
  172. fmt.Printf("错误: 文件夹不存在: %s\n", imageFolder)
  173. return
  174. }
  175. fmt.Println("文件夹存在,开始扫描...")
  176. // 获取所有图片文件
  177. imageFiles, err := getImageFiles(imageFolder)
  178. if err != nil {
  179. fmt.Printf("错误: 获取图片文件失败: %v\n", err)
  180. return
  181. }
  182. fmt.Printf("找到 %d 个图片文件\n", len(imageFiles))
  183. // 创建S3会话
  184. sess, err := createS3Session()
  185. if err != nil {
  186. fmt.Printf("错误: 创建S3会话失败: %v\n", err)
  187. return
  188. }
  189. s3Client := s3.New(sess)
  190. // 处理每个图片文件(只处理前10个文件进行测试)
  191. successCount := 0
  192. errorCount := 0
  193. // 限制处理文件数量
  194. for i, imageFile := range imageFiles {
  195. fmt.Printf("处理文件 %d/%d: %s\n", i+1, len(imageFiles), filepath.Base(imageFile))
  196. // 提取文件名中的序号
  197. fileID, err := extractIDFromFilename(filepath.Base(imageFile))
  198. if err != nil {
  199. fmt.Printf(" 错误: 无法提取序号 - %v\n", err)
  200. errorCount++
  201. continue
  202. }
  203. fmt.Printf(" 提取的序号: %d\n", fileID)
  204. // 检查数据库中是否存在对应的记录
  205. var testRecord models.Test
  206. result := models.DB.First(&testRecord, fileID)
  207. if result.Error != nil {
  208. fmt.Printf(" 错误: 数据库中未找到ID为 %d 的记录 - %v\n", fileID, result.Error)
  209. errorCount++
  210. continue
  211. }
  212. fmt.Printf(" 找到数据库记录: ID=%d, Title1=%s\n", testRecord.ID, testRecord.Title1)
  213. // 上传图片到OSS
  214. imageURL, err := uploadImageToOSSTest(s3Client, imageFile, fileID)
  215. if err != nil {
  216. fmt.Printf(" 错误: 上传图片失败 - %v\n", err)
  217. errorCount++
  218. continue
  219. }
  220. fmt.Printf(" 图片上传成功: %s\n", imageURL)
  221. // 更新数据库记录
  222. result = models.DB.Model(&testRecord).Update("title3", imageURL)
  223. if result.Error != nil {
  224. fmt.Printf(" 错误: 更新数据库失败 - %v\n", result.Error)
  225. errorCount++
  226. continue
  227. }
  228. fmt.Printf(" 数据库更新成功\n")
  229. successCount++
  230. // 添加延迟避免请求过于频繁
  231. time.Sleep(100 * time.Millisecond)
  232. }
  233. fmt.Println("处理完成!")
  234. fmt.Printf("成功处理: %d 个文件\n", successCount)
  235. fmt.Printf("处理失败: %d 个文件\n", errorCount)
  236. }
  237. // 获取文件夹中的所有文档文件
  238. func getDocumentFiles(folderPath string) ([]string, error) {
  239. var documentFiles []string
  240. // 支持的文档格式
  241. documentExts := map[string]bool{
  242. ".pdf": true,
  243. ".doc": true,
  244. ".docx": true,
  245. ".txt": true,
  246. ".rtf": true,
  247. ".odt": true,
  248. ".xls": true,
  249. ".xlsx": true,
  250. ".ppt": true,
  251. ".pptx": true,
  252. }
  253. err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
  254. if err != nil {
  255. return err
  256. }
  257. if !info.IsDir() {
  258. ext := strings.ToLower(filepath.Ext(path))
  259. if documentExts[ext] {
  260. documentFiles = append(documentFiles, path)
  261. }
  262. }
  263. return nil
  264. })
  265. return documentFiles, err
  266. }
  267. // 获取文件夹中的所有图片文件
  268. func getImageFiles(folderPath string) ([]string, error) {
  269. var imageFiles []string
  270. // 支持的图片格式
  271. imageExts := map[string]bool{
  272. ".jpg": true,
  273. ".jpeg": true,
  274. ".png": true,
  275. ".gif": true,
  276. ".bmp": true,
  277. ".webp": true,
  278. ".tiff": true,
  279. ".svg": true,
  280. ".ico": true,
  281. }
  282. err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
  283. if err != nil {
  284. return err
  285. }
  286. if !info.IsDir() {
  287. ext := strings.ToLower(filepath.Ext(path))
  288. if imageExts[ext] {
  289. imageFiles = append(imageFiles, path)
  290. }
  291. }
  292. return nil
  293. })
  294. return imageFiles, err
  295. }
  296. // 从文件名中提取序号
  297. func extractIDFromFilename(filename string) (int, error) {
  298. // 移除文件扩展名
  299. nameWithoutExt := strings.TrimSuffix(filename, filepath.Ext(filename))
  300. // 使用正则表达式提取开头的数字
  301. re := regexp.MustCompile(`^(\d+)`)
  302. matches := re.FindStringSubmatch(nameWithoutExt)
  303. if len(matches) < 2 {
  304. return 0, fmt.Errorf("文件名中未找到数字序号: %s", filename)
  305. }
  306. id, err := strconv.Atoi(matches[1])
  307. if err != nil {
  308. return 0, fmt.Errorf("无法解析序号: %s", matches[1])
  309. }
  310. return id, nil
  311. }
  312. // 创建S3会话
  313. func createS3Session() (*session.Session, error) {
  314. s3Config := &aws.Config{
  315. Credentials: credentials.NewStaticCredentials(testOssAccessKey, testOssSecretKey, ""),
  316. Endpoint: aws.String(testOssEndpoint),
  317. Region: aws.String(testOssRegion),
  318. S3ForcePathStyle: aws.Bool(true),
  319. }
  320. sess, err := session.NewSession(s3Config)
  321. if err != nil {
  322. return nil, err
  323. }
  324. // 验证凭据
  325. _, err = sess.Config.Credentials.Get()
  326. if err != nil {
  327. return nil, fmt.Errorf("凭据验证失败: %v", err)
  328. }
  329. return sess, nil
  330. }
  331. // 上传文件到OSS
  332. func uploadFileToOSSTest(s3Client *s3.S3, filePath string, fileName string) (string, error) {
  333. // 打开文件
  334. file, err := os.Open(filePath)
  335. if err != nil {
  336. return "", err
  337. }
  338. defer file.Close()
  339. // 生成文件名
  340. ext := filepath.Ext(filePath)
  341. ossFileName := fmt.Sprintf("documents/%d_%s",
  342. time.Now().Unix(), fileName)
  343. // 读取文件内容
  344. fileBytes, err := io.ReadAll(file)
  345. if err != nil {
  346. return "", err
  347. }
  348. // 确定Content-Type
  349. contentType := getContentType(ext)
  350. // 上传到S3
  351. _, err = s3Client.PutObject(&s3.PutObjectInput{
  352. Bucket: aws.String(testOssBucket),
  353. Key: aws.String(ossFileName),
  354. Body: aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
  355. ContentType: aws.String(contentType),
  356. ACL: aws.String("public-read"),
  357. })
  358. if err != nil {
  359. return "", err
  360. }
  361. // 生成访问URL
  362. fileURL := fmt.Sprintf("%s/%s/%s", testOssEndpoint, testOssBucket, ossFileName)
  363. return fileURL, nil
  364. }
  365. // 根据文件扩展名获取Content-Type
  366. func getContentType(ext string) string {
  367. contentTypes := map[string]string{
  368. ".pdf": "application/pdf",
  369. ".doc": "application/msword",
  370. ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  371. ".txt": "text/plain",
  372. ".rtf": "application/rtf",
  373. ".odt": "application/vnd.oasis.opendocument.text",
  374. ".xls": "application/vnd.ms-excel",
  375. ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  376. ".ppt": "application/vnd.ms-powerpoint",
  377. ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  378. }
  379. if contentType, exists := contentTypes[strings.ToLower(ext)]; exists {
  380. return contentType
  381. }
  382. return "application/octet-stream" // 默认类型
  383. }
  384. // 上传图片到OSS
  385. func uploadImageToOSSTest(s3Client *s3.S3, imagePath string, fileID int) (string, error) {
  386. // 打开图片文件
  387. file, err := os.Open(imagePath)
  388. if err != nil {
  389. return "", err
  390. }
  391. defer file.Close()
  392. // 生成文件名
  393. ext := filepath.Ext(imagePath)
  394. fileName := fmt.Sprintf("batch_upload/%d_%d%s",
  395. time.Now().Unix(), fileID, ext)
  396. // 读取文件内容
  397. fileBytes, err := io.ReadAll(file)
  398. if err != nil {
  399. return "", err
  400. }
  401. // 上传到S3
  402. _, err = s3Client.PutObject(&s3.PutObjectInput{
  403. Bucket: aws.String(testOssBucket),
  404. Key: aws.String(fileName),
  405. Body: aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
  406. ACL: aws.String("public-read"),
  407. })
  408. if err != nil {
  409. return "", err
  410. }
  411. // 生成访问URL
  412. imageURL := fmt.Sprintf("%s/%s/%s", testOssEndpoint, testOssBucket, fileName)
  413. return imageURL, nil
  414. }
  415. // BatchCompressAndReuploadImages 批量压缩和重新上传third_scene表中的图片
  416. func BatchCompressAndReuploadImages() {
  417. fmt.Println("=== 开始批量压缩和重新上传图片 ===")
  418. // 获取third_scene表中所有记录
  419. var thirdScenes []models.ThirdScene
  420. result := models.DB.Find(&thirdScenes)
  421. if result.Error != nil {
  422. fmt.Printf("错误: 获取third_scene表记录失败: %v\n", result.Error)
  423. return
  424. }
  425. fmt.Printf("找到 %d 条third_scene记录\n", len(thirdScenes))
  426. // 创建S3会话
  427. sess, err := createS3Session()
  428. if err != nil {
  429. fmt.Printf("错误: 创建S3会话失败: %v\n", err)
  430. return
  431. }
  432. s3Client := s3.New(sess)
  433. successCount := 0
  434. errorCount := 0
  435. skipCount := 0
  436. for i, thirdScene := range thirdScenes {
  437. fmt.Printf("处理记录 %d/%d: ID=%d, Name=%s\n", i+1, len(thirdScenes), thirdScene.ID, thirdScene.ThirdSceneName)
  438. // 处理correct_example_image
  439. if thirdScene.CorrectExampleImage != "" {
  440. fmt.Printf(" 处理correct_example_image: %s\n", thirdScene.CorrectExampleImage)
  441. newURL, err := compressAndReuploadImage(s3Client, thirdScene.CorrectExampleImage, fmt.Sprintf("correct_%d", thirdScene.ID))
  442. if err != nil {
  443. fmt.Printf(" 错误: 处理correct_example_image失败 - %v\n", err)
  444. errorCount++
  445. } else {
  446. thirdScene.CorrectExampleImage = newURL
  447. fmt.Printf(" correct_example_image更新成功: %s\n", newURL)
  448. }
  449. } else {
  450. fmt.Printf(" correct_example_image为空,跳过\n")
  451. skipCount++
  452. }
  453. // 处理wrong_example_image
  454. if thirdScene.WrongExampleImage != "" {
  455. fmt.Printf(" 处理wrong_example_image: %s\n", thirdScene.WrongExampleImage)
  456. newURL, err := compressAndReuploadImage(s3Client, thirdScene.WrongExampleImage, fmt.Sprintf("wrong_%d", thirdScene.ID))
  457. if err != nil {
  458. fmt.Printf(" 错误: 处理wrong_example_image失败 - %v\n", err)
  459. errorCount++
  460. } else {
  461. thirdScene.WrongExampleImage = newURL
  462. fmt.Printf(" wrong_example_image更新成功: %s\n", newURL)
  463. }
  464. } else {
  465. fmt.Printf(" wrong_example_image为空,跳过\n")
  466. skipCount++
  467. }
  468. // 更新数据库记录
  469. result := models.DB.Model(&thirdScene).Updates(map[string]interface{}{
  470. "correct_example_image": thirdScene.CorrectExampleImage,
  471. "wrong_example_image": thirdScene.WrongExampleImage,
  472. })
  473. if result.Error != nil {
  474. fmt.Printf(" 错误: 更新数据库失败 - %v\n", result.Error)
  475. errorCount++
  476. } else {
  477. fmt.Printf(" 数据库更新成功\n")
  478. successCount++
  479. }
  480. // 添加延迟避免请求过于频繁
  481. time.Sleep(200 * time.Millisecond)
  482. }
  483. fmt.Println("处理完成!")
  484. fmt.Printf("成功处理: %d 条记录\n", successCount)
  485. fmt.Printf("处理失败: %d 条记录\n", errorCount)
  486. fmt.Printf("跳过空字段: %d 次\n", skipCount)
  487. }
  488. // compressAndReuploadImage 压缩并重新上传图片
  489. func compressAndReuploadImage(s3Client *s3.S3, imageURL string, prefix string) (string, error) {
  490. // 下载图片
  491. tempFilePath, err := downloadImage(imageURL)
  492. if err != nil {
  493. return "", fmt.Errorf("下载图片失败: %v", err)
  494. }
  495. defer os.Remove(tempFilePath) // 清理临时文件
  496. // 压缩图片
  497. compressedFilePath, err := compressImageFile(tempFilePath)
  498. if err != nil {
  499. return "", fmt.Errorf("压缩图片失败: %v", err)
  500. }
  501. defer os.Remove(compressedFilePath) // 清理临时文件
  502. // 上传压缩后的图片
  503. newURL, err := uploadCompressedImageToOSS(s3Client, compressedFilePath, prefix)
  504. if err != nil {
  505. return "", fmt.Errorf("上传压缩图片失败: %v", err)
  506. }
  507. return newURL, nil
  508. }
  509. // downloadImage 下载图片到临时文件
  510. func downloadImage(imageURL string) (string, error) {
  511. // 创建HTTP请求
  512. resp, err := http.Get(imageURL)
  513. if err != nil {
  514. return "", err
  515. }
  516. defer resp.Body.Close()
  517. if resp.StatusCode != http.StatusOK {
  518. return "", fmt.Errorf("下载失败,状态码: %d", resp.StatusCode)
  519. }
  520. // 创建临时文件
  521. tempFile, err := os.CreateTemp("", "download_*.jpg")
  522. if err != nil {
  523. return "", err
  524. }
  525. defer tempFile.Close()
  526. // 复制内容到临时文件
  527. _, err = io.Copy(tempFile, resp.Body)
  528. if err != nil {
  529. os.Remove(tempFile.Name())
  530. return "", err
  531. }
  532. return tempFile.Name(), nil
  533. }
  534. // compressImageFile 压缩图片到1M以下
  535. func compressImageFile(inputPath string) (string, error) {
  536. // 打开原始图片
  537. file, err := os.Open(inputPath)
  538. if err != nil {
  539. return "", err
  540. }
  541. defer file.Close()
  542. // 解码图片
  543. img, format, err := image.Decode(file)
  544. if err != nil {
  545. return "", err
  546. }
  547. // 创建临时文件用于压缩后的图片
  548. tempFile, err := os.CreateTemp("", "compressed_*.jpg")
  549. if err != nil {
  550. return "", err
  551. }
  552. defer tempFile.Close()
  553. // 尝试不同的质量级别直到文件大小小于1MB
  554. targetSize := int64(1024 * 1024) // 1MB
  555. quality := 90
  556. for quality > 10 {
  557. // 重置文件指针
  558. tempFile.Seek(0, 0)
  559. tempFile.Truncate(0)
  560. // 根据格式编码图片
  561. if format == "png" {
  562. err = png.Encode(tempFile, img)
  563. } else {
  564. err = jpeg.Encode(tempFile, img, &jpeg.Options{Quality: quality})
  565. }
  566. if err != nil {
  567. return "", err
  568. }
  569. // 检查文件大小
  570. fileInfo, err := tempFile.Stat()
  571. if err != nil {
  572. return "", err
  573. }
  574. if fileInfo.Size() <= targetSize {
  575. break
  576. }
  577. quality -= 10
  578. }
  579. // 如果还是太大,尝试缩放图片
  580. if quality <= 10 {
  581. // 获取图片尺寸
  582. bounds := img.Bounds()
  583. width := bounds.Dx()
  584. height := bounds.Dy()
  585. // 计算缩放比例
  586. scale := 0.8
  587. newWidth := int(float64(width) * scale)
  588. newHeight := int(float64(height) * scale)
  589. // 创建缩放后的图片
  590. resizedImg := resizeImageFile(img, newWidth, newHeight)
  591. // 重置文件指针
  592. tempFile.Seek(0, 0)
  593. tempFile.Truncate(0)
  594. // 编码缩放后的图片
  595. err = jpeg.Encode(tempFile, resizedImg, &jpeg.Options{Quality: 80})
  596. if err != nil {
  597. return "", err
  598. }
  599. }
  600. return tempFile.Name(), nil
  601. }
  602. // resizeImageFile 缩放图片
  603. func resizeImageFile(img image.Image, width, height int) image.Image {
  604. // 简单的最近邻缩放
  605. bounds := img.Bounds()
  606. srcWidth := bounds.Dx()
  607. srcHeight := bounds.Dy()
  608. // 创建新的图片
  609. newImg := image.NewRGBA(image.Rect(0, 0, width, height))
  610. // 计算缩放比例
  611. xRatio := float64(srcWidth) / float64(width)
  612. yRatio := float64(srcHeight) / float64(height)
  613. for y := 0; y < height; y++ {
  614. for x := 0; x < width; x++ {
  615. srcX := int(float64(x) * xRatio)
  616. srcY := int(float64(y) * yRatio)
  617. newImg.Set(x, y, img.At(srcX, srcY))
  618. }
  619. }
  620. return newImg
  621. }
  622. // uploadCompressedImageToOSS 上传压缩后的图片到OSS
  623. func uploadCompressedImageToOSS(s3Client *s3.S3, imagePath string, prefix string) (string, error) {
  624. // 打开图片文件
  625. file, err := os.Open(imagePath)
  626. if err != nil {
  627. return "", err
  628. }
  629. defer file.Close()
  630. // 生成文件名
  631. fileName := fmt.Sprintf("compressed_images/%s_%d.jpg", prefix, time.Now().Unix())
  632. // 读取文件内容
  633. fileBytes, err := io.ReadAll(file)
  634. if err != nil {
  635. return "", err
  636. }
  637. // 上传到S3
  638. _, err = s3Client.PutObject(&s3.PutObjectInput{
  639. Bucket: aws.String(testOssBucket),
  640. Key: aws.String(fileName),
  641. Body: aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
  642. ContentType: aws.String("image/jpeg"),
  643. ACL: aws.String("public-read"),
  644. })
  645. if err != nil {
  646. return "", err
  647. }
  648. // 生成访问URL
  649. imageURL := fmt.Sprintf("%s/%s/%s", testOssEndpoint, testOssBucket, fileName)
  650. return imageURL, nil
  651. }