usda_psd.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. package services
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "eta/eta_crawler/services/alarm_msg"
  7. "eta/eta_crawler/utils"
  8. "fmt"
  9. "github.com/PuerkitoBio/goquery"
  10. "github.com/xuri/excelize/v2"
  11. "io"
  12. "mime/multipart"
  13. "net/http"
  14. "os"
  15. "os/exec"
  16. "strconv"
  17. "strings"
  18. "time"
  19. )
  20. // UsdaPsdDataQueryParams 定义了JSON到Go结构体的映射
  21. type UsdaPsdDataQueryParams struct {
  22. QueryID int `json:"queryId"`
  23. CommodityGroupCode string `json:"commodityGroupCode"`
  24. Commodities []string `json:"commodities"`
  25. Attributes []int `json:"attributes"`
  26. Countries []string `json:"countries"`
  27. MarketYears []int `json:"marketYears"`
  28. ChkCommoditySummary bool `json:"chkCommoditySummary"`
  29. ChkAttribSummary bool `json:"chkAttribSummary"`
  30. ChkCountrySummary bool `json:"chkCountrySummary"`
  31. CommoditySummaryText string `json:"commoditySummaryText"`
  32. AttribSummaryText string `json:"attribSummaryText"`
  33. CountrySummaryText string `json:"countrySummaryText"`
  34. OptionColumn string `json:"optionColumn"`
  35. ChkTopCountry bool `json:"chkTopCountry"`
  36. TopCountryCount string `json:"topCountryCount"`
  37. ChkFileFormat bool `json:"chkfileFormat"`
  38. ChkPrevMonth bool `json:"chkPrevMonth"`
  39. ChkMonthChange bool `json:"chkMonthChange"`
  40. ChkCodes bool `json:"chkCodes"`
  41. ChkYearChange bool `json:"chkYearChange"`
  42. QueryName string `json:"queryName"`
  43. SortOrder string `json:"sortOrder"`
  44. TopCountryState bool `json:"topCountryState"`
  45. }
  46. type UsdaPsdData struct {
  47. TableHeaders []string `json:"tableHeaders"`
  48. QueryResult []map[string]interface{} `json:"queryResult"`
  49. }
  50. type UsdaPsdDataAttribute struct {
  51. AttributeId int `json:"attributeId"`
  52. }
  53. // UsdaFasIndex 美国农业部指标数据
  54. type UsdaFasIndex struct {
  55. ClassifyName string `description:"指标目录"`
  56. ParentClassifyName string `description:"父级指标目录"`
  57. ClassifySort int `description:"指标目录排序号"`
  58. IndexName string `description:"指标名称"`
  59. IndexCode string `description:"指标编码"`
  60. Unit string `description:"单位"`
  61. Sort int `description:"排序号"`
  62. Frequency string `description:"频度"`
  63. TerminalCode string `description:"编码"`
  64. Country string `description:"国家"`
  65. Commodity string `description:"属性"`
  66. ExcelDataMap map[string]string
  67. }
  68. func DownloadUsdaPsdDataTask(cont context.Context) (err error) {
  69. //月度供需,年度和月度
  70. //todo 设置下载频率
  71. // 获取最近两年的年份
  72. years := []int{time.Now().Year() + 1, time.Now().Year()}
  73. var commodities []string
  74. commodities = append(commodities, "0813800", "0813200", "0813600", "0813100", "0813500", "4242000", "4233000", "4235000", "4243000", "4244000", "4234000", "4239100", "4232000", "4236000", "2223000", "2232000", "2221000", "2226000", "2222000", "2224000")
  75. for _, commodity := range commodities {
  76. err = DownloadUsdaPsdData(commodity, years)
  77. if err != nil {
  78. utils.FileLog.Info("DownloadUsdaPsdData " + commodity + "ErrMsg:" + err.Error())
  79. }
  80. //time.Sleep(time.Duration(utils.RangeRand(2, 10)*10) * time.Second)
  81. utils.FileLog.Info("DownloadUsdaPsdData " + commodity + " 爬取成功")
  82. }
  83. return
  84. }
  85. func DownloadUsdaFmsDataTask(cont context.Context) (err error) {
  86. //出口销售周度数据
  87. startDate := time.Now().AddDate(0, -1, 0).Format("01/02/2006")
  88. endDate := time.Now().Format("01/02/2006")
  89. err = DownloadUsdaFmsData(startDate, endDate)
  90. return
  91. }
  92. // Meal, Palm Kernel:0813800
  93. // Meal, Peanut:0813200
  94. // Meal, Rapeseed:0813600
  95. // Meal, Soybean:0813100
  96. // Meal, Sunflowerseed:0813500
  97. // Oil, Coconut:4242000
  98. // Oil, Cottonseed:4233000
  99. // Oil, Olive:4235000
  100. // Oil, Palm:4243000
  101. // Oil, Palm Kernel:4244000
  102. // Oil, Peanut:4234000
  103. // Oil, Rapeseed:4239100
  104. // Oil, Soybean:4232000
  105. // Oil, Sunflowerseed:4236000
  106. // Oilseed, Cottonseed:2223000
  107. // Oilseed, Palm Kernel:2232000
  108. // Oilseed, Peanut:2221000
  109. // Oilseed, Rapeseed:2226000
  110. // Oilseed, Soybean:2222000
  111. // Oilseed, Sunflowerseed:2224000
  112. // 美国农业部月度供需平衡表数据
  113. func DownloadUsdaPsdData(commodityCode string, years []int) (err error) {
  114. defer func() {
  115. if err != nil {
  116. msg := "失败提醒" + "downloadUsdaPsdData ErrMsg:" + err.Error()
  117. fmt.Println("msg:", msg)
  118. utils.FileLog.Info(msg)
  119. go alarm_msg.SendAlarmMsg(msg, 3)
  120. }
  121. }()
  122. // 定义请求地址
  123. attributeUrl := "https://apps.fas.usda.gov/PSDOnlineApi/api/query/GetMultiCommodityAttributes?"
  124. dataUrl := "https://apps.fas.usda.gov/PSDOnlineApi/api/query/RunQuery"
  125. var commodities []string
  126. //commodities = append(commodities, "0813800", "0813200", "0813600", "0813100", "0813500", "4242000", "4233000", "4235000", "4243000", "4244000", "4234000", "4239100", "4232000", "4236000", "2223000", "2232000", "2221000", "2226000", "2222000", "2224000")
  127. commodities = append(commodities, commodityCode)
  128. commodityCodes := strings.Join(commodities, ",")
  129. attributeUrl = attributeUrl + "commodityCodes=" + commodityCodes
  130. fmt.Println("attributeUrl", attributeUrl)
  131. // 定义请求参数
  132. // 获取属性入参
  133. attributeBody, e := utils.HttpGetNoCookie(attributeUrl)
  134. if e != nil {
  135. err = e
  136. return
  137. }
  138. attrList := make([]UsdaPsdDataAttribute, 0)
  139. err = json.Unmarshal(attributeBody, &attrList)
  140. if err != nil {
  141. fmt.Println("json.Unmarshal err:" + err.Error())
  142. return
  143. }
  144. // 解析
  145. var attributes []int
  146. for _, v := range attrList {
  147. // 键值对的值
  148. attributes = append(attributes, v.AttributeId)
  149. }
  150. // 定义请求方法
  151. //attributes = append(attributes, 4, 20, 28, 57, 81, 84, 86, 88, 113, 130, 192, 125, 176, 178, 184)
  152. var countries []string
  153. countries = append(countries, "R00", "ALL")
  154. var marketYears []int
  155. //marketYears = append(marketYears, 2025, 2024, 2023, 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014)
  156. marketYears = append(marketYears, years...)
  157. // {"queryId":0,"commodityGroupCode":null,"commodities":["0430000"],"attributes":[4,20,28,57,81,84,86,88,113,130,192,125,176,178,184],"countries":["R00","ALL"],"marketYears":[2024,2023,2022,2021,2020,2019,2018,2017,2016,2015,2014],"chkCommoditySummary":false,"chkAttribSummary":false,"chkCountrySummary":false,"commoditySummaryText":"","attribSummaryText":"","countrySummaryText":"","optionColumn":"year","chkTopCountry":false,"topCountryCount":"","chkfileFormat":false,"chkPrevMonth":true,"chkMonthChange":false,"chkCodes":false,"chkYearChange":false,"queryName":"","sortOrder":"Commodity/Attribute/Country","topCountryState":false}
  158. var req UsdaPsdDataQueryParams
  159. req.Commodities = commodities
  160. req.Attributes = attributes
  161. req.Countries = countries
  162. req.MarketYears = marketYears
  163. req.OptionColumn = "year"
  164. req.ChkPrevMonth = true
  165. req.ChkYearChange = true
  166. req.SortOrder = "Commodity/Country/Attribute"
  167. // 构造httppost请求
  168. reqBody, _ := json.Marshal(req)
  169. // 解析返回值
  170. fmt.Println("reqBody", string(reqBody))
  171. headerParams := make(map[string]string)
  172. //headerParams["Cookie"] = "CT6T=312900; SF_cookie_3=68941398"
  173. //headerParams["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
  174. headerParams["Content-Type"] = "application/json"
  175. body, e := utils.HttpPostNoCookie(dataUrl, string(reqBody), headerParams)
  176. if e != nil {
  177. err = e
  178. return
  179. }
  180. item := new(UsdaPsdData)
  181. err = json.Unmarshal(body, &item)
  182. if err != nil {
  183. fmt.Println("json.Unmarshal err:" + err.Error())
  184. return
  185. }
  186. // 使用通道等待解析完成
  187. done := make(chan error)
  188. go func() {
  189. done <- handleUsdaFasPsd(item)
  190. }()
  191. // 等待解析完成或超时
  192. select {
  193. case err = <-done:
  194. if err != nil {
  195. err = fmt.Errorf("handleUsdaFasPsd, Err:%w", err)
  196. return
  197. }
  198. /*case <-time.After(20 * time.Minute): // 假设20分钟超时
  199. err = fmt.Errorf("parse excel timed out")
  200. return*/
  201. }
  202. utils.FileLog.Info("月度供需 " + commodityCode + "结束")
  203. return
  204. }
  205. // 美国农业出库销售数据
  206. func DownloadUsdaFmsData(startDate, endDate string) (err error) {
  207. // todo 设置下载频率, 如果有正在处理中的,则暂停下载
  208. defer func() {
  209. if err != nil {
  210. msg := "失败提醒" + "DownloadUsdaFmsData ErrMsg:" + err.Error()
  211. fmt.Println("msg:", msg)
  212. utils.FileLog.Info(msg)
  213. go alarm_msg.SendAlarmMsg(msg, 3)
  214. }
  215. }()
  216. downloadFile := fmt.Sprintf("./static/usda_fms_excel_%s.xls", time.Now().Format(utils.FormatDateTimeUnSpace))
  217. //请求首页,获取入参
  218. dataUrl := "https://apps.fas.usda.gov/esrquery/esrq.aspx"
  219. body1, err := utils.HttpGetNoCookie(dataUrl)
  220. if err != nil {
  221. return
  222. }
  223. htmlString := string(body1)
  224. // 解析返回值,截取htmlinput标签,input标签里,id=“__EVENTVALIDATION”的input标签里的值
  225. // 使用goquery读取HTML字符串
  226. doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlString))
  227. if err != nil {
  228. return
  229. }
  230. stateValue := doc.Find("input#__VIEWSTATE").AttrOr("value", "")
  231. stateEneratorValue := doc.Find("input#__VIEWSTATEGENERATOR").AttrOr("value", "")
  232. // 查询并获取input标签的值
  233. validValue := doc.Find("input#__EVENTVALIDATION").AttrOr("value", "")
  234. var body bytes.Buffer
  235. multipartWriter := multipart.NewWriter(&body)
  236. // 添加表单字段(如果需要的话)
  237. if err = multipartWriter.WriteField("__EVENTTARGET", ""); err != nil {
  238. err = fmt.Errorf("set __EVENTTARGET, Err:%s", err)
  239. return
  240. }
  241. if err = multipartWriter.WriteField("__EVENTARGUMENT", ""); err != nil {
  242. err = fmt.Errorf("set __EVENTARGUMENT, Err:%s", err)
  243. return
  244. }
  245. if err = multipartWriter.WriteField("__LASTFOCUS", ""); err != nil {
  246. err = fmt.Errorf("set __LASTFOCUS, Err:%s", err)
  247. return
  248. }
  249. if err = multipartWriter.WriteField("__VIEWSTATE", stateValue); err != nil {
  250. err = fmt.Errorf("set __VIEWSTATE, Err:%s", err)
  251. return
  252. }
  253. if err = multipartWriter.WriteField("__VIEWSTATEGENERATOR", stateEneratorValue); err != nil {
  254. err = fmt.Errorf("set __VIEWSTATEGENERATOR, Err:%s", err)
  255. return
  256. }
  257. if err = multipartWriter.WriteField("__EVENTVALIDATION", validValue); err != nil {
  258. err = fmt.Errorf("set __EVENTVALIDATION, Err:%s", err)
  259. return
  260. }
  261. //整理需要下载的品种ID
  262. //Soybeans:801,Soybean cake & meal:901,Soybean Oil:902
  263. CommodityIds := []string{"801", "901", "902"}
  264. for _, v := range CommodityIds {
  265. if err = multipartWriter.WriteField("ctl00$MainContent$lbCommodity", v); err != nil {
  266. err = fmt.Errorf("set ctl00$MainContent$lbCommodity, Err:%s", err)
  267. return
  268. }
  269. }
  270. if err = multipartWriter.WriteField("ctl00$MainContent$lbCountry", "0:0"); err != nil {
  271. err = fmt.Errorf("set ctl00$MainContent$lbCountry, Err:%s", err)
  272. return
  273. }
  274. if err = multipartWriter.WriteField("ctl00$MainContent$ddlReportFormat", "10"); err != nil {
  275. return
  276. }
  277. if err = multipartWriter.WriteField("ctl00$MainContent$rblOutputType", "2"); err != nil {
  278. return
  279. }
  280. if err = multipartWriter.WriteField("ctl00$MainContent$tbStartDate", startDate); err != nil {
  281. return
  282. }
  283. if err = multipartWriter.WriteField("ctl00$MainContent$tbEndDate", endDate); err != nil {
  284. return
  285. }
  286. if err = multipartWriter.WriteField("ctl00$MainContent$rblColumnSelection", "regular"); err != nil {
  287. return
  288. }
  289. if err = multipartWriter.WriteField("ctl00$MainContent$btnSubmit", "Submit"); err != nil {
  290. return
  291. }
  292. // 注意:如果接口需要文件上传,这里应该使用multipartWriter.CreateFormFile来添加文件
  293. // 关闭multipart writer以添加最后的边界
  294. if err = multipartWriter.Close(); err != nil {
  295. err = fmt.Errorf("close multipart writer, Err:%s", err)
  296. return
  297. }
  298. // 构造请求
  299. req, err := http.NewRequest("POST", dataUrl, &body)
  300. if err != nil {
  301. err = fmt.Errorf("create request, Err:%s", err)
  302. return
  303. }
  304. // 设置请求头
  305. req.Header.Set("Content-Type", multipartWriter.FormDataContentType())
  306. // 发送请求
  307. client := &http.Client{}
  308. resp, err := client.Do(req)
  309. if err != nil {
  310. err = fmt.Errorf("send request, Err:%s", err)
  311. return
  312. }
  313. defer resp.Body.Close()
  314. // 检查响应状态码
  315. if resp.StatusCode != http.StatusOK {
  316. err = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
  317. return
  318. }
  319. // 读取响应体
  320. out, err := os.Create(downloadFile)
  321. if err != nil {
  322. return
  323. }
  324. // 将响应体写入到文件
  325. _, err = io.Copy(out, resp.Body)
  326. if err != nil {
  327. return
  328. }
  329. // 关闭临时文件以确保数据写入完成
  330. err = out.Close()
  331. if err != nil {
  332. err = fmt.Errorf("Failed to close temporary file: %v", err)
  333. return
  334. }
  335. // 转换文件格式
  336. downloadFileXlsx := downloadFile + "x"
  337. err = ConvertXlsToXlsx(downloadFile, downloadFileXlsx)
  338. if err != nil {
  339. err = fmt.Errorf("文件格式转换失败 convert excel, Err:%w", err)
  340. return
  341. }
  342. // 使用通道等待解析完成
  343. done := make(chan error)
  344. go func() {
  345. done <- ParseUsdaFmsExcel(downloadFileXlsx)
  346. }()
  347. // 等待解析完成或超时
  348. select {
  349. case err = <-done:
  350. if err != nil {
  351. err = fmt.Errorf("parse excel, Err:%w", err)
  352. return
  353. }
  354. /*case <-time.After(20 * time.Minute): // 假设20分钟超时
  355. err = fmt.Errorf("parse excel timed out")
  356. return*/
  357. }
  358. // 删除临时文件
  359. defer func() {
  360. os.Remove(downloadFile)
  361. }()
  362. fmt.Println("Excel file downloaded successfully")
  363. return
  364. }
  365. func ParseUsdaFmsExcel(path string) (err error) {
  366. defer func() {
  367. if err != nil {
  368. msg := "失败提醒" + "DownloadUsdaFmsData_ParseUsdaFmsExcel ErrMsg:" + err.Error()
  369. fmt.Println("msg:", msg)
  370. utils.FileLog.Info(msg)
  371. go alarm_msg.SendAlarmMsg(msg, 3)
  372. }
  373. }()
  374. //var xlFile *xlsx.File
  375. exist, err := PathExists(path)
  376. if err != nil {
  377. fmt.Println(err)
  378. err = fmt.Errorf("文件地址不存在 err:%s", err.Error())
  379. return
  380. }
  381. if !exist {
  382. err = fmt.Errorf("文件地址不存在")
  383. return
  384. }
  385. //xlFile, err = xlsx.OpenFile(path)
  386. xlFile, err := excelize.OpenFile(path)
  387. if err != nil {
  388. fmt.Println("OpenFile err:", err)
  389. err = fmt.Errorf("打开文件失败 err:%s", err.Error())
  390. return
  391. }
  392. defer func() {
  393. // 关闭工作簿
  394. if err = xlFile.Close(); err != nil {
  395. fmt.Println(err)
  396. }
  397. os.Remove(path)
  398. }()
  399. sheetName := xlFile.GetSheetName(0)
  400. fmt.Println("Sheet Name:", sheetName)
  401. //解析出表头第7行
  402. //拼接指标名称
  403. // 指标名称
  404. indexMap := make(map[string]*UsdaFasIndex)
  405. indexList := make([]*UsdaFasIndex, 0)
  406. sort := 0
  407. rows, err := xlFile.GetRows(sheetName)
  408. //for _, sheet := range xlFile.Sheets {
  409. //遍历行读取
  410. for i, row := range rows {
  411. if i > 6 {
  412. commodity := ""
  413. dateStr := ""
  414. country := ""
  415. dataVal := ""
  416. unit := "Metric Tons"
  417. for k, text := range row {
  418. //fmt.Println("第", i, "行,第", k, "列,内容:", text)
  419. kind := ""
  420. indexName := ""
  421. if k == 1 { // 品种名称Commodity
  422. commodity = text
  423. } else if k == 2 {
  424. dateStr = text
  425. } else if k == 4 {
  426. country = text
  427. } else if k == 5 {
  428. kind = "Weekly Exports"
  429. } else if k == 6 {
  430. kind = "Accum Exports"
  431. } else if k == 7 {
  432. kind = "Outstanding Sale:CMY"
  433. } else if k == 8 {
  434. kind = "Gross Sale:CMY"
  435. } else if k == 9 {
  436. kind = "Net Sale :CMY"
  437. } else if k == 10 {
  438. kind = "Total Commitment:CMY"
  439. } else if k == 11 {
  440. kind = "Outstanding Sale:NMY"
  441. } else if k == 12 {
  442. kind = "Net Sale :NMY"
  443. }
  444. if k > 4 && k < 13 {
  445. // 处理日期
  446. //fmt.Println(dateStr)
  447. //fmt.Println(unit)
  448. timeT, e := time.ParseInLocation(utils.FormatDateTime, dateStr, time.Local)
  449. if e != nil {
  450. utils.FileLog.Info("日期格式转换失败 err:%s", e.Error())
  451. continue
  452. }
  453. date := timeT.Format(utils.FormatDate)
  454. dataVal = text
  455. firstCommodity := utils.GetFirstLetter(commodity)
  456. firstKind := utils.GetFirstLetter(kind)
  457. indexName = fmt.Sprintf("%s: %s: %s", commodity, country, kind)
  458. inCode := fmt.Sprintf("usda%s%s%s", firstCommodity, strings.ToLower(strings.ReplaceAll(country, " ", "")), firstKind)
  459. indexItem, okIndex := indexMap[indexName]
  460. // 首字母大写
  461. classifyName := commodity
  462. if !okIndex {
  463. // 新增指标
  464. indexItem = new(UsdaFasIndex)
  465. indexItem.IndexName = indexName
  466. indexItem.ClassifyName = classifyName
  467. indexItem.Country = country
  468. indexItem.Commodity = kind
  469. indexItem.ParentClassifyName = "出口销售"
  470. indexItem.ClassifySort = 0
  471. indexItem.IndexCode = inCode
  472. indexItem.Frequency = "周度"
  473. indexItem.Sort = sort
  474. indexItem.Unit = unit
  475. indexItem.ExcelDataMap = make(map[string]string)
  476. sort++
  477. }
  478. if strings.Contains(dataVal, ",") {
  479. dataVal = strings.ReplaceAll(dataVal, ",", "")
  480. }
  481. val, e := strconv.ParseFloat(dataVal, 64)
  482. if e != nil {
  483. utils.FileLog.Info("数据转换失败 err:%s", e.Error())
  484. continue
  485. }
  486. indexItem.ExcelDataMap[date] = fmt.Sprintf("%.4f", val)
  487. indexMap[indexName] = indexItem
  488. }
  489. }
  490. }
  491. }
  492. //}
  493. for _, v := range indexMap {
  494. //fmt.Printf("IndexName: %s \n", v.IndexName)
  495. //fmt.Printf("IndexCode: %s \n", v.IndexCode)
  496. indexList = append(indexList, v)
  497. if len(indexList) > 100 {
  498. err = addUsdaFasPsdData(indexList, "出口销售")
  499. if err != nil {
  500. return
  501. }
  502. indexList = []*UsdaFasIndex{}
  503. }
  504. }
  505. err = addUsdaFasPsdData(indexList, "出口销售")
  506. if err != nil {
  507. return
  508. }
  509. fmt.Println("出口销售 执行成功")
  510. return
  511. }
  512. // 月度供需
  513. func handleUsdaFasPsd(item *UsdaPsdData) (err error) {
  514. //设置缓存key,防止重复处理
  515. errMsg := ""
  516. defer func() {
  517. if err != nil {
  518. errMsg += err.Error()
  519. }
  520. if errMsg != "" {
  521. msg := "失败提醒" + "downloadUsdaPsdData_handleUsdaFasPsd ErrMsg:" + errMsg
  522. fmt.Println("msg:", msg)
  523. utils.FileLog.Info(msg)
  524. go alarm_msg.SendAlarmMsg(msg, 3)
  525. }
  526. }()
  527. // 解析
  528. headerSlice := make([]string, 0)
  529. for index, v := range item.TableHeaders {
  530. // 键值对的值
  531. fmt.Println("key:", index, "value:", v)
  532. if !strings.Contains(v, "/") && !strings.Contains(v, " ") {
  533. v = strings.ToLower(v)
  534. }
  535. if v == "Unit Description" {
  536. v = "unit Description"
  537. }
  538. headerSlice = append(headerSlice, v)
  539. }
  540. sort := 0
  541. // 指标名称
  542. indexMap := make(map[string]*UsdaFasIndex)
  543. // 键值对的值
  544. commodityRow := ""
  545. countriesRow := ""
  546. attributesRow := ""
  547. for _, row := range item.QueryResult {
  548. unitK := headerSlice[len(headerSlice)-1]
  549. unit := row[unitK].(string)
  550. // unit 去掉左右两边的括号,去掉中间的空格
  551. unit = strings.Replace(unit, " ", "", -1)
  552. unit = strings.Trim(unit, "()")
  553. for _, k := range headerSlice {
  554. col, ok := row[k]
  555. if !ok || col == nil {
  556. //utils.FileLog.Info("col is nil")
  557. continue
  558. }
  559. if k == "commodity" {
  560. commodityRow = col.(string)
  561. } else if k == "country" {
  562. countriesRow = col.(string)
  563. } else if k == "attribute" {
  564. attributesRow = col.(string)
  565. } else if k == "unit Description" {
  566. // unit = col.(string)
  567. } else {
  568. //数据列
  569. year, _ := strconv.Atoi(strings.Split(k, "/")[0])
  570. indexName := ""
  571. classifyName := ""
  572. classifySort := 0
  573. inCode := ""
  574. fre := "年度"
  575. lastStr := "Yearly"
  576. // year年度的最后一天日期
  577. dateT := time.Date(year, time.December, 31, 0, 0, 0, 0, time.Local)
  578. if strings.Contains(k, "(") {
  579. fre = "月度"
  580. lastStr = "Monthly"
  581. // 截取括号中间的月度数据
  582. monthStr := strings.Split(k, "(")[1]
  583. monthStr = strings.Split(monthStr, ")")[0]
  584. // 将Jul英文月份前缀转成数字月份
  585. monthT, e := time.ParseInLocation("Jan", monthStr, time.Local)
  586. if e != nil {
  587. errMsg += fmt.Sprintf("月份转换错误:%s%s\n", monthStr, e.Error())
  588. continue
  589. }
  590. // 将year和month拼接成日期,该月的最后一天日期
  591. // 获取下一个月份的第一天,减去1天为当前月份的最后一天
  592. dateT = time.Date(year, monthT.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
  593. }
  594. date := dateT.Format("2006-01-02")
  595. // 封装成指标数据
  596. if commodityRow != "" && countriesRow != "" && attributesRow != "" {
  597. indexName = commodityRow + ": " + countriesRow + ": " + attributesRow + ": " + lastStr
  598. } else {
  599. fmt.Println("commodityRow:", commodityRow, "countriesRow:", countriesRow, "attributesRow:", attributesRow)
  600. errMsg += fmt.Sprintf("指标名称为空 commodityRow:%s,countriesRow:%s,attributesRow:%s\n", commodityRow, countriesRow, attributesRow)
  601. continue
  602. }
  603. firstCommodityRow := strings.Replace(commodityRow, ", ", "", -1)
  604. firstCommodityRow = strings.Replace(firstCommodityRow, " ", "", -1)
  605. firstCommodityRow = strings.ToLower(firstCommodityRow)
  606. firstAttributesRow := utils.GetFirstLetter(attributesRow)
  607. firstLastStr := utils.GetFirstLetter(lastStr)
  608. inCode = fmt.Sprintf("usda%s%s%s%s", firstCommodityRow, strings.ToLower(strings.ReplaceAll(countriesRow, " ", "")), firstAttributesRow, firstLastStr)
  609. indexItem, okIndex := indexMap[indexName]
  610. // 首字母大写
  611. classifyName = commodityRow
  612. if !okIndex {
  613. // 新增指标
  614. indexItem = new(UsdaFasIndex)
  615. indexItem.IndexName = indexName
  616. indexItem.ClassifyName = classifyName
  617. indexItem.Country = countriesRow
  618. indexItem.Commodity = attributesRow
  619. indexItem.ParentClassifyName = "月度供需"
  620. indexItem.ClassifySort = classifySort
  621. indexItem.IndexCode = inCode
  622. indexItem.Frequency = fre
  623. indexItem.Sort = sort
  624. indexItem.Unit = unit
  625. indexItem.ExcelDataMap = make(map[string]string)
  626. sort++
  627. }
  628. val := col.(float64)
  629. val = utils.FloatFormatRound(val, 2)
  630. indexItem.ExcelDataMap[date] = fmt.Sprintf("%.4f", val)
  631. indexMap[indexName] = indexItem
  632. continue
  633. }
  634. }
  635. }
  636. indexList := make([]*UsdaFasIndex, 0)
  637. for _, v := range indexMap {
  638. //fmt.Printf("IndexName: %s \n", v.IndexName)
  639. //fmt.Printf("IndexCode: %s \n", v.IndexCode)
  640. indexList = append(indexList, v)
  641. if len(indexList) > 100 {
  642. err = addUsdaFasPsdData(indexList, "月度供需")
  643. if err != nil {
  644. return
  645. }
  646. indexList = []*UsdaFasIndex{}
  647. }
  648. }
  649. err = addUsdaFasPsdData(indexList, "月度供需")
  650. if err != nil {
  651. return
  652. }
  653. fmt.Println("月度供需 " + commodityRow + "执行成功")
  654. return
  655. }
  656. func addUsdaFasPsdData(indexList []*UsdaFasIndex, sheetName string) (err error) {
  657. if len(indexList) > 0 {
  658. params := make(map[string]interface{})
  659. params["List"] = indexList
  660. params["TerminalCode"] = ""
  661. result, e := utils.PostEdbLib(params, "usda_fas/handle/excel_data")
  662. if e != nil {
  663. err = fmt.Errorf("sheet :%s PostEdbLib err: %s", sheetName, e.Error())
  664. b, _ := json.Marshal(params)
  665. utils.FileLog.Info(fmt.Sprintf("sheet :%s PostEdbLib err: %s, params: %s", sheetName, e.Error(), string(b)))
  666. return
  667. }
  668. resp := new(utils.BaseEdbLibResponse)
  669. if e := json.Unmarshal(result, &resp); e != nil {
  670. err = fmt.Errorf("sheet :%s json.Unmarshal err: %s", sheetName, e)
  671. utils.FileLog.Info(fmt.Sprintf("sheet :%s json.Unmarshal err: %s", sheetName, e))
  672. return
  673. }
  674. if resp.Ret != 200 {
  675. err = fmt.Errorf("sheet :%s Msg: %s, ErrMsg: %s", sheetName, resp.Msg, resp.ErrMsg)
  676. utils.FileLog.Info(fmt.Sprintf("sheet :%s Msg: %s, ErrMsg: %s", sheetName, resp.Msg, resp.ErrMsg))
  677. return
  678. }
  679. }
  680. return
  681. }
  682. // ConvertXlsToXlsx 调用python服务把旧的xls格式转换成xlsx格式
  683. func ConvertXlsToXlsx(inputFile, outputFile string) (err error) {
  684. pythonScript := "./static/convert_xls_to_xlsx.py"
  685. cmd := exec.Command(utils.PYTHON_PATH, pythonScript, inputFile, outputFile)
  686. // 创建一个缓冲区来捕获输出
  687. var out bytes.Buffer
  688. cmd.Stdout = &out
  689. cmd.Stderr = os.Stderr // 你仍然可以将错误输出到标准错误
  690. // 运行命令
  691. err = cmd.Run()
  692. if err != nil {
  693. err = fmt.Errorf("Error running command: %v\n", err)
  694. fmt.Printf("Error running command: %v\n", err)
  695. return
  696. }
  697. // 检查输出是否包含 "SUCCESS"
  698. output := out.String()
  699. if strings.TrimSpace(output) == "SUCCESS" {
  700. fmt.Println("Conversion completed successfully.")
  701. } else {
  702. err = fmt.Errorf("Conversion failed: %s", output)
  703. fmt.Println("Conversion failed.")
  704. // 如果需要,可以打印更详细的错误信息(如果 Python 脚本打印了的话)
  705. fmt.Println("Output from Python script:", output)
  706. }
  707. return
  708. }