github.com/SupenBysz/gf-admin-community@v0.7.4/internal/logic/sys_file/sys_file.go (about) 1 package sys_file 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/SupenBysz/gf-admin-community/sys_consts" 7 "github.com/SupenBysz/gf-admin-community/sys_model" 8 "github.com/SupenBysz/gf-admin-community/sys_model/sys_do" 9 "github.com/SupenBysz/gf-admin-community/sys_model/sys_enum" 10 "github.com/SupenBysz/gf-admin-community/sys_model/sys_hook" 11 "github.com/kysion/base-library/utility/crypto" 12 "github.com/kysion/base-library/utility/daoctl" 13 "github.com/kysion/base-library/utility/kconv" 14 "github.com/kysion/base-library/utility/kmap" 15 "github.com/kysion/oss-library/api/oss_api" 16 "github.com/kysion/oss-library/oss_controller" 17 "github.com/kysion/oss-library/oss_global" 18 "github.com/kysion/oss-library/oss_model" 19 "io" 20 "net/http" 21 "os" 22 "time" 23 24 "github.com/gogf/gf/v2/encoding/gbase64" 25 "github.com/gogf/gf/v2/encoding/gjson" 26 "github.com/gogf/gf/v2/errors/gerror" 27 "github.com/gogf/gf/v2/frame/g" 28 "github.com/gogf/gf/v2/os/gfile" 29 "github.com/gogf/gf/v2/os/gtime" 30 "github.com/gogf/gf/v2/text/gstr" 31 "github.com/gogf/gf/v2/util/gconv" 32 "github.com/yitter/idgenerator-go/idgen" 33 34 "github.com/SupenBysz/gf-admin-community/sys_model/sys_dao" 35 "github.com/SupenBysz/gf-admin-community/sys_model/sys_entity" 36 "github.com/SupenBysz/gf-admin-community/sys_service" 37 ) 38 39 type hookInfo sys_model.KeyValueT[int64, sys_hook.FileHookInfo] 40 41 type sFile struct { 42 cachePrefix string 43 hookArr []hookInfo 44 CacheDuration time.Duration 45 } 46 47 func init() { 48 sys_service.RegisterFile(New()) 49 } 50 51 func New() *sFile { 52 return &sFile{ 53 cachePrefix: "upload", 54 hookArr: make([]hookInfo, 0), 55 CacheDuration: time.Hour * 2, 56 } 57 } 58 59 // InstallHook 安装Hook 60 func (s *sFile) InstallHook(state sys_enum.UploadEventState, hookFunc sys_hook.FileHookFunc) int64 { 61 item := hookInfo{Key: idgen.NextId(), Value: sys_hook.FileHookInfo{Key: state, Value: hookFunc}} 62 s.hookArr = append(s.hookArr, item) 63 return item.Key 64 } 65 66 // UnInstallHook 卸载Hook 67 func (s *sFile) UnInstallHook(savedHookId int64) { 68 newFuncArr := make([]hookInfo, 0) 69 for _, item := range s.hookArr { 70 if item.Key != savedHookId { 71 newFuncArr = append(newFuncArr, item) 72 continue 73 } 74 } 75 s.hookArr = newFuncArr 76 } 77 78 // CleanAllHook 清除Hook 79 func (s *sFile) CleanAllHook() { 80 s.hookArr = make([]hookInfo, 0) 81 } 82 83 // Upload 统一上传文件 84 func (s *sFile) Upload(ctx context.Context, in sys_model.FileUploadInput) (*sys_entity.SysFile, error) { 85 sessionUser := sys_service.SysSession().Get(ctx).JwtClaimsUser 86 uploadPath := g.Cfg().MustGet(ctx, "upload.tempPath").String() 87 // 获取系统默认的临时文件的存储路径 88 tmpPath := gfile.Temp("upload") 89 { 90 // 上传文件夹初始化 91 if uploadPath == "" { 92 uploadPath = tmpPath 93 } 94 95 // temp/upload/ ---> temp/upload 96 if len(uploadPath) > 0 && gstr.HasSuffix(uploadPath, "/") { 97 uploadPath = uploadPath[0 : len(uploadPath)-1] 98 } 99 100 // 为了下面存储userCacheJson 101 tmpPath = uploadPath 102 103 uploadPath = uploadPath + "/" + gtime.Now().Format("Ymd") 104 // 目录不存在则创建 105 if !gfile.Exists(uploadPath) { 106 gfile.Mkdir(uploadPath) 107 gfile.Chmod(uploadPath, gfile.DefaultPermCopy) 108 } 109 } 110 111 { 112 // 清理2天前上传的临时文件,释放空间 113 uploadExpirePath := tmpPath + "/" + gtime.Now().AddDate(0, 0, -2).Format("Ymd") 114 if gfile.Exists(uploadExpirePath) { 115 gfile.Remove(uploadExpirePath) 116 } 117 } 118 119 newUserUploadItemsCache := kmap.New[int64, *sys_model.FileInfo]() 120 strUserId := gconv.String(sessionUser.Id) 121 // 逻辑修改 122 userCacheJsonPath := gfile.Join(tmpPath, s.cachePrefix+"_"+strUserId+".json") // tmpPath : 没有年月日 123 //userCacheJsonPath := gfile.Join(uploadPath, s.cachePrefix+"_"+strUserId+".json") // uploadPath:有年月日 124 { 125 // 用户指定时间内上传文件最大数量限制 126 userUploadInfoCache := kmap.New[int64, *sys_model.FileInfo]() 127 jsonString := gfile.GetContents(userCacheJsonPath) 128 129 g.Try(ctx, func(ctx context.Context) { 130 gjson.DecodeTo(jsonString, &userUploadInfoCache) 131 }) 132 133 now := gtime.Now() 134 userUploadInfoCache.Iterator(func(k int64, item *sys_model.FileInfo) bool { 135 if item.ExpiresAt.Add(s.CacheDuration).After(now) { 136 newUserUploadItemsCache.Set(item.Id, item) 137 } 138 return true 139 }) 140 141 fileMaxUploadCountMinute := g.Cfg().MustGet(ctx, "service.fileMaxUploadCountMinute", 10).Int() 142 // 限定1分钟内允许上传的最大数量 143 if newUserUploadItemsCache.Size() >= fileMaxUploadCountMinute { 144 return nil, sys_service.SysLogs().ErrorSimple(ctx, gerror.New("您上传得太频繁,请稍后再操作"), "", sys_dao.SysFile.Table()) 145 } 146 } 147 148 if in.Name != "" { 149 in.File.Filename = in.Name 150 } 151 // 将文件写入临时目录 152 id := idgen.NextId() 153 idStr := gconv.String(id) 154 dateDirName := uploadPath 155 gfile.Chmod(dateDirName, gfile.DefaultPermCopy) 156 savePath := gfile.Join(dateDirName, idStr) 157 fileName, err := in.File.Save(savePath, in.RandomName) 158 if err != nil { 159 return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "文件保存失败", sys_dao.SysFile.Table()) 160 } 161 162 absPath := gfile.Join(savePath, fileName) 163 data := &sys_model.FileInfo{ 164 SysFile: sys_entity.SysFile{ 165 Id: id, 166 Name: fileName, 167 Src: absPath, 168 Url: absPath, 169 LocalPath: absPath, 170 Ext: gfile.Ext(absPath), 171 Size: in.File.Size, 172 Category: "", 173 UserId: sessionUser.Id, 174 UnionMainId: sessionUser.UnionMainId, 175 CreatedAt: gtime.Now(), 176 UpdatedAt: nil, 177 }, 178 ExpiresAt: gtime.Now().Add(s.CacheDuration), 179 } 180 181 // 缓存前的Hook广播 182 g.Try(ctx, func(ctx context.Context) { 183 for _, hook := range s.hookArr { 184 if hook.Value.Key.Code()&sys_enum.Upload.EventState.BeforeCache.Code() == sys_enum.Upload.EventState.BeforeCache.Code() { 185 hook.Value.Value(ctx, sys_enum.Upload.EventState.BeforeCache, &data.SysFile) 186 } 187 } 188 }) 189 190 // 追加到缓存队列 191 newUserUploadItemsCache.Set(data.Id, data) 192 193 // 写入缓存文件到指定的文件路径 194 gfile.PutContents(userCacheJsonPath, gjson.MustEncodeString(newUserUploadItemsCache)) // 路径 195 196 // 缓存后的Hook广播 197 g.Try(ctx, func(ctx context.Context) { 198 for _, hook := range s.hookArr { 199 if hook.Value.Key.Code()&sys_enum.Upload.EventState.AfterCache.Code() == sys_enum.Upload.EventState.AfterCache.Code() { 200 hook.Value.Value(ctx, sys_enum.Upload.EventState.AfterCache, &data.SysFile) 201 } 202 } 203 }) 204 205 //data.Url = s.MakeFileUrl(ctx,data.Id) 206 // data.Url = 远程接口地址 207 return &data.SysFile, nil 208 } 209 210 // GetUploadFile 根据上传ID 获取上传文件信息 211 func (s *sFile) GetUploadFile(ctx context.Context, uploadId int64, userId int64, message ...string) (*sys_model.FileInfo, error) { 212 strUserId := gconv.String(userId) 213 tempUploadPath := g.Cfg().MustGet(ctx, "upload.tempPath").String() 214 // tempUploadPath := g.Cfg().MustGet(ctx, "upload.tempPath").String() + "/" + gtime.Now().Format("Ymd") + "/" 215 // 获取系统默认的临时文件的存储路径 216 tmpPath := gfile.Temp("upload") 217 if tempUploadPath == "" { 218 tempUploadPath = tmpPath 219 } 220 221 userCacheJsonPath := gfile.Join(tempUploadPath, s.cachePrefix+"_"+strUserId+".json") 222 223 userUploadInfoCache := kmap.New[int64, *sys_model.FileInfo]() 224 225 gjson.DecodeTo(gfile.GetContents(userCacheJsonPath), &userUploadInfoCache) 226 227 // 从oss获取: upload_8610476923551813.json 228 if userUploadInfoCache.IsEmpty() { 229 //bucketName := g.Cfg().MustGet(ctx, "oss.bucketName").String() // 各端的oss 230 bucketName := g.Cfg().MustGet(ctx, "oss.masterBucketName").String() // 平台的oss 231 url, _ := s.GetOssFileSingUrl(ctx, bucketName, userCacheJsonPath) 232 233 if url != "" { 234 // 3、根据文件的签名URL,直接io读取文件,然后解析使用 235 v, err := http.Get(url) 236 if err != nil { 237 return nil, sys_service.SysLogs().WarnSimple(ctx, err, "Http get ["+url+"] failed!", sys_dao.SysFile.Table()) 238 } 239 defer v.Body.Close() 240 // 读取文件字节流 241 content, err := io.ReadAll(v.Body) 242 if err != nil { 243 return nil, sys_service.SysLogs().WarnSimple(ctx, err, "Read http response failed! "+url, sys_dao.SysFile.Table()) 244 } 245 if string(content) != "" { 246 // 将云存储中下载到本地的文件读取内容出来,序列化到userUploadInfoCache 247 gjson.DecodeTo(string(content), &userUploadInfoCache) 248 } 249 250 // 3、根据文件的签名URL,下载文件到本地 (Pass,本地带宽压力大) 251 // 临时的转载路径 252 //filePath := "temp/download" + "/" + gtime.Now().Format("Ymd") ( userCacheJson.json路径不需要加Ymd) 253 //filePath := g.Cfg().MustGet(ctx, "download.tempPath", "temp/download").String() 254 // 255 //// 目录不存在则创建 256 //if !gfile.Exists(filePath) { 257 // gfile.Mkdir(filePath) 258 // gfile.Chmod(filePath, gfile.DefaultPermCopy) 259 //} 260 // 261 //filePath += "/" + s.cachePrefix + "_" + strUserId + ".json" 262 263 //info.LocalPath = filePath 264 265 //withURL, err := s.GetOssFileWithURL(ctx, bucketName, filePath, url) 266 //if err != nil { 267 // fmt.Println(err) 268 //} 269 //if withURL { 270 // // 将云存储中下载到本地的文件读取内容出来,序列化到userUploadInfoCache 271 // gjson.DecodeTo(gfile.GetContents(filePath), &userUploadInfoCache) 272 //} 273 } 274 } 275 276 messageStr := "文件不存在" 277 if len(message) > 0 { 278 messageStr = message[0] 279 } 280 281 item, has := userUploadInfoCache.Search(uploadId) 282 if item != nil && has { 283 return item, nil 284 } 285 286 return nil, sys_service.SysLogs().ErrorSimple(ctx, nil, messageStr, sys_dao.SysFile.Table()) 287 } 288 289 // SaveFile 保存文件 290 func (s *sFile) SaveFile(ctx context.Context, storageAddr string, info *sys_model.FileInfo) (*sys_model.FileInfo, error) { 291 292 if gfile.GetContents(info.Src) == "" { // oss云存储 (TODO 注意:如果部署了NFS,那么获取文件就是获取得到的) 293 // 1.获取远端的临时temp文件,2.进行拉取下载到本地temp目录,3.然后将持久化 oss + 本地 294 //bucketName := g.Cfg().MustGet(ctx, "oss.bucketName").String() 295 bucketName := g.Cfg().MustGet(ctx, "oss.masterBucketName").String() 296 url, _ := s.GetOssFileSingUrl(ctx, bucketName, info.Src) 297 298 if url != "" { // TODO 优化:不要下载,直接通过url读取文件内容,然后写到目标路径 storageAddr (暂未优化) 299 filePath := g.Cfg().MustGet(ctx, "download.tempPath", "temp/download").String() 300 filePath += "/" + gtime.Now().Format("Ymd") + "/" + gconv.String(info.Id) 301 302 // 目录不存在则创建 303 if !gfile.Exists(filePath) { 304 gfile.Mkdir(filePath) 305 gfile.Chmod(filePath, gfile.DefaultPermCopy) 306 } 307 308 filePath += "/" + info.Name 309 310 { 311 // 清理2天前下载的临时文件,释放空间 312 downloadExpirePath := g.Cfg().MustGet(ctx, "download.tempPath", "temp/download").String() + "/" + gtime.Now().AddDate(0, 0, -2).Format("Ymd") 313 if gfile.Exists(downloadExpirePath) { 314 gfile.Remove(downloadExpirePath) 315 } 316 } 317 318 withURL, err := s.GetOssFileWithURL(ctx, bucketName, filePath, url) 319 if err != nil { 320 fmt.Println(err) 321 } 322 if withURL { 323 info.Src = filePath 324 } 325 } 326 } 327 328 if !gfile.Exists(info.Src) { // oss 不存在, 本地也不存在 329 return nil, sys_service.SysLogs().ErrorSimple(ctx, nil, "文件不存在", sys_dao.SysFile.Table()) 330 } 331 332 if storageAddr == info.Src { 333 return info, nil 334 } 335 336 // 文件保存前的Hook 337 g.Try(ctx, func(ctx context.Context) { 338 for _, hook := range s.hookArr { 339 if hook.Value.Key.Code()&sys_enum.Upload.EventState.BeforeSave.Code() == sys_enum.Upload.EventState.BeforeSave.Code() { 340 hook.Value.Value(ctx, sys_enum.Upload.EventState.BeforeSave, &info.SysFile) 341 } 342 } 343 }) 344 345 gfile.Chmod(gfile.Dir(storageAddr), gfile.DefaultPermCopy) 346 if err := gfile.CopyFile(info.Src, storageAddr); err != nil { 347 return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "文件保存失败", sys_dao.SysFile.Table()) 348 } 349 350 // 记录到数据表 351 data := kconv.Struct(info.SysFile, &sys_do.SysFile{}) 352 data.Src = storageAddr 353 //data.Url = storageAddr // 优化于2024年0509,URL一般会放远端的文件存储空间的URL,通过Hook修改 354 355 count, err := sys_dao.SysFile.Ctx(ctx).Where(sys_do.SysFile{Id: data.Id}).Count() 356 if count == 0 { 357 _, err = sys_dao.SysFile.Ctx(ctx).Data(data).OmitEmpty().Insert() 358 } else { 359 _, err = sys_dao.SysFile.Ctx(ctx).Data(data).OmitEmpty().Where(sys_do.SysFile{Id: data.Id}).Update() 360 } 361 362 if err != nil { 363 return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "文件保存失败", sys_dao.SysFile.Table()) 364 } 365 366 // 文件保存后的Hook 367 g.Try(ctx, func(ctx context.Context) { 368 for _, hook := range s.hookArr { // 微服务模式:同步持久化到oss 369 if hook.Value.Key.Code()&sys_enum.Upload.EventState.AfterSave.Code() == sys_enum.Upload.EventState.AfterSave.Code() { 370 hook.Value.Value(ctx, sys_enum.Upload.EventState.AfterSave, kconv.Struct(data, &sys_entity.SysFile{})) 371 } 372 } 373 }) 374 return info, nil 375 } 376 377 // UploadIDCard 上传身份证照片 378 func (s *sFile) UploadIDCard(ctx context.Context, in sys_model.OCRIDCardFileUploadInput) (*sys_model.IDCardWithOCR, error) { 379 380 result, err := s.Upload(ctx, in.FileUploadInput) 381 382 if err != nil { 383 return nil, err 384 } 385 386 ret := sys_model.IDCardWithOCR{ 387 SysFile: *result, 388 } 389 390 fileBase64, err := gbase64.EncodeFileToString(result.Src) 391 392 if err != nil { 393 return &ret, sys_service.SysLogs().ErrorSimple(ctx, nil, "解析证照信息失败", sys_dao.SysFile.Table()) 394 } 395 396 imageBase64 := fileBase64 397 398 OCRInfo, err := sys_service.SdkBaidu().OCRIDCard(ctx, imageBase64, in.DetectRisk, in.IDCardSide) 399 400 if err != nil { 401 return &ret, err 402 } 403 404 // ret.Id = result.Id 405 ret.OCRIDCardA = OCRInfo.OCRIDCardA 406 ret.OCRIDCardB = OCRInfo.OCRIDCardB 407 408 return &ret, nil 409 } 410 411 // UploadBankCard 上传银行卡照片 412 func (s *sFile) UploadBankCard(ctx context.Context, in sys_model.BankCardWithOCRInput) (*sys_model.BankCardWithOCR, error) { 413 result, err := s.Upload(ctx, in.FileUploadInput) 414 415 if err != nil { 416 return nil, err 417 } 418 419 ret := sys_model.BankCardWithOCR{ 420 SysFile: *result, 421 } 422 423 // 图片数据进行base64编码 424 fileBase64, err := gbase64.EncodeFileToString(result.Src) 425 426 if err != nil { 427 return &ret, sys_service.SysLogs().ErrorSimple(ctx, nil, "解析证照信息失败", sys_dao.SysFile.Table()) 428 } 429 430 bankCard, err := sys_service.SdkBaidu().OCRBankCard(ctx, fileBase64) 431 432 if err != nil { 433 return &ret, err 434 } 435 436 // 给返回数据赋值 437 ret.BaiduSdkOCRBankCard.OCRBankCard = *bankCard 438 // ret.Id = result.Id 439 return &ret, nil 440 } 441 442 // UploadBusinessLicense 上传营业执照照片 443 func (s *sFile) UploadBusinessLicense(ctx context.Context, in sys_model.OCRBusinessLicense) (*sys_model.BusinessLicenseWithOCR, error) { 444 result, err := s.Upload(ctx, in.FileUploadInput) 445 446 if err != nil { 447 return nil, err 448 } 449 450 ret := sys_model.BusinessLicenseWithOCR{ 451 SysFile: *result, 452 } 453 454 fileBase64, err := gbase64.EncodeFileToString(result.Src) 455 456 if err != nil { 457 return &ret, sys_service.SysLogs().ErrorSimple(ctx, gerror.New("解析证照信息失败"), "", sys_dao.SysFile.Table()) 458 } 459 460 imageBase64 := fileBase64 461 462 OCRInfo, err := sys_service.SdkBaidu().OCRBusinessLicense(ctx, imageBase64) 463 464 if err != nil { 465 return &ret, err 466 } 467 468 ret.BusinessLicenseOCR = *OCRInfo 469 // ret.Id = result.Id 470 return &ret, nil 471 } 472 473 // DownLoadFile 下载文件 474 func (s *sFile) DownLoadFile(ctx context.Context, savePath string, url string) (string, error) { // 文件获取url 475 // 校验目标存储路径是否存在 476 if !gfile.Exists(gfile.Dir(savePath)) { 477 return "", sys_service.SysLogs().WarnSimple(ctx, nil, "The save path does not exist! "+savePath, sys_dao.SysFile.Table()) 478 } 479 480 tmpPath := g.Cfg().MustGet(ctx, "download.tempPath", "temp/download").String() 481 482 gfile.Mkdir(tmpPath) 483 gfile.Chmod(tmpPath, 755) 484 485 tmpPath = gfile.Join(tmpPath, gconv.String(idgen.NextId())) 486 487 // 通过http获取文件 488 v, err := http.Get(url) 489 if err != nil { 490 return "", sys_service.SysLogs().WarnSimple(ctx, err, "Http get ["+url+"] failed!", sys_dao.SysFile.Table()) 491 } 492 defer v.Body.Close() 493 // 读取文件字节流 494 content, err := io.ReadAll(v.Body) 495 if err != nil { 496 return "", sys_service.SysLogs().WarnSimple(ctx, err, "Read http response failed! "+url, sys_dao.SysFile.Table()) 497 } 498 // 写入临时路径 499 err = os.WriteFile(tmpPath, content, 755) 500 if err != nil { 501 return "", sys_service.SysLogs().WarnSimple(ctx, err, "Save to sys_file failed! "+url, sys_dao.SysFile.Table()) 502 } 503 // 将临时文件的文件拷贝到目标路径中 504 if nil != gfile.CopyFile(tmpPath, savePath) { 505 return "", sys_service.SysLogs().WarnSimple(ctx, err, "Copy sys_file failed! "+savePath, sys_dao.SysFile.Table()) 506 } 507 508 return savePath, nil 509 } 510 511 // GetFileById 根据id获取并返回文件信息 512 func (s *sFile) GetFileById(ctx context.Context, id int64, errorMessage string) (*sys_model.FileInfo, error) { // 获取图片可以是id、token、路径 513 sessionUser := sys_service.SysSession().Get(ctx).JwtClaimsUser 514 515 { 516 // 优先尝试从缓存获取图片 (缓存查找) 517 cacheFile, _ := s.GetUploadFile(ctx, id, sessionUser.Id, errorMessage) 518 519 if cacheFile != nil { 520 //cacheFile.Url = s.MakeFileUrl(ctx, cacheFile.Id) 521 //cacheFile.LocalPath = s.MakeFileUrl(ctx, cacheFile.Id) // TODO 20240506 522 //cacheFile.LocalPath = s.MakeFileUrlByPath(ctx, cacheFile.Src) // 20240509 没什么必要吧,SRC就是本地的路径 523 cacheFile.LocalPath = cacheFile.Src // 20240509 本地路径 == src 524 return cacheFile, nil 525 } 526 } 527 { 528 file := &sys_entity.SysFile{} 529 model := sys_dao.SysFile.Ctx(ctx). 530 Where(sys_do.SysFile{Id: id}) 531 532 if sessionUser.IsAdmin == false { 533 // 判断用户是否有权限 534 can, _ := sys_service.SysPermission().CheckPermission(ctx, sys_enum.File.PermissionType.ViewDetail) 535 if can == false { 536 model = model.Where(sys_do.SysFile{UnionMainId: sessionUser.UnionMainId}) 537 } 538 } 539 err := model.Scan(file) 540 541 if err != nil { 542 return nil, sys_service.SysLogs().WarnSimple(ctx, err, errorMessage, sys_dao.SysFile.Table()) 543 } 544 545 //file.Url = s.GetUrlById(file.Id) 546 file.LocalPath = s.MakeFileUrl(ctx, file.Id) 547 548 return &sys_model.FileInfo{ 549 SysFile: *file, 550 ExpiresAt: nil, 551 }, nil 552 } 553 } 554 555 // MakeFileUrl 图像id换取url: 拼接三个参数,缓存fileInfo、然后返回url + 三参 556 func (s *sFile) MakeFileUrl(ctx context.Context, id int64) string { 557 file := &sys_entity.SysFile{} 558 err := sys_dao.SysFile.Ctx(ctx). 559 Where(sys_do.SysFile{Id: id}).Scan(file) 560 561 if err != nil { 562 return "" 563 } 564 565 sign := makeSign(file.Src, file.Id) 566 srcBase64 := string(gbase64.Encode([]byte(file.Src))) 567 fileId := gconv.String(file.Id) 568 569 // 获取到api接口前缀 570 apiPrefix := sys_consts.Global.ApiPreFix 571 572 // 拼接请求url 573 return apiPrefix + "/common/getFile?sign=" + sign + "&path=" + srcBase64 + "&id=" + fileId // id: oss.masterBucketName 574 } 575 576 // MakeFileUrlByPath 文件path换取url: 拼接三个参数,缓存签名数据、然后返回url + 三参 577 func (s *sFile) MakeFileUrlByPath(ctx context.Context, path string) string { 578 cId := idgen.NextId() 579 sign := makeSign(path, cId) 580 581 srcBase64 := string(gbase64.Encode([]byte(path))) 582 fileId := gconv.String(cId) 583 584 // 获取到api接口前缀 585 apiPrefix := sys_consts.Global.ApiPreFix 586 587 // 拼接请求url 588 return apiPrefix + "/common/getFile?sign=" + sign + "&path=" + srcBase64 + "&cid=" + fileId // cid: oss.bucketName 589 } 590 591 // GetFile 获取图片 公开 (srcBase64 + srcMd5 + fileId) ==> md5加密 592 func (s *sFile) GetFile(ctx context.Context, sign, srcBase64 string, id int64, cId int64) (*sys_model.FileInfo, error) { 593 // oss --> id: oss.masterBucketName cid: oss.bucketName 594 if cId != 0 { // 缓存 595 // 验签 596 srcDecode, _ := gbase64.DecodeToString(srcBase64) 597 checkSign := makeSign(srcDecode, cId) 598 // 校验签名 599 if sign != checkSign { 600 return nil, sys_service.SysLogs().WarnSimple(ctx, nil, "签名校验失败", sys_dao.SysFile.Table()) 601 } 602 603 // 资源:本地|oss远端 604 //if !gfile.IsFile(srcDecode) { // 跨进程资源,就会获取不到资源,所以需要查询oss的云存储空间 605 if gfile.GetContents(srcDecode) == "" { // 跨进程资源,就会获取不到资源,所以需要查询oss的云存储空间 606 607 //s.GetUploadFile(ctx, cId,) userId 拿不到 608 //split := gstr.Split(srcDecode, "/") 609 610 // 从oss 获取 611 bucketName := g.Cfg().MustGet(ctx, "oss.masterBucketName").String() 612 url, _ := s.GetOssFileSingUrl(ctx, bucketName, srcDecode) 613 614 if url != "" { 615 srcDecode = url 616 617 /* 618 oos 目的就是:降低服务器的带宽压力,所以不要下载到本地。直接使用url跳转 619 filePath := "temp/download" 620 filePath += "/" + gtime.Now().Format("Ymd") + "/" + gconv.String(cId) 621 622 //filePath := srcDecode 623 624 // 目录不存在则创建 625 if !gfile.Exists(filePath) { 626 gfile.Mkdir(filePath) 627 gfile.Chmod(filePath, gfile.DefaultPermCopy) 628 } 629 630 filePath += "/" + split[len(split)-1] 631 632 withURL, err := s.GetOssFileWithURL(ctx, bucketName, filePath, url) 633 if err != nil { 634 fmt.Println(err) 635 } 636 637 if withURL { 638 srcDecode = filePath 639 } 640 */ 641 } 642 } 643 644 //if !gfile.IsFile(srcDecode) { 645 // return nil, sys_service.SysLogs().WarnSimple(ctx, nil, "文件不存在", sys_dao.SysFile.Table()) 646 //} 647 648 // 签名通过,直接根据src返回 649 return &sys_model.FileInfo{ 650 SysFile: sys_entity.SysFile{ 651 Src: srcDecode, 652 }, 653 }, nil 654 } 655 656 // 优先从缓存获取,缓存要是获取不到,那么从数据库加载文件信息,从而加载文件 657 // 先获取图片,进行签名、验签,验签通过查找图片,如果不在缓存中的图片从数据库查询后进行缓存起来 缓存key == sign 658 fileInfo := daoctl.GetById[sys_entity.SysFile](sys_dao.SysFile.Ctx(ctx), id) 659 if fileInfo == nil { 660 return nil, sys_service.SysLogs().WarnSimple(ctx, nil, "文件不存在,请检查id", sys_dao.SysFile.Table()) 661 } 662 663 { 664 srcDecode, _ := gbase64.DecodeToString(srcBase64) 665 checkSign := makeSign(srcDecode, id) 666 // 校验签名 667 if sign != checkSign { 668 return nil, sys_service.SysLogs().WarnSimple(ctx, nil, "签名校验失败", sys_dao.SysFile.Table()) 669 } 670 } 671 672 { 673 // 优先尝试从缓存获取图片 (缓存查找,sign为key) 674 //cacheFile, _ := g.Redis().Get(ctx, sign) // redis 缓存 675 cacheFile, _ := g.DB().GetCache().Get(ctx, sign) // 内存缓存 676 if cacheFile.Val() != nil { 677 data := sys_model.FileInfo{} 678 679 gconv.Struct(cacheFile, &data) 680 return &data, nil 681 } 682 } 683 684 { 685 // 从数据库找,找到后缓存起来 686 687 /* 688 拉取图片方案,根据src判断是本地还是远端的图片: 689 如果是存在于本地的图片,直接拉出来,然后进行缓存,将Src赋值到cachePath上面 690 如果是存在于远端的图片,根据src从远端获取至本地的temp目录下面,然后将cachePath缓存本地的临时路径 691 */ 692 693 // 判断是否是本地图片 694 file := gfile.GetContents(fileInfo.Src) // 后期这里应该是cachePath 695 696 if file != "" { // 本地 697 data := sys_model.FileInfo{ 698 SysFile: *fileInfo, 699 ExpiresAt: gtime.New(time.Hour * 24), 700 } 701 702 g.DB().GetCache().Set(ctx, sign, data, time.Hour*24) 703 704 return &data, nil 705 } else { // 远端 706 // 获取远端图片,进行拉取到本地temp目录,然后将src赋值 (Pref:不需要下载到本地,直接使用oss的url) 707 708 // bucketName := g.Cfg().MustGet(ctx, "oss.bucketName").String() // 各端资源目录 709 // TODO 问题:不能直接这样拿,活动图片是商家上传的,如果直接这样的话,取资源的oss路径就是operator了,实际上应该是merchant的oss资源路径。 710 711 // 方案1: 需要有一个主体&oss-bucket的映射表 712 // 方案2: fileInfo,存储bucket的bucketDomain 或者bucket-name 713 split := gstr.Split(fileInfo.Url, ".") // mlj-merchant-service.oss-cn-shenzhen.aliyuncs.com 714 bucketName := split[0] 715 url, _ := s.GetOssFileSingUrl(ctx, bucketName, fileInfo.Src) 716 717 // TODO 本来我想直接使用url作为src输出,但是这个方法g.RequestFromCtx(ctx).Response.ServeFile(file.Src) 不支持输出网络路径的图片,所以需要临时下载 (Pref 不需要,直接使用这个url,进行Redirect跳转即可) 718 if url != "" { 719 fileInfo.Src = url 720 721 //filePath := "temp/download" 722 //filePath += "/" + gtime.Now().Format("Ymd") + "/" + gconv.String(cId) 723 // 724 //// 目录不存在则创建 725 //if !gfile.Exists(filePath) { 726 // gfile.Mkdir(filePath) 727 // gfile.Chmod(filePath, gfile.DefaultPermCopy) 728 //} 729 // 730 //filePath += "/" + fileInfo.Name 731 // 732 //withURL, err := s.GetOssFileWithURL(ctx, bucketName, filePath, url) 733 //if err != nil { 734 // fmt.Println(err) 735 //} 736 // 737 //if withURL { 738 // fileInfo.Src = filePath 739 //} 740 } 741 742 data := sys_model.FileInfo{ 743 SysFile: *fileInfo, 744 ExpiresAt: gtime.New(time.Hour * 24), 745 } 746 747 g.DB().GetCache().Set(ctx, sign, data, time.Hour*24) 748 749 return &data, nil 750 //fileInfo.LocalPath = fileInfo.Src 751 752 //daoctl.UpdateWithError(sys_dao.SysFile.Ctx(ctx).Where(sys_dao.SysFile.Columns().Id, fileInfo.Id).Data(sys_do.SysFile{CachePath: fileInfo.CachePath})) 753 } 754 } 755 756 return nil, nil 757 } 758 759 // 签名数据,组成部分:(srcBase64 + srcMd5 + fileId) ==> md5加密 760 func makeSign(fileSrc string, id int64) string { 761 srcBase64 := string(gbase64.Encode([]byte(fileSrc))) 762 srcMd5 := crypto.Md5Hash(fileSrc) 763 fileId := string(id) 764 765 cryptoData := srcBase64 + srcMd5 + fileId 766 checkSign := crypto.Md5Hash(cryptoData) 767 768 return checkSign 769 } 770 771 // UploadPicture 上传图片并审核 772 func (s *sFile) UploadPicture(ctx context.Context, input sys_model.PictureWithOCRInput) (*sys_model.PictureWithOCR, error) { 773 774 result, err := s.Upload(ctx, input.FileUploadInput) 775 776 if err != nil { 777 return nil, err 778 } 779 780 ret := sys_model.PictureWithOCR{ 781 SysFile: *result, 782 Data: make([]sys_model.DescriptionData, 0), 783 } 784 785 fileBase64, err := gbase64.EncodeFileToString(result.Src) 786 787 if err != nil { 788 return &ret, sys_service.SysLogs().ErrorSimple(ctx, nil, "图片审核失败", sys_dao.SysFile.Table()) 789 } 790 791 imageBase64 := fileBase64 792 793 PictureInfo, err := sys_service.SdkBaidu().AuditPicture(ctx, imageBase64, input.ImageType) 794 795 if err != nil { 796 return &ret, err 797 } 798 799 ret.Conclusion = PictureInfo.Conclusion 800 ret.ConclusionType = PictureInfo.ConclusionType 801 ret.Data = PictureInfo.Data 802 803 return &ret, err 804 } 805 806 // GetOssFileSingUrl 获取文件的签名访问URL 807 func (s *sFile) GetOssFileSingUrl(ctx context.Context, bucketName, objectKey string) (string, error) { 808 modules := oss_global.Global.Modules 809 810 // 1、获取默认的渠道商 (优先级最高的渠道商) 811 provider, err := modules.OssServiceProviderConfig().GetProviderByPriority(ctx, 1) 812 if err != nil { 813 return "", err 814 } 815 816 // 2、获取存储对象Bucket 817 bucketConfig, err := modules.OssBucketConfig().GetByBucketNameAndProviderNo(ctx, bucketName, provider.ProviderNo, 1) 818 if err != nil { 819 return "", err 820 } 821 822 // 24小时过期 823 duration := gconv.Int64(60 * 60 * 24) 824 825 reqInfo := oss_api.GetFileSingURLReq{ 826 GetFileSingURL: oss_model.GetFileSingURL{ 827 MustInfo: oss_model.MustInfo{ 828 ProviderId: provider.Id, 829 ProviderNo: provider.ProviderNo, 830 BucketName: bucketConfig.BucketName, 831 }, 832 ObjectKey: objectKey, 833 ExpiredInSec: duration, 834 }, 835 } 836 837 // 2、调用oss 进行请求 838 ret, err := oss_controller.OssFile(modules).GetFileSingURL(ctx, &reqInfo) 839 840 return (string)(ret), err 841 } 842 843 // GetOssFileWithURL 根据文件的签名访问URL获取文件 844 func (s *sFile) GetOssFileWithURL(ctx context.Context, bucketName, filePath, singUrl string) (bool, error) { 845 modules := oss_global.Global.Modules 846 847 // 1、获取默认的渠道商 (优先级最高的渠道商) 848 provider, err := modules.OssServiceProviderConfig().GetProviderByPriority(ctx, 1) 849 if err != nil { 850 return false, err 851 } 852 853 // 2、获取存储对象Bucket 854 bucketConfig, err := modules.OssBucketConfig().GetByBucketNameAndProviderNo(ctx, bucketName, provider.ProviderNo, 1) 855 if err != nil { 856 return false, err 857 } 858 859 reqInfo := oss_api.GetObjectToFileWithURLReq{ 860 GetObjectToFileWithURL: oss_model.GetObjectToFileWithURL{ 861 MustInfo: oss_model.MustInfo{ 862 ProviderId: provider.Id, 863 ProviderNo: provider.ProviderNo, 864 BucketName: bucketConfig.BucketName, 865 }, 866 FilePath: filePath, 867 SingUrl: singUrl, 868 }, 869 } 870 871 // 2、调用oss 进行请求 872 ret, err := oss_controller.OssFile(modules).GetObjectToFileWithURL(ctx, &reqInfo) 873 874 return ret == true, err 875 }