test.go 20 KB

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