long2short.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "git.aionnect.com/aionnect/go-common/utils"
  6. "git.aionnect.com/aionnect/go-common/utils/jsonutil"
  7. "github.com/tealeg/xlsx"
  8. "net/http"
  9. "net/url"
  10. "strings"
  11. "sync"
  12. )
  13. /*
  14. 长链接批量转短链接小工具
  15. long2short.exe 是Windows版本
  16. long2short 是Mac版本
  17. 不是直接双击使用,需要在控制台或终端中输入命令调用
  18. 用 long2short -help 查看命令参数说明
  19. 使用步骤:
  20. 1. 准备好包含长链接的Excel文件,文件务必保存为xlsx格式
  21. 2. 准备Token,-d参数用于选择生成短链接的网站
  22. 当默认不带-d参数或参数值为1时,从微信公众平台获取短链接,问研发或管理员获取最新有效的微信公众号AccessToken
  23. 当-d参数值为2时,从mrw.so获取短链接,-t 参数传 5f18f5c344bb352d8469636b@bc07aaef03c74705fcd31248643f8bff,如果大量报错,去mrw.so网站注册获取新的开发者key
  24. 当-d参数值为3时,从url.cy获取短链接,不需要-t参数
  25. 3. 打开Windows控制台或Mac终端,执行类似下面的命令
  26. Windows:
  27. .\long2short.exe -d 2 -i .\input.xlsx -o .\output.xlsx -h 长链接 -t AccessToken的值
  28. Mac:
  29. ./long2short -d 2 -i ./input.xlsx -o ./output.xlsx -h 长链接 -t AccessToken的值
  30. 4. 如果出错,会在命令窗口打印错误信息,如果顺利,会在程序所在目录下输出新的xlsx文件
  31. */
  32. func main() {
  33. var domainType int
  34. var inputFileName string
  35. var accessToken string
  36. var headerName string
  37. var outputFileName string
  38. flag.IntVar(&domainType, "d", 1, "生成短链接的域名类型,1:url.cn 2:mrw.so 3:url.cy,默认值1")
  39. flag.StringVar(&inputFileName, "i", "input.xlsx", "xlsx输入文件路径,默认值\"input.xlsx\"")
  40. flag.StringVar(&accessToken, "t", "", "Access Token,必填")
  41. flag.StringVar(&headerName, "h", "合成链接", "长链接列名,默认值\"合成链接\"")
  42. flag.StringVar(&outputFileName, "o", "output.xlsx", "xlsx输出文件路径,会在文件名后加上时间戳,默认值\"output.xlsx\"")
  43. flag.Parse()
  44. if domainType != 3 && accessToken == "" {
  45. println("当-d参数取值为1、2时-t AccessToken 参数是必须的")
  46. return
  47. }
  48. var reqUrl string
  49. if domainType == 3 { // url.cy缩短网址
  50. reqUrl = "https://url.cy/api/create"
  51. } else if domainType == 2 { // mrw.so缩短网址
  52. reqUrl = fmt.Sprintf("http://mrw.so/api.php?format=json&key=%s&url=", accessToken)
  53. } else { // 微信公众平台
  54. reqUrl = fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/shorturl?access_token=%s", accessToken)
  55. }
  56. file, err := xlsx.OpenFile(inputFileName)
  57. if err != nil {
  58. panic(fmt.Sprintf("读取输入文件%s失败 %s", inputFileName, err.Error()))
  59. }
  60. sheets := file.Sheets
  61. if nil == sheets || len(sheets) == 0 {
  62. println("输入文件中没有工作表")
  63. return
  64. }
  65. for _, sheet := range file.Sheets {
  66. rows := sheet.Rows
  67. if nil == rows || len(rows) < 2 {
  68. fmt.Printf("工作表%s中没有数据\n", sheet.Name)
  69. continue
  70. }
  71. header := rows[0]
  72. idx := -1
  73. for i, cell := range header.Cells {
  74. text := cell.String()
  75. if strings.TrimSpace(text) == headerName {
  76. idx = i
  77. }
  78. }
  79. if idx < 0 {
  80. fmt.Printf("工作表%s中没有找到长链接列名列名\"%s\"\n", sheet.Name, headerName)
  81. continue
  82. }
  83. newHeader := header.AddCell()
  84. newHeader.SetString("短链接")
  85. length := len(rows) - 1
  86. pNo := 1
  87. if length > 5 {
  88. pNo = 5
  89. }
  90. if length > 100 {
  91. pNo = 10
  92. }
  93. if length > 500 {
  94. pNo = 100
  95. }
  96. size := length / pNo
  97. var firstArr []*RowInfo
  98. rowGroup := [][]*RowInfo{firstArr}
  99. var j, ct int
  100. for i := 1; i < len(rows); i++ {
  101. rowGroup[j] = append(rowGroup[j], &RowInfo{Row: rows[i], RowNo: i + 1})
  102. ct++
  103. if ct >= size {
  104. var newArr []*RowInfo
  105. rowGroup = append(rowGroup, newArr)
  106. ct = 0
  107. j++
  108. }
  109. }
  110. var counter sync.WaitGroup
  111. counter.Add(length)
  112. for i := 0; i < len(rowGroup); i++ {
  113. arr := rowGroup[i]
  114. go func(domainType int, reqUrl string, idx int, sheetName string, arr []*RowInfo) {
  115. for j := 0; j < len(arr); j++ {
  116. info := arr[j]
  117. call(domainType, reqUrl, idx, sheetName, info)
  118. counter.Done()
  119. }
  120. }(domainType, reqUrl, idx, sheet.Name, arr)
  121. }
  122. counter.Wait()
  123. }
  124. dotIdx := strings.LastIndex(outputFileName, ".")
  125. outputFileName = fmt.Sprintf("%s_%d.xlsx", outputFileName[:dotIdx], utils.NextId())
  126. err = file.Save(outputFileName)
  127. if nil != err {
  128. panic(fmt.Sprintf("写输出文件%s失败 %s", outputFileName, err.Error()))
  129. }
  130. }
  131. var httpPromise = utils.NewRequest()
  132. func call(domainType int, reqUrl string, idx int, sheetName string, info *RowInfo) {
  133. row := info.Row
  134. var longUrl string
  135. if len(row.Cells) > idx {
  136. longUrl = row.Cells[idx].String()
  137. }
  138. longUrl = strings.TrimSpace(longUrl)
  139. if longUrl == "" {
  140. fmt.Printf("工作表%s中第%d行长链接值为空\n", sheetName, info.RowNo)
  141. return
  142. }
  143. var respBody []byte
  144. var err error
  145. if domainType == 3 { // url.cy缩短网址
  146. form := url.Values{}
  147. form.Add("url", longUrl)
  148. respBody, err = httpPromise.
  149. SetHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
  150. Call(http.MethodPost, reqUrl, strings.NewReader(form.Encode()))
  151. } else if domainType == 2 { // mrw.so缩短网址
  152. reqUrl = reqUrl + url.QueryEscape(longUrl)
  153. respBody, err = httpPromise.Call(http.MethodGet, reqUrl, nil)
  154. } else { // 微信公众平台
  155. reqBody := fmt.Sprintf(`{"action":"long2short","long_url":"%s"}`, longUrl)
  156. respBody, err = httpPromise.Call(http.MethodPost, reqUrl, strings.NewReader(reqBody))
  157. }
  158. if nil != err {
  159. fmt.Printf("工作表%s中第%d行请求短链接失败 %s\n", sheetName, info.RowNo, err.Error())
  160. return
  161. }
  162. var shortUrl string
  163. if domainType == 3 { // url.cy缩短网址
  164. var res CYShortResult
  165. err = jsonutil.Unmarshal(respBody, &res)
  166. if nil != err {
  167. fmt.Printf("工作表%s中第%d行请求短链接返回了意外的结果 %s\n", sheetName, info.RowNo, string(respBody))
  168. return
  169. }
  170. res.ShortUrl = strings.TrimSpace(res.ShortUrl)
  171. if res.ErrCode != 1 || res.ShortUrl == "" {
  172. fmt.Printf("工作表%s中第%d行请求短链接出错 %s\n", sheetName, info.RowNo, string(respBody))
  173. return
  174. }
  175. shortUrl = res.ShortUrl
  176. } else if domainType == 2 { // mrw.so缩短网址
  177. var res MRWShortResult
  178. err = jsonutil.Unmarshal(respBody, &res)
  179. if nil != err {
  180. fmt.Printf("工作表%s中第%d行请求短链接返回了意外的结果 %s\n", sheetName, info.RowNo, string(respBody))
  181. return
  182. }
  183. res.ErrMsg = strings.TrimSpace(res.ErrMsg)
  184. res.ShortUrl = strings.TrimSpace(res.ShortUrl)
  185. if res.ErrMsg != "" || res.ShortUrl == "" {
  186. fmt.Printf("工作表%s中第%d行请求短链接出错 %s\n", sheetName, info.RowNo, string(respBody))
  187. return
  188. }
  189. shortUrl = res.ShortUrl
  190. } else { // 微信公众平台
  191. var res WXShortResult
  192. err = jsonutil.Unmarshal(respBody, &res)
  193. if nil != err {
  194. fmt.Printf("工作表%s中第%d行请求短链接返回了意外的结果 %s\n", sheetName, info.RowNo, string(respBody))
  195. return
  196. }
  197. res.ShortUrl = strings.TrimSpace(res.ShortUrl)
  198. if res.ErrCode != 0 || res.ShortUrl == "" {
  199. fmt.Printf("工作表%s中第%d行请求短链接出错 %s\n", sheetName, info.RowNo, string(respBody))
  200. return
  201. }
  202. shortUrl = res.ShortUrl
  203. }
  204. newCell := row.AddCell()
  205. newCell.SetString(shortUrl)
  206. }
  207. type RowInfo struct {
  208. Row *xlsx.Row
  209. RowNo int
  210. }
  211. type WXShortResult struct {
  212. ErrCode int `json:"errcode"`
  213. ErrMsg string `json:"errmsg"`
  214. ShortUrl string `json:"short_url"`
  215. }
  216. type MRWShortResult struct {
  217. ErrMsg string `json:"err"`
  218. ShortUrl string `json:"url"`
  219. }
  220. type CYShortResult struct {
  221. ErrCode int `json:"code"`
  222. ErrMsg string `json:"msg"`
  223. ShortUrl string `json:"data"`
  224. }