You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by ku...@apache.org on 2024/07/08 03:23:24 UTC
(incubator-answer-plugins) 01/01: feat(cdn):Feat #128, Closes #128. Support CDN with Aliyun OSS
This is an automated email from the ASF dual-hosted git repository.
kumfo pushed a commit to branch feat/cdn/oss
in repository https://gitbox.apache.org/repos/asf/incubator-answer-plugins.git
commit fdbd186f2355995ce622b1eb14c973552aff3688
Author: kumfo <ku...@sifou.com>
AuthorDate: Mon Jul 8 11:23:13 2024 +0800
feat(cdn):Feat #128, Closes #128. Support CDN with Aliyun OSS
---
cdn-aliyunoss/README.md | 18 ++
cdn-aliyunoss/aliyunoss.go | 383 ++++++++++++++++++++++++++++++++++++++
cdn-aliyunoss/go.mod | 47 +++++
cdn-aliyunoss/i18n/en_US.yaml | 72 +++++++
cdn-aliyunoss/i18n/translation.go | 45 +++++
cdn-aliyunoss/i18n/zh_CN.yaml | 72 +++++++
6 files changed, 637 insertions(+)
diff --git a/cdn-aliyunoss/README.md b/cdn-aliyunoss/README.md
new file mode 100644
index 0000000..ceabbfe
--- /dev/null
+++ b/cdn-aliyunoss/README.md
@@ -0,0 +1,18 @@
+# CDN With Aliyun OSS Storage (preview)
+> This plugin can be used to store static files to Aliyun OSS.
+
+## How to use
+
+### Build
+```bash
+./answer build --with github.com/apache/incubator-answer-plugins/cdn-aliyunoss
+```
+
+### Configuration
+- `Endpoint` - Endpoint of AliCloud OSS storage, such as oss-cn-hangzhou.aliyuncs.com
+- `Bucket Name` - Your bucket name
+- `Object Key Prefix` - Prefix of the object key like 'static/' that ending with '/'
+- `Access Key Id` - AccessKeyID of the AliCloud OSS storage
+- `Access Key Secret` - AccessKeySecret of the AliCloud OSS storage
+- `Visit Url Prefix` - Prefix of access address for the CDN file, ending with '/' such as https://static.example.com/xxx/
+- `Max File Size` - Max file size in MB, default is 10MB
\ No newline at end of file
diff --git a/cdn-aliyunoss/aliyunoss.go b/cdn-aliyunoss/aliyunoss.go
new file mode 100644
index 0000000..9c741b8
--- /dev/null
+++ b/cdn-aliyunoss/aliyunoss.go
@@ -0,0 +1,383 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package aliyunoss
+
+import (
+ "encoding/json"
+ "github.com/aliyun/aliyun-oss-go-sdk/oss"
+ "github.com/apache/incubator-answer-plugins/cdn-aliyunoss/i18n"
+ "github.com/apache/incubator-answer/plugin"
+ "github.com/apache/incubator-answer/ui"
+ "github.com/segmentfault/pacman/log"
+ "io"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+)
+
+var staticPath = os.Getenv("ANSWER_STATIC_PATH")
+
+const (
+ // 10MB
+ defaultMaxFileSize int64 = 10 * 1024 * 1024
+)
+
+type CDN struct {
+ Config *CDNConfig
+}
+
+type CDNConfig struct {
+ Endpoint string `json:"endpoint"`
+ BucketName string `json:"bucket_name"`
+ ObjectKeyPrefix string `json:"object_key_prefix"`
+ AccessKeyID string `json:"access_key_id"`
+ AccessKeySecret string `json:"access_key_secret"`
+ VisitUrlPrefix string `json:"visit_url_prefix"`
+ MaxFileSize string `json:"max_file_size"`
+}
+
+type CustomFile struct {
+ file io.Reader
+ cdnPrefix string
+ old string
+}
+
+func (f *CustomFile) Read(p []byte) (n int, err error) {
+ c := make([]byte, len(p))
+ n, err = f.file.Read(c)
+ if f.old != "" {
+ c = []byte(strings.ReplaceAll(string(c), f.old, "\""+f.cdnPrefix+"/static"))
+ }
+
+ n = copy(p, c)
+ return
+}
+
+func (f *CustomFile) Close() error { return nil }
+
+func init() {
+ plugin.Register(&CDN{
+ Config: &CDNConfig{},
+ })
+}
+
+func (c *CDN) Info() plugin.Info {
+ return plugin.Info{
+ Name: plugin.MakeTranslator(i18n.InfoName),
+ SlugName: "aliyunoss_cdn",
+ Description: plugin.MakeTranslator(i18n.InfoDescription),
+ Author: "answerdev",
+ Version: "1.0.0",
+ Link: "https://github.com/apache/incubator-answer-plugins/tree/main/cdn-aliyunoss",
+ }
+}
+
+// GetStaticPrefix get static prefix
+func (c *CDN) GetStaticPrefix() string {
+ return c.Config.VisitUrlPrefix + c.Config.ObjectKeyPrefix
+}
+
+// scanFiles scan all the static files in the build directory
+func (c *CDN) scanFiles() {
+ if staticPath == "" {
+ c.scanEmbedFiles("build")
+ log.Info("complete scan embed files")
+ return
+ }
+ c.scanStaticPathFiles(staticPath)
+ log.Info("complete scan static path files")
+}
+
+// scanStaticPathFiles scan static path files
+func (c *CDN) scanStaticPathFiles(fileName string) {
+ // scan static path files
+ entry, err := os.ReadDir(fileName)
+ if err != nil {
+ log.Error("read static dir failed: %v", err)
+ return
+ }
+ for _, info := range entry {
+ if info.IsDir() {
+ c.scanStaticPathFiles(filepath.Join(fileName, info.Name()))
+ continue
+ }
+
+ filePath := filepath.Join(fileName, info.Name())
+ fi, _ := info.Info()
+ size := fi.Size()
+ file, err := os.Open(filePath)
+ if err != nil {
+ log.Error("open file failed: %v", err)
+ continue
+ }
+
+ suffix := staticPath[:1]
+ if suffix != "/" {
+ suffix = ""
+ }
+ filePath = strings.TrimPrefix(filePath, staticPath+suffix)
+
+ // rebuild custom io.Reader
+ ns := strings.Split(info.Name(), ".")
+ if info.Name() == "asset-manifest.json" {
+ c.Upload(filePath, c.rebuildReader(file, map[string]string{
+ "\"/static": "",
+ }), size)
+ continue
+ }
+ if ns[0] == "main" {
+ ext := strings.ToLower(filepath.Ext(filePath))
+ if ext == ".js" || ext == ".map" {
+ c.Upload(filePath, c.rebuildReader(file, map[string]string{
+ "\"static": "",
+ "=\"/\",": "=\"\",",
+ }), size)
+ }
+ continue
+ }
+
+ c.Upload(filePath, file, size)
+ }
+}
+
+func (c *CDN) scanEmbedFiles(fileName string) {
+ entry, err := ui.Build.ReadDir(fileName)
+ if err != nil {
+ log.Error("read static dir failed: %v", err)
+ return
+ }
+ for _, info := range entry {
+ if info.IsDir() {
+ c.scanEmbedFiles(filepath.Join(fileName, info.Name()))
+ continue
+ }
+
+ filePath := filepath.Join(fileName, info.Name())
+ fi, _ := info.Info()
+ size := fi.Size()
+ file, err := ui.Build.Open(filePath)
+ defer file.Close()
+ if err != nil {
+ log.Error("open file failed: %v", err)
+ continue
+ }
+
+ filePath = strings.TrimPrefix(filePath, "build/")
+
+ // rebuild custom io.Reader
+ ns := strings.Split(info.Name(), ".")
+ if info.Name() == "asset-manifest.json" {
+ c.Upload(filePath, c.rebuildReader(file, map[string]string{
+ "\"/static": "",
+ }), size)
+ continue
+ }
+ if ns[0] == "main" {
+ ext := strings.ToLower(filepath.Ext(filePath))
+ if ext == ".js" || ext == ".map" {
+ c.Upload(filePath, c.rebuildReader(file, map[string]string{
+ "\"static": "",
+ "=\"/\",": "=\"\",",
+ }), size)
+ }
+ continue
+ }
+
+ c.Upload(filePath, file, size)
+ }
+}
+
+func (c *CDN) rebuildReader(file io.Reader, replaceMap map[string]string) io.Reader {
+ var (
+ bufr = make([]byte, 0)
+ res string
+ )
+
+ for {
+ buf := make([]byte, 1024)
+ n, err := file.Read(buf)
+ if err != nil {
+ break
+ }
+ bufr = append(bufr, buf[:n]...)
+ }
+
+ res = string(bufr)
+
+ for oldStr, newStr := range replaceMap {
+ if oldStr != "" {
+ if newStr == "" {
+ newStr = "\"" + c.GetStaticPrefix() + "/static"
+ }
+ res = strings.ReplaceAll(res, oldStr, newStr)
+ }
+ }
+ return strings.NewReader(res)
+}
+
+func (c *CDN) Upload(filePath string, file io.Reader, size int64) {
+ client, err := oss.New(c.Config.Endpoint, c.Config.AccessKeyID, c.Config.AccessKeySecret)
+ if err != nil {
+ log.Error(plugin.MakeTranslator(i18n.ErrMisStorageConfig), err)
+ return
+ }
+ bucket, err := client.Bucket(c.Config.BucketName)
+ if err != nil {
+ log.Error(plugin.MakeTranslator(i18n.ErrMisStorageConfig), err)
+ return
+ }
+
+ if !c.CheckFileType(filePath) {
+ log.Error(plugin.MakeTranslator(i18n.ErrUnsupportedFileType), filePath)
+ return
+ }
+
+ if size > c.maxFileSizeLimit() {
+ log.Error(plugin.MakeTranslator(i18n.ErrOverFileSizeLimit))
+ return
+ }
+
+ objectKey := c.createObjectKey(filePath)
+ request := &oss.PutObjectRequest{
+ ObjectKey: objectKey,
+ Reader: file,
+ }
+ respBody, err := bucket.DoPutObject(request, nil)
+ if err != nil {
+ log.Error(plugin.MakeTranslator(i18n.ErrUploadFileFailed), err)
+ return
+ }
+ defer respBody.Close()
+}
+
+func (c *CDN) createObjectKey(filePath string) string {
+ return c.Config.ObjectKeyPrefix + filePath
+}
+
+func (c *CDN) CheckFileType(filePath string) bool {
+ ext := strings.ToLower(filepath.Ext(filePath))
+ if _, ok := plugin.DefaultCDNFileType[ext]; ok {
+ return true
+ }
+ return false
+}
+
+func (c *CDN) maxFileSizeLimit() int64 {
+ if len(c.Config.MaxFileSize) == 0 {
+ return defaultMaxFileSize
+ }
+ limit, _ := strconv.Atoi(c.Config.MaxFileSize)
+ if limit <= 0 {
+ return defaultMaxFileSize
+ }
+ return int64(limit) * 1024 * 1024
+}
+
+func (c *CDN) ConfigFields() []plugin.ConfigField {
+ return []plugin.ConfigField{
+ {
+ Name: "endpoint",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigEndpointTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigEndpointDescription),
+ Required: true,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeText,
+ },
+ Value: c.Config.Endpoint,
+ },
+ {
+ Name: "bucket_name",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigBucketNameTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigBucketNameDescription),
+ Required: true,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeText,
+ },
+ Value: c.Config.BucketName,
+ },
+ {
+ Name: "object_key_prefix",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigObjectKeyPrefixTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigObjectKeyPrefixDescription),
+ Required: false,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeText,
+ },
+ Value: c.Config.ObjectKeyPrefix,
+ },
+ {
+ Name: "access_key_id",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigAccessKeyIdTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigAccessKeyIdDescription),
+ Required: true,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeText,
+ },
+ Value: c.Config.AccessKeyID,
+ },
+ {
+ Name: "access_key_secret",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigAccessKeySecretTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigAccessKeySecretDescription),
+ Required: true,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeText,
+ },
+ Value: c.Config.AccessKeySecret,
+ },
+ {
+ Name: "visit_url_prefix",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigVisitUrlPrefixTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigVisitUrlPrefixDescription),
+ Required: true,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeText,
+ },
+ Value: c.Config.VisitUrlPrefix,
+ },
+ {
+ Name: "max_file_size",
+ Type: plugin.ConfigTypeInput,
+ Title: plugin.MakeTranslator(i18n.ConfigMaxFileSizeTitle),
+ Description: plugin.MakeTranslator(i18n.ConfigMaxFileSizeDescription),
+ Required: false,
+ UIOptions: plugin.ConfigFieldUIOptions{
+ InputType: plugin.InputTypeNumber,
+ },
+ Value: c.Config.MaxFileSize,
+ },
+ }
+}
+
+func (c *CDN) ConfigReceiver(config []byte) error {
+ cfg := &CDNConfig{}
+ _ = json.Unmarshal(config, cfg)
+ c.Config = cfg
+
+ go c.scanFiles()
+ return nil
+}
diff --git a/cdn-aliyunoss/go.mod b/cdn-aliyunoss/go.mod
new file mode 100644
index 0000000..140928c
--- /dev/null
+++ b/cdn-aliyunoss/go.mod
@@ -0,0 +1,47 @@
+module github.com/apache/incubator-answer-plugins/cdn-aliyunoss
+
+go 1.19
+
+require (
+ github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
+ github.com/apache/incubator-answer v1.3.6
+)
+
+require (
+ github.com/LinkinStars/go-i18n/v2 v2.2.2 // indirect
+ github.com/aymerick/douceur v0.2.0 // indirect
+ github.com/bytedance/sonic v1.9.1 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-gonic/gin v1.9.1 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/google/wire v0.5.0 // indirect
+ github.com/gorilla/css v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/microcosm-cc/bluemonday v1.0.21 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f // indirect
+ github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ golang.org/x/arch v0.3.0 // indirect
+ golang.org/x/crypto v0.21.0 // indirect
+ golang.org/x/net v0.21.0 // indirect
+ golang.org/x/sys v0.18.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
+)
diff --git a/cdn-aliyunoss/i18n/en_US.yaml b/cdn-aliyunoss/i18n/en_US.yaml
new file mode 100644
index 0000000..2aa7e27
--- /dev/null
+++ b/cdn-aliyunoss/i18n/en_US.yaml
@@ -0,0 +1,72 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+plugin:
+ aliyunoss_storage:
+ backend:
+ info:
+ name:
+ other: Aliyun CDN OSS storage
+ description:
+ other: Upload files to AliCloud CDN OSS storage
+ config:
+ endpoint:
+ title:
+ other: Endpoint
+ description:
+ other: Endpoint of AliCloud OSS storage
+ bucket_name:
+ title:
+ other: Bucket name
+ description:
+ other: Bucket name of AliCloud OSS storage
+ object_key_prefix:
+ title:
+ other: Object Key prefix
+ description:
+ other: prefix of the object key like 'answer/data/' that ending with '/'
+ access_key_id:
+ title:
+ other: AccessKeyID
+ description:
+ other: AccessKeyID of the AliCloud OSS storage
+ access_key_secret:
+ title:
+ other: AccessKeySecret
+ description:
+ other: AccessKeySecret of AliCloud OSS storage
+ visit_url_prefix:
+ title:
+ other: Access URL prefix
+ description:
+ other: prefix of the final access address of the CDN, ending with '/' https://example.com/xxx/
+ max_file_size:
+ title:
+ other: Maximum file size(MB)
+ description:
+ other: Limit the maximum size of uploaded files, in MB, default is 10MB
+ err:
+ mis_storage_config:
+ other: Wrong storage configuration causes upload failure.
+ file_not_found:
+ other: File not found.
+ unsupported_file_type:
+ other: Unsupported file type.
+ over_file_size_limit:
+ other: File size limit exceeded.
+ upload_file_failed:
+ other: Failed to upload a file.
\ No newline at end of file
diff --git a/cdn-aliyunoss/i18n/translation.go b/cdn-aliyunoss/i18n/translation.go
new file mode 100644
index 0000000..773fe17
--- /dev/null
+++ b/cdn-aliyunoss/i18n/translation.go
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package i18n
+
+const (
+ InfoName = "plugin.aliyunoss_cdn.backend.info.name"
+ InfoDescription = "plugin.aliyunoss_cdn.backend.info.description"
+
+ ConfigEndpointTitle = "plugin.aliyunoss_cdn.backend.config.endpoint.title"
+ ConfigEndpointDescription = "plugin.aliyunoss_cdn.backend.config.endpoint.description"
+ ConfigBucketNameTitle = "plugin.aliyunoss_cdn.backend.config.bucket_name.title"
+ ConfigBucketNameDescription = "plugin.aliyunoss_cdn.backend.config.bucket_name.description"
+ ConfigObjectKeyPrefixTitle = "plugin.aliyunoss_cdn.backend.config.object_key_prefix.title"
+ ConfigObjectKeyPrefixDescription = "plugin.aliyunoss_cdn.backend.config.object_key_prefix.description"
+ ConfigAccessKeyIdTitle = "plugin.aliyunoss_cdn.backend.config.access_key_id.title"
+ ConfigAccessKeyIdDescription = "plugin.aliyunoss_cdn.backend.config.access_key_id.description"
+ ConfigAccessKeySecretTitle = "plugin.aliyunoss_cdn.backend.config.access_key_secret.title"
+ ConfigAccessKeySecretDescription = "plugin.aliyunoss_cdn.backend.config.access_key_secret.description"
+ ConfigVisitUrlPrefixTitle = "plugin.aliyunoss_cdn.backend.config.visit_url_prefix.title"
+ ConfigVisitUrlPrefixDescription = "plugin.aliyunoss_cdn.backend.config.visit_url_prefix.description"
+ ConfigMaxFileSizeTitle = "plugin.aliyunoss_cdn.backend.config.max_file_size.title"
+ ConfigMaxFileSizeDescription = "plugin.aliyunoss_cdn.backend.config.max_file_size.description"
+
+ ErrMisStorageConfig = "plugin.aliyunoss_cdn.backend.err.mis_storage_config"
+ ErrUnsupportedFileType = "plugin.aliyunoss_cdn.backend.err.unsupported_file_type"
+ ErrOverFileSizeLimit = "plugin.aliyunoss_cdn.backend.err.over_file_size_limit"
+ ErrUploadFileFailed = "plugin.aliyunoss_cdn.backend.err.upload_file_failed"
+)
diff --git a/cdn-aliyunoss/i18n/zh_CN.yaml b/cdn-aliyunoss/i18n/zh_CN.yaml
new file mode 100644
index 0000000..7044a64
--- /dev/null
+++ b/cdn-aliyunoss/i18n/zh_CN.yaml
@@ -0,0 +1,72 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+plugin:
+ aliyunoss_storage:
+ backend:
+ info:
+ name:
+ other: 阿里云CDN OSS
+ description:
+ other: 上传文件到阿里云CDN OSS
+ config:
+ endpoint:
+ title:
+ other: Endpoint
+ description:
+ other: 阿里云OSS存储的Endpoint
+ bucket_name:
+ title:
+ other: Bucket名称
+ description:
+ other: 阿里云OSS存储的Bucket名称
+ object_key_prefix:
+ title:
+ other: 对象Key前缀
+ description:
+ other: 对象键的前缀,如'answer/data/',以'/'结尾
+ access_key_id:
+ title:
+ other: AccessKeyID
+ description:
+ other: 阿里云OSS存储的AccessKeyID
+ access_key_secret:
+ title:
+ other: AccessKeySecret
+ description:
+ other: 阿里云OSS存储的AccessKeySecret
+ visit_url_prefix:
+ title:
+ other: 访问URL前缀
+ description:
+ other: CDN最终访问地址的前缀,以 '/' 结尾 https://example.com/xxx/
+ max_file_size:
+ title:
+ other: 最大文件大小(MB)
+ description:
+ other: 限制上传文件的最大大小,单位为MB,默认为 10MB
+ err:
+ mis_storage_config:
+ other: 错误的存储配置导致上传失败
+ file_not_found:
+ other: 文件未找到
+ unsupported_file_type:
+ other: 不支持的文件类型
+ over_file_size_limit:
+ other: 超过文件大小限制
+ upload_file_failed:
+ other: 上传文件失败
\ No newline at end of file