upload.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "github.com/gogf/gf/v2/errors/gerror"
  7. "github.com/gogf/gf/v2/frame/g"
  8. "github.com/gogf/gf/v2/net/ghttp"
  9. "github.com/gogf/gf/v2/os/gfile"
  10. "github.com/gogf/gf/v2/os/gtime"
  11. "github.com/gogf/gf/v2/text/gregex"
  12. "github.com/gogf/gf/v2/text/gstr"
  13. "github.com/gogf/gf/v2/util/gconv"
  14. "github.com/gogf/gf/v2/util/grand"
  15. "github.com/tencentyun/cos-go-sdk-v5"
  16. "github.com/tencentyun/cos-go-sdk-v5/debug"
  17. "github.com/tiger1103/gfast/v3/api/v1/common"
  18. "github.com/tiger1103/gfast/v3/internal/app/common/consts"
  19. "github.com/tiger1103/gfast/v3/internal/app/common/model/entity"
  20. "io"
  21. "net/http"
  22. "net/url"
  23. "strconv"
  24. "strings"
  25. "time"
  26. )
  27. type IUpload interface {
  28. UploadFile(ctx context.Context, file *ghttp.UploadFile, checkFileType string, source int) (result common.UploadResponse, err error)
  29. UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int) (result common.UploadMultipleRes, err error)
  30. }
  31. type uploadTmpl struct{}
  32. func Upload() IUpload {
  33. return &uploadTmpl{}
  34. }
  35. // 上传多文件
  36. func (s *uploadTmpl) UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int) (result common.UploadMultipleRes, err error) {
  37. for _, item := range files {
  38. f, e := s.UploadFile(ctx, item, checkFileType, source)
  39. if e != nil {
  40. return
  41. }
  42. result = append(result, &f)
  43. }
  44. return
  45. }
  46. // 上传单文件
  47. func (s *uploadTmpl) UploadFile(ctx context.Context, file *ghttp.UploadFile, checkFileType string, source int) (result common.UploadResponse, err error) {
  48. // 检查文件类型
  49. err = s.CheckType(ctx, checkFileType, file)
  50. if err != nil {
  51. return
  52. }
  53. // 检查文件大小
  54. err = s.CheckSize(ctx, checkFileType, file)
  55. if err != nil {
  56. return
  57. }
  58. // 非图片文件只能上传至本地
  59. if checkFileType == consts.CheckFileTypeFile {
  60. source = consts.SourceLocal
  61. }
  62. switch source {
  63. // 上传至本地
  64. case consts.SourceLocal:
  65. result, err = s.UploadLocal(ctx, file)
  66. // 上传至腾讯云
  67. case consts.SourceTencent:
  68. result, err = s.UploadTencent(ctx, file)
  69. default:
  70. err = errors.New("source参数错误!")
  71. }
  72. if err != nil {
  73. return
  74. }
  75. return
  76. }
  77. // 上传至腾讯云
  78. func (s *uploadTmpl) UploadTencent(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) {
  79. v, err := g.Cfg().Get(ctx, "upload.tencentCOS")
  80. if err != nil {
  81. return
  82. }
  83. m := v.MapStrVar()
  84. var (
  85. upPath = m["upPath"].String()
  86. rawUrl = m["rawUrl"].String()
  87. secretID = m["secretID"].String()
  88. secretKey = m["secretKey"].String()
  89. )
  90. name := gfile.Basename(file.Filename)
  91. name = strings.ToLower(strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6))
  92. name = name + gfile.Ext(file.Filename)
  93. path := upPath + name
  94. url, _ := url.Parse(rawUrl)
  95. b := &cos.BaseURL{BucketURL: url}
  96. client := cos.NewClient(b, &http.Client{
  97. Transport: &cos.AuthorizationTransport{
  98. SecretID: secretID,
  99. SecretKey: secretKey,
  100. Transport: &debug.DebugRequestTransport{
  101. RequestHeader: false,
  102. RequestBody: false,
  103. ResponseHeader: false,
  104. ResponseBody: false,
  105. },
  106. },
  107. })
  108. opt := &cos.ObjectPutOptions{
  109. ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
  110. ContentLength: int64(file.Size),
  111. },
  112. }
  113. var f io.ReadCloser
  114. f, err = file.Open()
  115. if err != nil {
  116. return
  117. }
  118. defer f.Close()
  119. _, err = client.Object.Put(context.Background(), path, f, opt)
  120. result = common.UploadResponse{
  121. Size: file.Size,
  122. Path: rawUrl + path,
  123. FullPath: rawUrl + path,
  124. Name: file.Filename,
  125. Type: file.Header.Get("Content-type"),
  126. }
  127. return
  128. }
  129. // 上传本地
  130. func (s *uploadTmpl) UploadLocal(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) {
  131. if file == nil {
  132. err = errors.New("文件必须!")
  133. return
  134. }
  135. r := g.RequestFromCtx(ctx)
  136. urlPerfix := fmt.Sprintf("http://%s/", r.Host)
  137. p := strings.Trim(consts.UploadPath, "/")
  138. sp := s.getStaticPath(ctx)
  139. if sp != "" {
  140. sp = strings.Trim(sp, "/")
  141. }
  142. nowData := time.Now().Format("2006-01-02")
  143. // 包含静态文件夹的路径
  144. fullDirPath := sp + "/" + p + "/" + nowData
  145. fileName, err := file.Save(fullDirPath, true)
  146. if err != nil {
  147. return
  148. }
  149. // 不含静态文件夹的路径
  150. fullPath := p + "/" + nowData + "/" + fileName
  151. result = common.UploadResponse{
  152. Size: file.Size,
  153. Path: fullPath,
  154. FullPath: urlPerfix + fullPath,
  155. Name: file.Filename,
  156. Type: file.Header.Get("Content-type"),
  157. }
  158. return
  159. }
  160. // 检查上传文件大小
  161. func (s *uploadTmpl) CheckSize(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
  162. var (
  163. configSize *entity.SysConfig
  164. )
  165. if checkFileType == consts.CheckFileTypeFile {
  166. //获取上传大小配置
  167. configSize, err = s.getUpConfig(ctx, consts.FileSizeKey)
  168. if err != nil {
  169. return
  170. }
  171. } else if checkFileType == consts.CheckFileTypeImg {
  172. //获取上传大小配置
  173. configSize, err = s.getUpConfig(ctx, consts.ImgSizeKey)
  174. if err != nil {
  175. return
  176. }
  177. } else {
  178. return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg))
  179. }
  180. var rightSize bool
  181. rightSize, err = s.checkSize(configSize.ConfigValue, file.Size)
  182. if err != nil {
  183. return
  184. }
  185. if !rightSize {
  186. err = gerror.New("上传文件超过最大尺寸:" + configSize.ConfigValue)
  187. return
  188. }
  189. return
  190. }
  191. // 检查上传文件类型
  192. func (s *uploadTmpl) CheckType(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
  193. var (
  194. configType *entity.SysConfig
  195. )
  196. if checkFileType == consts.CheckFileTypeFile {
  197. //获取上传类型配置
  198. configType, err = s.getUpConfig(ctx, consts.FileTypeKey)
  199. if err != nil {
  200. return
  201. }
  202. } else if checkFileType == consts.CheckFileTypeImg {
  203. //获取上传类型配置
  204. configType, err = s.getUpConfig(ctx, consts.ImgTypeKey)
  205. if err != nil {
  206. return
  207. }
  208. } else {
  209. return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg))
  210. }
  211. rightType := s.checkFileType(file.Filename, configType.ConfigValue)
  212. if !rightType {
  213. err = gerror.New("上传文件类型错误,只能包含后缀为:" + configType.ConfigValue + "的文件。")
  214. return
  215. }
  216. return
  217. }
  218. //获取上传配置
  219. func (s *uploadTmpl) getUpConfig(ctx context.Context, key string) (config *entity.SysConfig, err error) {
  220. config, err = Config().GetConfigByKey(ctx, key)
  221. if err != nil {
  222. return
  223. }
  224. if config == nil {
  225. err = gerror.New("上传文件类型未设置,请在后台配置")
  226. return
  227. }
  228. return
  229. }
  230. //判断上传文件类型是否合法
  231. func (s *uploadTmpl) checkFileType(fileName, typeString string) bool {
  232. suffix := gstr.SubStrRune(fileName, gstr.PosRRune(fileName, ".")+1, gstr.LenRune(fileName)-1)
  233. imageType := gstr.Split(typeString, ",")
  234. rightType := false
  235. for _, v := range imageType {
  236. if gstr.Equal(suffix, v) {
  237. rightType = true
  238. break
  239. }
  240. }
  241. return rightType
  242. }
  243. //检查文件大小是否合法
  244. func (s *uploadTmpl) checkSize(configSize string, fileSize int64) (bool, error) {
  245. match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configSize)
  246. if err != nil {
  247. return false, err
  248. }
  249. if len(match) == 0 {
  250. err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB)")
  251. return false, err
  252. }
  253. var cfSize int64
  254. switch gstr.ToUpper(match[2]) {
  255. case "MB", "M":
  256. cfSize = gconv.Int64(match[1]) * 1024 * 1024
  257. case "KB", "K":
  258. cfSize = gconv.Int64(match[1]) * 1024
  259. case "":
  260. cfSize = gconv.Int64(match[1])
  261. }
  262. if cfSize == 0 {
  263. err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB),最大单位为MB")
  264. return false, err
  265. }
  266. return cfSize >= fileSize, nil
  267. }
  268. // 静态文件夹目录
  269. func (s *uploadTmpl) getStaticPath(ctx context.Context) string {
  270. value, _ := g.Cfg().Get(ctx, "server.serverRoot")
  271. if !value.IsEmpty() {
  272. return value.String()
  273. }
  274. return ""
  275. }