defmodule COS.Object do
@moduledoc """
对象(Object)是对象存储的基本单元,可理解为任何格式类型的数据,例如图片、文档和音视频文件等。
[腾讯云文档](https://cloud.tencent.com/document/product/436/13324)
"""
alias COS.{HTTPClient, Utils}
@doc ~S"""
简单上传对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/7749)
可以上传一个对象至指定存储桶中,该操作需要请求者对存储桶有 WRITE 权限。
最大支持上传不超过5GB的对象,5GB以上对象请使用分块上传(Todo)或高级接口(Todo)上传。
## 示例
iex> COS.Object.put("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", "content")
{:ok, %Tesla.Env{
body: "",
headers: [
{"server", "tencent-cos"},
{"data", "Tue, 29 Mar 2022 16:39:58 GMT"},
...
],
...
}}
# 设置传对象的内容类型
COS.Object.put(
"https://bucket-1250000000.cos.ap-beijing.myqcloud.com",
"example.json",
"{\"key\":\"value\"}",
headers: [{"content-type", "application-json"}]
)
# 设置 HTTP 响应的超时时间
COS.Object.put(
"https://bucket-1250000000.cos.ap-beijing.myqcloud.com",
"example.txt",
"content",
tesla_opts: [adapter: [recv_timeout: 30_000]]
)
# 创建“文件夹”
COS.Object.put("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example/", "")
"""
@spec put(
host :: binary(),
key :: binary(),
content :: binary(),
opts :: [headers: Tesla.Env.headers(), tesla_opts: Tesla.Env.opts()]
) :: Tesla.Env.t()
def put(host, key, content, opts \\ []) do
headers = opts[:headers] || []
HTTPClient.request(
method: :put,
url: host <> "/" <> key,
body: content,
headers: headers,
opts: opts[:tesla_opts]
)
end
@doc """
上传本地文件
## 示例
iex> COS.Object.put_from_file("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", "./example.txt")
{:ok, %Tesla.Env{
body: "",
headers: [
{"server", "tencent-cos"},
{"data", "Tue, 29 Mar 2022 16:39:58 GMT"},
...
],
...
}}
"""
@spec put_from_file(
host :: binary(),
key :: binary(),
path :: binary(),
opts :: [headers: Tesla.Env.headers(), tesla_opts: Tesla.Env.opts()]
) :: Tesla.Env.t()
def put_from_file(host, key, path, opts \\ []) do
put(host, key, File.read!(path), opts)
end
@doc """
追加上传对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/7741)
## 示例
iex> COS.Object.append("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", 0, "hello")
{:ok, %Tesla.Env{
body: "",
headers: [
{"x-cos-content-sha1", "5d41402abc4b2a76b9719d911017c592"},
{"x-cos-next-append-position", "5"},
...
],
...
}}
iex> COS.Object.append("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", 5, " world")
{:ok, %Tesla.Env{
body: "",
headers: [
{"x-cos-content-sha1", "b7913aa15c43be7d534b4eec6e99e8a0"},
{"x-cos-next-append-position", "11"},
...
],
...
}}
"""
@spec append(
host :: binary(),
key :: binary(),
position :: pos_integer(),
content :: binary(),
opts :: [headers: Tesla.Env.headers(), tesla_opts: Tesla.Env.opts()]
) :: Tesla.Env.t()
def append(host, key, position, content, opts \\ []) do
headers = opts[:headers] || []
HTTPClient.request(
method: :post,
url: host <> "/" <> key,
query: %{append: "", position: position},
headers: headers,
body: content,
opts: opts[:tesla_opts]
)
end
@doc """
复制对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/10881)
创建一个已存在 COS 的对象的副本,即将一个对象从源路径(对象键)复制到目标路径(对象键)。
建议对象大小为1M到5G,超过5G的对象请使用分块上传(Todo)。
## 示例
iex> COS.Object.copy(
"https://bucket-1250000000.cos.ap-beijing.myqcloud.com",
"destination.txt",
"other-bucket-1250000000.cos.ap-shanghai.myqcloud.com/source.txt"
)
{:ok, %Tesla.Env{
body: %{
"crc64" => "1791993320000000000",
"e_tag" => "\"6ae33dfd6c0c9fa03b77f75xxxxxxxxx\"",
"last_modified" => "2022-03-29T17:20:24Z"
},
headers: [
{"server", "tencent-cos"},
{"data", "Tue, 29 Mar 2022 16:39:58 GMT"},
...
],
...
}}
"""
@spec copy(
host :: binary(),
key :: binary(),
source :: binary(),
opts :: [headers: Tesla.Env.headers(), tesla_opts: Tesla.Env.opts()]
) :: Tesla.Env.t()
def copy(host, key, source, opts \\ []) do
headers = opts[:headers] || []
HTTPClient.request(
method: :put,
url: host <> "/" <> key,
headers: [{"x-cos-copy-source", source} | headers],
opts: opts[:tesla_opts],
result_key: "copy_object_result"
)
end
@doc """
查询对象元数据 - [腾讯云文档](https://cloud.tencent.com/document/product/436/7745)
"""
@spec head(
host :: binary(),
key :: binary(),
opts :: [
query: %{optional(:version_id) => binary()} | nil,
headers: Tesla.Env.headers(),
tesla_opts: Tesla.Env.opts()
]
) :: Tesla.Env.t()
def head(host, key, opts \\ []) do
version_id = get_in(opts, [:query, :version_id])
headers = opts[:headers] || []
HTTPClient.request(
method: :head,
url: host <> "/" <> key,
query: %{versionId: version_id},
headers: headers,
opts: opts[:tesla_opts]
)
end
@doc """
检查对象是否存在
## 示例
iex> COS.Object.exists?("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt")
true
iex> COS.Object.exists?("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example/")
true
iex> COS.Object.exists?("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "missing.txt")
false
"""
@spec exists?(
host :: binary(),
key :: binary(),
opts :: [
query: %{optional(:version_id) => binary()} | nil,
headers: Tesla.Env.headers(),
tesla_opts: Tesla.Env.opts()
]
) :: boolean()
def exists?(host, key, opts \\ []) do
with {:ok, _response} <- head(host, key, opts) do
true
else
_ -> false
end
end
@doc """
删除单个对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/7743)
## 示例
COS.Object.delete("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt")
# 删除“文件夹”,如果“文件夹”内有对象,则不会删除。
COS.Object.delete("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example/")
"""
@spec delete(
host :: binary(),
key :: binary(),
opts :: [
query: %{optional(:version_id) => binary()} | nil,
tesla_opts: Tesla.Env.opts()
]
) :: Tesla.Env.t()
def delete(host, key, opts \\ []) do
version_id = get_in(opts, [:query, :version_id])
HTTPClient.request(
method: :delete,
url: host <> "/" <> key,
query: %{versionId: version_id},
opts: opts[:tesla_opts]
)
end
@doc """
删除多个对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/8289)
## 示例
iex> COS.Object.multi_delete("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", %{
object: [%{key: "example.txt"}, %{key: "error.txt"}]
})
{:ok, %Tesla.Env{
body: %{
"deleted" => [%{
"key" => "example.txt",
...
}],
"error" => [%{
"key" => "error.txt",
...
}]
},
...
}}
# Quiet 模式,在响应中仅包含删除失败的对象信息和错误信息
iex> COS.Object.multi_delete("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", %{
object: [%{key: "example.txt"}, %{key: "error.txt"}],
quiet: true
})
{:ok, %Tesla.Env{
body: %{
"deleted" => [],
"error" => [%{
"key" => "error.txt",
...
}]
},
...
}}
"""
@spec multi_delete(
host :: binary(),
body :: %{
:object => [%{:key => binary(), optional(:version_id) => binary()}],
optional(:quiet) => boolean()
},
opts :: [tesla_opts: Tesla.Env.opts()]
) :: Tesla.Env.t()
def multi_delete(host, body, opts \\ []) do
body = {:delete, body}
with {:ok, response} <-
HTTPClient.request(
method: :post,
url: host <> "/?delete",
body: body,
opts: opts[:tesla_opts]
) do
body =
Enum.reduce(["deleted", "error"], %{}, fn key, acc ->
value =
response.body
|> get_in(["delete_result", key])
|> List.wrap()
Map.put(acc, key, value)
end)
{:ok, %{response | body: body}}
end
end
@doc """
获取请求预签名 URL
说明:
- 建议用户使用临时密钥生成预签名,通过临时授权的方式进一步提高预签名上传、下载等请求的安全性。
申请临时密钥时,请遵循[最小权限指引原则](https://cloud.tencent.com/document/product/436/38618),防止泄漏目标存储桶或对象之外的资源。
- 如果您一定要使用永久密钥来生成预签名,建议永久密钥的权限范围仅限于上传或下载操作,以规避风险。
- 获取对象的 URL 并下载对象参数,可在获取的 URL 后拼接参数 `response-content-disposition=attachment`。
## 示例
iex> COS.Object.get_presigned_url("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt")
"https://bucket-1250000000.cos.ap-beijing.myqcloud.com/example.txt?q-ak=AKIDb************************DmNIJ&q-header-list=&q-key-time=1649011703%3B1649012603&q-sign-algorithm=sha1&q-sign-time=1649011703%3B1649012603&q-signature=a9c13b2b1e09c5ce46df0242ac37b9cb828c0d6b&q-url-param-list="
"""
@spec get_presigned_url(
host :: binary(),
key :: binary(),
opts :: [
method: Tesla.Env.method(),
query: Tesla.Env.query(),
headers: Tesla.Env.headers(),
expired_at: DateTime.t(),
expire_in: COS.Utils.expire_in()
]
) :: binary()
def get_presigned_url(host, key, opts \\ []) do
method = opts[:method] || :get
query = opts[:query] || %{}
query =
method
|> COS.Auth.get("/" <> key,
query: query,
headers: opts[:headers],
expire_in: opts[:expire_in],
expired_at: opts[:expired_at]
)
|> Map.new()
|> Map.merge(query)
|> URI.encode_query()
encoded_key = key |> Utils.url_encode() |> String.replace("%2F", "/")
%{URI.parse(host) | path: "/" <> encoded_key, query: query}
|> URI.to_string()
end
@doc """
下载对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/7753)
## 示例
iex> COS.Object.put("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", "content")
{:ok, ...}
iex> COS.Object.get("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", "content")
{:ok, %Tesla.Env{
body: "content",
headers: [
{"content-type", "application/octet-stream"},
{"content-length", "7"},
...
],
...
}}
# 指定 Range 请求头部下载部分内容
iex> COS.Object.get("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", "content", headers: [
{"range", "bytes=0-2"}
])
{:ok, %Tesla.Env{
body: "con",
headers: [
{"content-type", "application/octet-stream"},
{"content-length", "3"},
{"accept-ranges", "bytes"},
{"content-range", "bytes 0-0/3"},
...
],
...
}}
"""
@spec get(
host :: binary(),
key :: binary(),
opts :: [
query: %{optional(:version_id) => binary()} | nil,
headers: Tesla.Env.headers(),
tesla_opts: Tesla.Env.opts()
]
) :: Tesla.Env.t()
def get(host, key, opts \\ []) do
version_id = get_in(opts, [:query, :version_id])
headers = opts[:headers] || []
HTTPClient.request(
method: :get,
url: host <> "/" <> key,
query: %{versionId: version_id},
headers: headers,
opts: opts[:tesla_opts]
)
end
@doc """
下载对象到本地文件
## 示例
iex> COS.Object.get_to_file("https://bucket-1250000000.cos.ap-beijing.myqcloud.com", "example.txt", "./example.txt")
{:ok, %Tesla.Env{
body: "content",
headers: [
{"content-type", "application/octet-stream"},
{"content-length", "7"},
...
],
...
}}
"""
@spec get_to_file(
host :: binary(),
key :: binary(),
path :: binary(),
opts :: [
query: %{optional(:version_id) => binary()} | nil,
headers: Tesla.Env.headers(),
tesla_opts: Tesla.Env.opts()
]
) :: Tesla.Env.t()
def get_to_file(host, key, path, opts \\ []) do
with {:ok, response} <- get(host, key, opts),
:ok <- File.write(path, response.body) do
{:ok, response}
end
end
@doc """
恢复归档对象 - [腾讯云文档](https://cloud.tencent.com/document/product/436/12633)
"""
@spec restore(
host :: binary(),
key :: binary(),
body :: %{days: pos_integer(), c_a_s_job_parameters: %{tier: binary()}},
opts :: [headers: Tesla.Env.headers(), tesla_opts: Tesla.Env.opts()]
) :: Tesla.Env.t()
def restore(host, key, body, opts \\ []) do
body = {:restore_request, body}
version_id = get_in(opts, [:query, :version_id])
headers = opts[:headers] || []
HTTPClient.request(
method: :post,
url: host <> "/" <> key,
query: %{restore: "", versionId: version_id},
headers: headers,
body: body,
opts: opts[:tesla_opts]
)
end
end