This is an automated email from the ASF dual-hosted git repository.

lufei pushed a commit to branch cos
in repository https://gitbox.apache.org/repos/asf/incubator-answer-plugins.git

commit 60e371a267414bf5f97b9534ec9245204c781b2d
Author: sy-records <5...@qq52o.cn>
AuthorDate: Wed Sep 25 11:09:53 2024 +0800

    feat: Support Tencent COS Storage
---
 storage-tencentyuncos/README.md           |  21 +++
 storage-tencentyuncos/go.mod              |  51 ++++++
 storage-tencentyuncos/go.sum              | 166 +++++++++++++++++++
 storage-tencentyuncos/i18n/en_US.yaml     |  72 ++++++++
 storage-tencentyuncos/i18n/translation.go |  46 +++++
 storage-tencentyuncos/i18n/zh_CN.yaml     |  72 ++++++++
 storage-tencentyuncos/info.yaml           |  22 +++
 storage-tencentyuncos/tencentyuncos.go    | 267 ++++++++++++++++++++++++++++++
 8 files changed, 717 insertions(+)

diff --git a/storage-tencentyuncos/README.md b/storage-tencentyuncos/README.md
new file mode 100644
index 0000000..013fc10
--- /dev/null
+++ b/storage-tencentyuncos/README.md
@@ -0,0 +1,21 @@
+# Tencent COS Storage (preview)
+
+> This plugin can be used to store attachments and avatars to Tencent COS.
+
+## How to use
+
+### Build
+
+```bash
+./answer build --with 
github.com/apache/incubator-answer-plugins/storage-tencentyuncos
+```
+
+### Configuration
+
+- `Region` - Region of Tencent COS storage, like 'ap-beijing'
+- `Bucket Name` - Your bucket name
+- `Object Key Prefix` - Prefix of the object key like 'answer/data/' that 
ending with '/'
+- `Secret ID` - Secret ID of the Tencent COS storage
+- `Secret Key` - Secret Key of the Tencent COS storage
+- `Visit Url Prefix` - Prefix of access address for the uploaded file, ending 
with '/' such as https://example.com/xxx/
+- `Max File Size` - Max file size in MB, default is 10MB
diff --git a/storage-tencentyuncos/go.mod b/storage-tencentyuncos/go.mod
new file mode 100644
index 0000000..bebd74e
--- /dev/null
+++ b/storage-tencentyuncos/go.mod
@@ -0,0 +1,51 @@
+module tencentyuncos
+
+go 1.21.3
+
+require (
+       github.com/apache/incubator-answer v1.4.0
+       github.com/apache/incubator-answer-plugins/util v1.0.2
+       github.com/tencentyun/cos-go-sdk-v5 v0.7.55
+)
+
+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/clbanning/mxj v1.8.4 // 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/go-querystring v1.0.0 // 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/mitchellh/mapstructure v1.5.0 // indirect
+       github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 
indirect
+       github.com/modern-go/reflect2 v1.0.2 // indirect
+       github.com/mozillazg/go-httpheader v0.2.1 // 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
+       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/storage-tencentyuncos/go.sum b/storage-tencentyuncos/go.sum
new file mode 100644
index 0000000..2fa1db7
--- /dev/null
+++ b/storage-tencentyuncos/go.sum
@@ -0,0 +1,166 @@
+github.com/BurntSushi/toml v1.0.0 
h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
+github.com/BurntSushi/toml v1.0.0/go.mod 
h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/LinkinStars/go-i18n/v2 v2.2.2 
h1:ZfjpzbW13dv6btv3RALKZkpN9A+7K1JA//2QcNeWaxU=
+github.com/LinkinStars/go-i18n/v2 v2.2.2/go.mod 
h1:hLglSJ4/3M0Y7ZVcoEJI+OwqkglHCA32DdjuJJR2LbM=
+github.com/QcloudApi/qcloud_sign_golang 
v0.0.0-20141224014652-e4130a326409/go.mod 
h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
+github.com/apache/incubator-answer v1.4.0 
h1:W3y4TAQ4sdzgcqntGqNBPe0BdyeW7+l8FWYBDs9g8+Y=
+github.com/apache/incubator-answer v1.4.0/go.mod 
h1:Q4NkQmBd0sV7t3Cd8NBsWh9w8jFRo/2qjzOw9MlRNwk=
+github.com/apache/incubator-answer-plugins/util v1.0.2 
h1:PontocVaiEm+oTj+4aDonwWDZnxywUeHsaTwlQgclfA=
+github.com/apache/incubator-answer-plugins/util v1.0.2/go.mod 
h1:KPMSiM4ec4uEl2njaGINYuSl6zVmHdvPB2nHUxVcQDo=
+github.com/aymerick/douceur v0.2.0 
h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod 
h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
+github.com/bytedance/sonic v1.5.0/go.mod 
h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 
h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod 
h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod 
h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 
h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod 
h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
+github.com/clbanning/mxj v1.8.4/go.mod 
h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
+github.com/creack/pty v1.1.9/go.mod 
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.2 
h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod 
h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gin-contrib/sse v0.1.0 
h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod 
h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod 
h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-playground/assert/v2 v2.2.0 
h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod 
h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 
h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod 
h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 
h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod 
h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0 
h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod 
h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/goccy/go-json v0.10.2 
h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod 
h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod 
h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang/protobuf v1.5.0/go.mod 
h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.2.0/go.mod 
h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-querystring v1.0.0 
h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod 
h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/gofuzz v1.0.0/go.mod 
h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/subcommands v1.0.1/go.mod 
h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
+github.com/google/uuid v1.1.1/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
+github.com/google/wire v0.5.0/go.mod 
h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod 
h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
+github.com/json-iterator/go v1.1.12 
h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod 
h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod 
h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 
h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod 
h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod 
h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.4 
h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod 
h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/mattn/go-isatty v0.0.19 
h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod 
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/microcosm-cc/bluemonday v1.0.21 
h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
+github.com/microcosm-cc/bluemonday v1.0.21/go.mod 
h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
+github.com/mitchellh/mapstructure v1.4.3/go.mod 
h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 
h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod 
h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod 
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd 
h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod 
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 
h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod 
h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mozillazg/go-httpheader v0.2.1 
h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
+github.com/mozillazg/go-httpheader v0.2.1/go.mod 
h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
+github.com/pelletier/go-toml/v2 v2.0.8 
h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod 
h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.8.0 
h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod 
h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f 
h1:9f2Bjf6bdMvNyUop32wAGJCdp+Jdm/d6nKBYvFvkRo0=
+github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f/go.mod 
h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
+github.com/segmentfault/pacman/contrib/i18n v0.0.0-20230516093754-b76aef1c1150 
h1:OEuW1D7RGDE0CZDr0oGMw9Eiq7fAbD9C4WMrvSixamk=
+github.com/segmentfault/pacman/contrib/i18n 
v0.0.0-20230516093754-b76aef1c1150/go.mod 
h1:7QcRmnV7OYq4hNOOCWXT5HXnN/u756JUsqIW0Bw8n9E=
+github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod 
h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod 
h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod 
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod 
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod 
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod 
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3/go.mod 
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 
h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod 
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common 
v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod 
h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
+github.com/tencentyun/cos-go-sdk-v5 v0.7.55 
h1:9DfH3umWUd0I2jdqcUxrU1kLfUPOydULNy4T9qN5PF8=
+github.com/tencentyun/cos-go-sdk-v5 v0.7.55/go.mod 
h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
+github.com/twitchyliquid64/golang-asm v0.15.1 
h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod 
h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 
h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod 
h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/yuin/goldmark v1.4.13/go.mod 
h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod 
h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod 
h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod 
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod 
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod 
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod 
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod 
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod 
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 
h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod 
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.30.0 
h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod 
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod 
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/storage-tencentyuncos/i18n/en_US.yaml 
b/storage-tencentyuncos/i18n/en_US.yaml
new file mode 100644
index 0000000..e3ae90d
--- /dev/null
+++ b/storage-tencentyuncos/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:
+  tencentyuncos_storage:
+    backend:
+      info:
+        name:
+          other: Tencent COS storage
+        description:
+          other: Upload files to Tencent COS storage
+      config:
+        region:
+          title:
+            other: Region
+          description:
+            other: Region of Tencent COS storage, like 'ap-beijing'
+        bucket_name:
+          title:
+            other: Bucket name
+          description:
+            other: Bucket name of Tencent COS storage
+        object_key_prefix:
+          title:
+            other: Object Key prefix
+          description:
+            other: prefix of the object key like 'answer/data/' that ending 
with '/'
+        secret_id:
+          title:
+            other: SecretID
+          description:
+            other: SecretID of the Tencent COS storage
+        secret_key:
+          title:
+            other: SecretKey
+          description:
+            other: SecretKey of the Tencent COS storage
+        visit_url_prefix:
+          title:
+            other: Access URL prefix
+          description:
+            other: prefix of the final access address of the uploaded file, 
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.
diff --git a/storage-tencentyuncos/i18n/translation.go 
b/storage-tencentyuncos/i18n/translation.go
new file mode 100644
index 0000000..2664a8c
--- /dev/null
+++ b/storage-tencentyuncos/i18n/translation.go
@@ -0,0 +1,46 @@
+/*
+ * 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.tencentyuncos_storage.backend.info.name"
+       InfoDescription = 
"plugin.tencentyuncos_storage.backend.info.description"
+
+       ConfigRegionTitle                = 
"plugin.tencentyuncos_storage.backend.config.region.title"
+       ConfigRegionDescription          = 
"plugin.tencentyuncos_storage.backend.config.region.description"
+       ConfigBucketNameTitle            = 
"plugin.tencentyuncos_storage.backend.config.bucket_name.title"
+       ConfigBucketNameDescription      = 
"plugin.tencentyuncos_storage.backend.config.bucket_name.description"
+       ConfigObjectKeyPrefixTitle       = 
"plugin.tencentyuncos_storage.backend.config.object_key_prefix.title"
+       ConfigObjectKeyPrefixDescription = 
"plugin.tencentyuncos_storage.backend.config.object_key_prefix.description"
+       ConfigSecretIdTitle              = 
"plugin.tencentyuncos_storage.backend.config.secret_id.title"
+       ConfigSecretIdDescription        = 
"plugin.tencentyuncos_storage.backend.config.secret_id.description"
+       ConfigSecretKeyTitle             = 
"plugin.tencentyuncos_storage.backend.config.secret_key.title"
+       ConfigSecretKeyDescription       = 
"plugin.tencentyuncos_storage.backend.config.secret_key.description"
+       ConfigVisitUrlPrefixTitle        = 
"plugin.tencentyuncos_storage.backend.config.visit_url_prefix.title"
+       ConfigVisitUrlPrefixDescription  = 
"plugin.tencentyuncos_storage.backend.config.visit_url_prefix.description"
+       ConfigMaxFileSizeTitle           = 
"plugin.tencentyuncos_storage.backend.config.max_file_size.title"
+       ConfigMaxFileSizeDescription     = 
"plugin.tencentyuncos_storage.backend.config.max_file_size.description"
+
+       ErrMisStorageConfig    = 
"plugin.tencentyuncos_storage.backend.err.mis_storage_config"
+       ErrFileNotFound        = 
"plugin.tencentyuncos_storage.backend.err.file_not_found"
+       ErrUnsupportedFileType = 
"plugin.tencentyuncos_storage.backend.err.unsupported_file_type"
+       ErrOverFileSizeLimit   = 
"plugin.tencentyuncos_storage.backend.err.over_file_size_limit"
+       ErrUploadFileFailed    = 
"plugin.tencentyuncos_storage.backend.err.upload_file_failed"
+)
diff --git a/storage-tencentyuncos/i18n/zh_CN.yaml 
b/storage-tencentyuncos/i18n/zh_CN.yaml
new file mode 100644
index 0000000..280aa19
--- /dev/null
+++ b/storage-tencentyuncos/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:
+  tencentyuncos_storage:
+    backend:
+      info:
+        name:
+          other: 腾讯云COS对象存储
+        description:
+          other: 上传文件到腾讯云COS对象存储
+      config:
+        region:
+          title:
+            other: 所属地域
+          description:
+            other: 存储桶的所属地域,如'ap-beijing'
+        bucket_name:
+          title:
+            other: 存储桶名称
+          description:
+            other: 存储桶的名称,如'example-1234567890'
+        object_key_prefix:
+          title:
+            other: 对象Key前缀
+          description:
+            other: 对象键的前缀,如'answer/data/',以'/'结尾
+        secret_id:
+          title:
+            other: SecretID
+          description:
+            other: 腾讯云COS对象存储的SecretID,在 
https://console.cloud.tencent.com/cam/capi 获取
+        secret_key:
+          title:
+            other: SecretKey
+          description:
+            other: 腾讯云COS对象存储的SecretKey
+        visit_url_prefix:
+          title:
+            other: 访问URL前缀
+          description:
+            other: 上传文件最终访问地址的前缀,以 '/' 结尾 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: 上传文件失败
diff --git a/storage-tencentyuncos/info.yaml b/storage-tencentyuncos/info.yaml
new file mode 100644
index 0000000..5ee9905
--- /dev/null
+++ b/storage-tencentyuncos/info.yaml
@@ -0,0 +1,22 @@
+# 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.
+
+slug_name: tencentyuncos_storage
+type: storage
+version: 1.0.0
+author: Luffy
+link: 
https://github.com/apache/incubator-answer-plugins/tree/main/storage-tencentyuncos
diff --git a/storage-tencentyuncos/tencentyuncos.go 
b/storage-tencentyuncos/tencentyuncos.go
new file mode 100644
index 0000000..f2273ee
--- /dev/null
+++ b/storage-tencentyuncos/tencentyuncos.go
@@ -0,0 +1,267 @@
+/*
+ * 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 tencentyuncos
+
+import (
+       "crypto/rand"
+       "embed"
+       "encoding/hex"
+       "encoding/json"
+       "fmt"
+       "github.com/apache/incubator-answer-plugins/util"
+       "net/http"
+       "net/url"
+       "path/filepath"
+       "strconv"
+       "strings"
+       "time"
+
+       "github.com/apache/incubator-answer-plugins/storage-tencentyuncos/i18n"
+       "github.com/apache/incubator-answer/plugin"
+       "github.com/tencentyun/cos-go-sdk-v5"
+)
+
+//go:embed  info.yaml
+var Info embed.FS
+
+const (
+       // 10MB
+       defaultMaxFileSize int64 = 10 * 1024 * 1024
+)
+
+type Storage struct {
+       Config *StorageConfig
+}
+
+type StorageConfig struct {
+       Region          string `json:"region"`
+       BucketName      string `json:"bucket_name"`
+       ObjectKeyPrefix string `json:"object_key_prefix"`
+       SecretID        string `json:"secret_id"`
+       SecretKey       string `json:"secret_key"`
+       VisitUrlPrefix  string `json:"visit_url_prefix"`
+       MaxFileSize     string `json:"max_file_size"`
+}
+
+func init() {
+       plugin.Register(&Storage{
+               Config: &StorageConfig{},
+       })
+}
+
+func (s *Storage) Info() plugin.Info {
+       info := &util.Info{}
+       info.GetInfo(Info)
+
+       return plugin.Info{
+               Name:        plugin.MakeTranslator(i18n.InfoName),
+               SlugName:    info.SlugName,
+               Description: plugin.MakeTranslator(i18n.InfoDescription),
+               Author:      info.Author,
+               Version:     info.Version,
+               Link:        info.Link,
+       }
+}
+
+func (s *Storage) UploadFile(ctx *plugin.GinContext, source 
plugin.UploadSource) (resp plugin.UploadFileResponse) {
+       resp = plugin.UploadFileResponse{}
+
+       BucketURL, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com";, 
s.Config.BucketName, s.Config.Region))
+       BaseURL := &cos.BaseURL{BucketURL: BucketURL}
+       client := cos.NewClient(BaseURL, &http.Client{
+               Transport: &cos.AuthorizationTransport{
+                       SecretID:  s.Config.SecretID,
+                       SecretKey: s.Config.SecretKey,
+               },
+       })
+
+       _, err := client.Bucket.IsExist(ctx)
+       if err != nil {
+               resp.OriginalError = fmt.Errorf("head bucket failed: %v", err)
+               resp.DisplayErrorMsg = 
plugin.MakeTranslator(i18n.ErrMisStorageConfig)
+               return resp
+       }
+
+       file, err := ctx.FormFile("file")
+       if err != nil {
+               resp.OriginalError = fmt.Errorf("get upload file failed: %v", 
err)
+               resp.DisplayErrorMsg = 
plugin.MakeTranslator(i18n.ErrFileNotFound)
+               return resp
+       }
+
+       if !s.CheckFileType(file.Filename, source) {
+               resp.OriginalError = fmt.Errorf("file type not allowed")
+               resp.DisplayErrorMsg = 
plugin.MakeTranslator(i18n.ErrUnsupportedFileType)
+               return resp
+       }
+
+       if file.Size > s.maxFileSizeLimit() {
+               resp.OriginalError = fmt.Errorf("file size too large")
+               resp.DisplayErrorMsg = 
plugin.MakeTranslator(i18n.ErrOverFileSizeLimit)
+               return resp
+       }
+
+       openFile, err := file.Open()
+       if err != nil {
+               resp.OriginalError = fmt.Errorf("get file failed: %v", err)
+               resp.DisplayErrorMsg = 
plugin.MakeTranslator(i18n.ErrFileNotFound)
+               return resp
+       }
+       defer openFile.Close()
+
+       objectKey := s.createObjectKey(file.Filename, source)
+       _, err = client.Object.Put(ctx, objectKey, openFile, nil)
+       if err != nil {
+               resp.OriginalError = fmt.Errorf("upload file failed: %v", err)
+               resp.DisplayErrorMsg = 
plugin.MakeTranslator(i18n.ErrUploadFileFailed)
+               return resp
+       }
+       resp.FullURL = s.Config.VisitUrlPrefix + objectKey
+       return resp
+}
+
+func (s *Storage) createObjectKey(originalFilename string, source 
plugin.UploadSource) string {
+       ext := strings.ToLower(filepath.Ext(originalFilename))
+       randomString := s.randomObjectKey()
+       switch source {
+       case plugin.UserAvatar:
+               return s.Config.ObjectKeyPrefix + "avatar/" + randomString + ext
+       case plugin.UserPost:
+               return s.Config.ObjectKeyPrefix + "post/" + randomString + ext
+       case plugin.AdminBranding:
+               return s.Config.ObjectKeyPrefix + "branding/" + randomString + 
ext
+       default:
+               return s.Config.ObjectKeyPrefix + "other/" + randomString + ext
+       }
+}
+
+func (s *Storage) randomObjectKey() string {
+       bytes := make([]byte, 4)
+       _, _ = rand.Read(bytes)
+       return fmt.Sprintf("%d", time.Now().UnixNano()) + 
hex.EncodeToString(bytes)
+}
+
+func (s *Storage) CheckFileType(originalFilename string, source 
plugin.UploadSource) bool {
+       ext := strings.ToLower(filepath.Ext(originalFilename))
+       if _, ok := plugin.DefaultFileTypeCheckMapping[source][ext]; ok {
+               return true
+       }
+       return false
+}
+
+func (s *Storage) maxFileSizeLimit() int64 {
+       if len(s.Config.MaxFileSize) == 0 {
+               return defaultMaxFileSize
+       }
+       limit, _ := strconv.Atoi(s.Config.MaxFileSize)
+       if limit <= 0 {
+               return defaultMaxFileSize
+       }
+       return int64(limit) * 1024 * 1024
+}
+
+func (s *Storage) ConfigFields() []plugin.ConfigField {
+       return []plugin.ConfigField{
+               {
+                       Name:        "region",
+                       Type:        plugin.ConfigTypeInput,
+                       Title:       
plugin.MakeTranslator(i18n.ConfigRegionTitle),
+                       Description: 
plugin.MakeTranslator(i18n.ConfigRegionDescription),
+                       Required:    true,
+                       UIOptions: plugin.ConfigFieldUIOptions{
+                               InputType: plugin.InputTypeText,
+                       },
+                       Value: s.Config.Region,
+               },
+               {
+                       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: s.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: s.Config.ObjectKeyPrefix,
+               },
+               {
+                       Name:        "secret_id",
+                       Type:        plugin.ConfigTypeInput,
+                       Title:       
plugin.MakeTranslator(i18n.ConfigSecretIdTitle),
+                       Description: 
plugin.MakeTranslator(i18n.ConfigSecretIdDescription),
+                       Required:    true,
+                       UIOptions: plugin.ConfigFieldUIOptions{
+                               InputType: plugin.InputTypeText,
+                       },
+                       Value: s.Config.SecretID,
+               },
+               {
+                       Name:        "secret_key",
+                       Type:        plugin.ConfigTypeInput,
+                       Title:       
plugin.MakeTranslator(i18n.ConfigSecretKeyTitle),
+                       Description: 
plugin.MakeTranslator(i18n.ConfigSecretKeyDescription),
+                       Required:    true,
+                       UIOptions: plugin.ConfigFieldUIOptions{
+                               InputType: plugin.InputTypeText,
+                       },
+                       Value: s.Config.SecretKey,
+               },
+               {
+                       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: s.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: s.Config.MaxFileSize,
+               },
+       }
+}
+
+func (s *Storage) ConfigReceiver(config []byte) error {
+       c := &StorageConfig{}
+       _ = json.Unmarshal(config, c)
+       s.Config = c
+       return nil
+}


Reply via email to