序文
OSS には多くのプロジェクトが保存されています (プロジェクトは TMagic ローコード プラットフォームによって編集および生成され、自動的に OSS にアップロードされます)。現在、プロジェクトは管理バックグラウンドでパッケージ化して ZIP にダウンロードする必要があり、ファイルはローカルに生成されません。
OSS プロジェクト ファイルをダウンロードするには:
1. アイデアの実現
- OSSClientインスタンスを作成する
- バケットインスタンスの取得
- 必要なすべてのファイル情報を取得し、各ファイル ストリームをループし、ZIP アーカイブを作成して書き込みます。
- ZIPファイルをダウンロード
2. コードの実装 (Go)
1. OSSClientの作成
コードは以下のように表示されます。
const (
EndPoint = "OSS账号EndPoint"
AccessKeyId = "OSS账号AccessKeyId"
AccessKeySecret = "OSS账号AccessKeySecret"
BucketName = "OSS账号BucketName"
Prefix = "cuisines/" // 文件前缀
)
// 创建OSSClient实例。
// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
client, err := oss.New(EndPoint, AccessKeyId, AccessKeySecret)
if err != nil {
log.Fatalf("creates the new client instance failed, err: %v", err.Error())
}
// 获取Bucket实例
bucket, err := client.Bucket(BucketName)
if err != nil {
log.Fatalf("gets the bucket instance failed, err: %v", err.Error())
}
2. OSSのBucketインスタンスを取得する
コードは以下のように表示されます。
// 获取Bucket实例
bucket, err := client.Bucket(BucketName)
if err != nil {
log.Fatalf("gets the bucket instance failed, err: %v", err.Error())
}
// 列举所有文件。
// oss.Prefix(Prefix) 通过Prefix参数设置列举的文件前缀为"cuisines/"
// oss.MaxKeys(1000) 限制数量1000,默认100,最大值1000
lsRes, err := bucket.ListObjectsV2(oss.Prefix(Prefix), oss.MaxKeys(1000))
if err != nil {
log.Fatalf("list the objects under the current bucket failed, err: %v", err.Error())
}
3. ファイル情報を取得し、スキャンして ZIP ファイルに書き込みます
コードは以下のように表示されます。
// 创建文件(cuisines.zip)
f, err := os.Create(fmt.Sprintf("%s.zip", strings.TrimSuffix(Prefix, "/")))
if err != nil {
log.Fatalf("create file failed, err: %v", err.Error())
}
// 关闭文件,释放资源。
defer f.Close()
// 创建一个向 zip 文件中写入的 writer
zipWriter := zip.NewWriter(f)
// 关闭压缩文件
defer zipWriter.Close()
// 打印列举结果。默认情况下,一次返回100条记录。
for _, object := range lsRes.Objects {
// log.Printf("%+v", object.Key)
// cuisines/
// cuisines/css/animate.css
// cuisines/js/easing.js
// cuisines/images/asia.jpg
// cuisines/index.html
// 将其分成目录和文件名部分。 path = dir + file,例:cuisines/css/animate.css,dir:cuisines/css/,file:animate.css
dir, file := filepath.Split(object.Key)
dir = strings.TrimPrefix(dir, Prefix)
// 路径+文件为空,则跳过,否则创建无名文件
if dir+file == "" {
continue
}
// 下载文件到流
body, err := bucket.GetObject(object.Key)
if err != nil {
log.Fatalf("downloads the object failed, err: %v", err.Error())
}
// 创建一个文件到ZIP中
fileWriter, err := zipWriter.Create(dir + file)
if err != nil {
log.Fatalf("adds a file to the zip file failed, err: %v", err.Error())
}
// 写入文件内容
if _, err := io.Copy(fileWriter, body); err != nil {
log.Fatalf("file copy failed, err: %+v", err.Error())
}
}
このとき、「main,go」配下に「cuisines.zip」ファイルが生成されます。しかし実際には、ダウンロードしてパッケージ化するときに、特定のファイル内の情報を変更する必要があるという要件がある場合があります。
この実装は実際には非常に簡単で、ダウンロードしたファイルを読み取り、一致させて置換するだけです。
コードは以下のように表示されます。
// 下载文件到流
body, err := bucket.GetObject(object.Key)
if err != nil {
log.Fatalf("downloads the object failed, err: %v", err.Error())
}
// 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
defer body.Close()
// 创建一个文件到ZIP中
fileWriter, err := zipWriter.Create(dir + file)
if err != nil {
log.Fatalf("adds a file to the zip file failed, err: %v", err.Error())
}
// 读取文件数据
data, err := ioutil.ReadAll(body)
if err != nil {
log.Fatalf("read file failed, err: %+v", err.Error())
}
// 替换 cuisines/index.html 文件中指定内容
if dir+file == "index.html" {
var newData = strings.ReplaceAll(string(data), "<title>Home</title>", "<title>主页</title>")
// 写入文件内容
if _, err := io.Copy(fileWriter, strings.NewReader(newData)); err != nil {
log.Fatalf("file copy failed, err: %+v", err.Error())
}
} else {
// 写入文件内容
if _, err := io.Copy(fileWriter, body); err != nil {
log.Fatalf("file copy failed, err: %+v", err.Error())
}
}
前後の比較:
4.ZIPファイルのダウンロード
ファイルがローカルに生成されている場合は、プロジェクト ルートからアクセスできるようにするだけでダウンロードできます。ただし、ZIP ファイルをローカルに生成したくなく、ダウンロード時に直接ダウンロードする必要があります。 ?
次に、コードを調整し、Gin フレームワークを使用してサービスを開始します。
コードは以下のように表示されます。
package main
import (
"archive/zip"
"bytes"
"context"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gin-gonic/gin"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
)
const (
EndPoint = "OSS账号EndPoint"
AccessKeyId = "OSS账号AccessKeyId"
AccessKeySecret = "OSS账号AccessKeySecret"
BucketName = "OSS账号BucketName"
Prefix = "cuisines/" // 文件前缀
)
// download
func main() {
router := gin.Default()
router.GET("/download", zipDownload)
err := http.ListenAndServe(":8888", router)
if err != nil {
log.Printf("%+v", err.Error())
}
}
// 生成ZIP文件,直接下载
func zipDownload(c *gin.Context) {
// 创建OSSClient实例。
// yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
client, err := oss.New(EndPoint, AccessKeyId, AccessKeySecret)
if err != nil {
log.Fatalf("creates the new client instance failed, err: %v", err.Error())
}
// 获取Bucket实例
bucket, err := client.Bucket(BucketName)
if err != nil {
log.Fatalf("gets the bucket instance failed, err: %v", err.Error())
}
// 列举所有文件。
// oss.Prefix(Prefix) 通过Prefix参数设置列举的文件前缀为"cuisines/"
// oss.MaxKeys(1000) 限制数量1000,默认100,最大值1000
lsRes, err := bucket.ListObjectsV2(oss.Prefix(Prefix), oss.MaxKeys(1000))
if err != nil {
log.Fatalf("list the objects under the current bucket failed, err: %v", err.Error())
}
// 创建一个缓冲区来写入我们的存档。
buf := new(bytes.Buffer)
// 创建一个向 zip 文件中写入的 writer
zipWriter := zip.NewWriter(buf)
// 打印列举结果。默认情况下,一次返回100条记录。
for _, object := range lsRes.Objects {
// log.Printf("%+v", object.Key)
// cuisines/
// cuisines/css/animate.css
// cuisines/js/easing.js
// cuisines/images/asia.jpg
// cuisines/index.html
// 将其分成目录和文件名部分。 path = dir + file,例:cuisines/css/animate.css,dir:cuisines/css/,file:animate.css
dir, file := filepath.Split(object.Key)
dir = strings.TrimPrefix(dir, Prefix)
// 路径+文件为空,则跳过,否则创建无名文件
if dir+file == "" {
continue
}
// 下载文件到流
body, err := bucket.GetObject(object.Key)
if err != nil {
log.Fatalf("downloads the object failed, err: %v", err.Error())
}
// 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
defer body.Close()
// 创建一个文件到ZIP中
fileWriter, err := zipWriter.Create(dir + file)
if err != nil {
log.Fatalf("adds a file to the zip file failed, err: %v", err.Error())
}
// 读取文件数据
data, err := ioutil.ReadAll(body)
if err != nil {
log.Fatalf("read file failed, err: %+v", err.Error())
}
// 替换 cuisines/index.html 文件中指定内容
if dir+file == "index.html" {
var newData = strings.ReplaceAll(string(data), "<title>Home</title>", "<title>主页</title>")
// 写入文件内容
if _, err := io.Copy(fileWriter, strings.NewReader(newData)); err != nil {
log.Fatalf("file copy failed, err: %+v", err.Error())
}
} else {
// 写入文件内容
if _, err := io.Copy(fileWriter, body); err != nil {
log.Fatalf("file copy failed, err: %+v", err.Error())
}
}
}
// 关闭压缩文件
if err = zipWriter.Close(); err != nil {
log.Fatalf("zip writer close failed, err: %+v", err.Error())
}
c.Writer.Header().Set("Content-Type", "application/octet-stream")
disposition := fmt.Sprintf("attachment; filename=\"%s.zip\"", strings.TrimSuffix(Prefix, "/"))
c.Writer.Header().Set("Content-Disposition", disposition)
c.Writer.Write(buf.Bytes())
}
ブラウザに「 http://127.0.0.1:8888/download 」と入力して、パッケージ化されたファイルをダウンロードします。
要約する
ここのプロジェクトはわずか数 MB です。ダウンロードしたファイルが大きすぎる場合は、直接ダウンロードすることはお勧めできません。それでもローカルにダウンロードすることをお勧めします。