如何使用 AWS API
在使用它的任何一個 service 前要先準備好 credential 然候再建立 session,然候再跟 AWS services 互動,
Session 可以讓全部 AWS services 共用 (在使用各服務前會需要用 session 建立) ,最好 cache 起來,
每次要用之前再從 cache 拿出來, 避免每一次重新建立連線耗費資源。
[1] 初始化 credential
可以使用 aws-cli 指令 aws configure
幫你產生或手動建立檔案
~/.aws/config
[這裡填 profile name]
region = us-west-2
output = json
~/.aws/credentials
[這裡填 profile name]
aws_access_key_id = A******************A
aws_secret_access_key = 9**************************************V
常用的 credential 有幾種,以下會按照順序,哪個可以取到就使用
func GetAWSCredentialChain() (*credentials.Credentials, *aws.Config) {
config := aws.NewConfig()
var ProviderList []credentials.Provider = []credentials.Provider{
&credentials.EnvProvider{}, # 讀取本機環境變數
&credentials.SharedCredentialsProvider{ # 讀取本機端實體檔案的 credentials
Filename: "/Users/me/.aws/credentials",
Profile: "myProject",
},
&ec2rolecreds.EC2RoleProvider{ # IAM 賦與 EC2 Role 的權限
Client: ec2metadata.New(session.New(), config),
},
}
cred := credentials.NewChainCredentials(ProviderList)
return cred, config
}
(或) credential 也可以直接帶入 access key 與 secret key
cred := credentials.NewStaticCredentials(accessKey, secretKey, ``)
svc := s3.New(session.New(),
&aws.Config{
Region: aws.String(S3Region),
Credentials: cred,
},
)
[2] 初始化設定檔
func InitAWSConfig(region string) (*aws.Config, error) {
cred, conf := GetAWSCredentialChain()
val, err := cred.Get()
if err != nil {
logs.Error("InitAWSConfig error:", err)
}
logs.Debug("Cred ProviderName:", val.ProviderName)
conf.WithRegion(region).WithCredentials(cred)
return conf, nil
}
或直接返回 session
func NewSession(region string) *session.Session {
cred, conf := GetAWSCredentialChain()
conf.WithRegion(region).WithCredentials(cred)
return session.New(conf)
}
[3] 建立 Session (e.g. dynamo db)
func GetDynamodbInstance() (*dynamodb.DynamoDB, error) {
conf, err := InitAWSConfig("Dynamo DB 的 Region name e.g. us-west-2")
if err != nil {
logs.Error("GetDynamodbInstance error:", err)
return nil, err
}
svc := dynamodb.New(session.New(), conf)
return svc, nil
}
[4] 測試 (列出 DynamoDB 的 Table list)
svc, _ := services.GetDynamodbInstance()
result, err := svc.ListTables(&dynamodb.ListTablesInput{})
if err != nil {
log.Println(err)
return
}
log.Println("Tables:")
for _, table := range result.TableNames {
log.Println(*table)
}
補充 :
sess := session.New(&aws.Config{
Region: aws.String("ap-northeast-1"),
Credentials: credentials.NewSharedCredentials("/Users/home/.aws/credentials", "aws-cred-profile"),
MaxRetries: aws.Int(5),
})
svc := sqs.New(sess)
DynamoDB
型態
DynamoDB 有自定義的型態,像傳遞參數或接收參收要再將它的型態轉成我們熟悉的
B []byte `type:"blob"`
// A Boolean data type.
BOOL *bool `type:"boolean"`
// A Binary Set data type.
BS [][]byte `type:"list"`
// A List of attribute values.
L []*AttributeValue `type:"list"`
// A Map of attribute values.
M map[string]*AttributeValue `type:"map"`
// A Number data type.
N *string `type:"string"`
// A Number Set data type.
NS []*string `type:"list"`
// A Null data type.
NULL *bool `type:"boolean"`
// A String data type.
S *string `type:"string"`
// A String Set data type.
SS []*string `type:"list"`
params = &dynamodb.GetItemInput{
TableName: aws.String("user_contact"),
Key: map[string]*dynamodb.AttributeValue{ // Required
"user_id": { // Required
S: aws.String(uid),
},
},
AttributesToGet: []*string{ // 可省略,不加就是所有欄位都拿
aws.String("contact_list"),
},
}
svc, _ := services.GetDynamodbInstance()
resp, err := svc.GetItem(params)
contact_list := resp.Item["contact_list"].M
for key, val := range contact_list {
logs.Info(key)
logs.Info(val.S)
}
最多只可以取 100 筆
params = &dynamodb.BatchGetItemInput{
RequestItems: map[string]*dynamodb.KeysAndAttributes{
"user_contacts": {
Keys: []map[string]*dynamodb.AttributeValue{
{
"user_id": {S: aws.String("要查詢的 user_id")},
},
{
"user_id": {S: aws.String("要查詢的 user_id")},
},
},
AttributesToGet: []*string{
aws.String("contact_list"),
},
},
},
}
svc, _ := services.GetDynamodbInstance()
resp, err := svc.BatchGetItem(params)
for _, val := range resp.Responses["user_contacts"] {
contact_list := val["contact_list"].M # 將型態轉回 Map
for key, val := range contact_list {
logs.Info(key)
logs.Info(val.S) # 轉回 String
}
}
補充 : 用迴圈組出 BatchGetItem 需要的參數
id_keys []map[string]*dynamodb.AttributeValue
for _, id := range ids {
id_key := map[string]*dynamodb.AttributeValue{
"id": {S: aws.String(id)},
}
id_keys = append(id_keys, id_key)
}
params := &dynamodb.BatchGetItemInput{
RequestItems: map[string]*dynamodb.KeysAndAttributes{ // Required
"users": { // Required
Keys: id_keys,
AttributesToGet: ...略...
},
},
}
BatchGetItem & GetItem key 不支援用 index, 只能用 primary key
PutItem
建立一個 list 裡面有兩個 map
var list []*dynamodb.AttributeValue
var item dynamodb.AttributeValue
item.M = map[string]*dynamodb.AttributeValue{
"name": {S: aws.String(name)},
}
list = append(list, &item)
list = append(list, &item)
params := &dynamodb.PutItemInput{
TableName: aws.String("user_contacts"),
Item: map[string]*dynamodb.AttributeValue{
"name": {S: aws.String("Tom")},
"is_friend": {BOOL: aws.Bool(true)},
"contacts": {L: list},
"age": {N: aws.String("15")}, // int 用 N (number), 但後面還是要轉成字串
},
}
resp, err := svc.PutItem(params) // 即使成功 resp 不會有回傳值
建立一個 Map{“time”: map{…}, “friends”: list: [map{…}]}
結構 :
contacts : {
"time": {
"start": "",
"end": "",
},
"friends": [
{"name":"", "age":""},
{"name":"", "age":""},
]
}
var contacts, time map[string]*dynamodb.AttributeValue
var friends []*dynamodb.AttributeValue
// contacts - time
time = map[string]*dynamodb.AttributeValue{
"start": {S: aws.String(start)},
"end": {S: aws.String(end)},
}
// contacts - friends
for _, d := range Friends {
var item dynamodb.AttributeValue
item.M = map[string]*dynamodb.AttributeValue{
"name": {S: aws.String(name)},
"age": {S: aws.String(age)},
}
friends = append(friends, &item)
}
// contacts (最外層)
contacts = map[string]*dynamodb.AttributeValue{
"time": {M: time},
"friends": {L: friends},
}
Optional 的值,一定要宣告一個空物件,不要用 var
condition := map[string]*dynamodb.AttributeValue{}
if 是否有值 {
time := &dynamodb.AttributeValue{
M: map[string]*dynamodb.AttributeValue{
"start": {S: aws.String(start)},
"end": {S: aws.String(end)},
},
}
condition["time"] = time
}
params := &dynamodb.PutItemInput{
TableName: aws.String("user_policy"),
Item: map[string]*dynamodb.AttributeValue{
"condition": {M: condition},
},
}
DeleteItem
params := &dynamodb.DeleteItemInput{
TableName: aws.String("user_name"),
Key: map[string]*dynamodb.AttributeValue{
"user_id": {
S: aws.String(t.UserID),
},
},
}
resp, err := svc.DeleteItem(params) // 即使成功 resp 不會有回傳值
UpdateItem
Update 使用上每次都是整筆資料更新會比較簡單一點,也就是當沒有資料時,也要給它一個空物件,這樣 Update 時就可以把欄位刪除了,沒有空物件會引發錯誤
// info 為 optional 的值
info := map[string]*dynamodb.AttributeValue{}
friend_list []*string = []*string{aws.String(user_id)}
params := &dynamodb.UpdateItemInput{
Key: map[string]*dynamodb.AttributeValue{ // Required
"uid": { // Required
S: aws.String(uid),
},
},
TableName: aws.String("user"), // Required
UpdateExpression: aws.String(`
SET map.#key = :key,
#updated_at = :updated_at,
#a_map = :a_map
ADD #friend_list :friend_list
`),
ExpressionAttributeNames: map[string]*string{ // Required
"#key": aws.String("xxxkeyxxx"), // map 如果沒有存在的 key 會新增, 但是 map 的欄位一定要先存在, 否則會有錯誤
"#updated_at": aws.String("updated_at"),
"#a_map": aws.String("a_map"),
"#friend_list": aws.String("friend_list"),
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":key": {S: aws.String("xxxvalue")},
":updated_at": {S: aws.String(update_at)},
":a_map": {M: info},
":friend_list": {SS: friend_list},
},
ReturnValues: aws.String("UPDATED_NEW"), // (optional) 回傳更新後的資料
}
resp, err = svc.UpdateItem(params) // 即使成功 resp 不會有回傳值
更新巢狀資料下的某個值, 假設 friends 下有很多 friend_id 為 key 的 map, UpdateExpression 這樣寫:
SET friends.#friend_id.name = :val
(optional) info map
if 當有資料再更新 {
var time map[string]*dynamodb.AttributeValue
time = map[string]*dynamodb.AttributeValue{
"start": {S: aws.String(p.Condition.Time.Start)},
"end": {S: aws.String(p.Condition.Time.End)},
}
info["time"] = &dynamodb.AttributeValue{M: time}
}
- 不存在的資料會新增
- UpdateExpression 不能 ADD 一個 item 到 Map, 必須用 SET,但前提是欄位要先存在
update expression 其他說明
SET list[0] = :val1
REMOVE #m.nestedField1, #m.nestedField2
ADD aNumber :val2, anotherNumber :val3
DELETE aSet :val4
SET list[0] = :val1 REMOVE #m.nestedField1, #m.nestedField2 ADD aNumber :val2, anotherNumber :val3 DELETE aSet :val4
Query
第一種方法 : dynamodb 除了 GetItem (用 partition key 取得資料) 也可以使用其中某個欄位取得,不過要先到 Dynamodb 的 AWS Console 上對那個欄位建立 index
params := &dynamodb.QueryInput{
TableName: aws.String("users"), // Required
IndexName: aws.String("user_id-index"),
ConsistentRead: aws.Bool(false),
KeyConditionExpression: aws.String("#user_id = :user_id"),
ExpressionAttributeNames: map[string]*string{
"#user_id": aws.String("user_id"), // Required
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":user_id": { // Required
S: aws.String(user_id),
},
},
}
Query 支援用 index, GetItem 不支援用 index
ExpressionAttributeNames 也可以只寫成這樣
KeyConditionExpression: aws.String("user_id = :user_id"),
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
':user_id': {
S: aws.String(user_id),
},
},
ExpressionAttributeNames 代入 int : 最然對程式來說他是 int 但是實際上還是要用 string,只不過要指定 N
(Number)
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":num": {N: aws.String("1")}
}
resp, err = svc.Query(params)
如果沒有資料,不會引起 err
喔! 要記得判斷 resp 是否為 nil
第二種方法 : 假設你有設 partition key 及 sort key ,但你只知道 partition key 不知道 sort key 你會沒辦法用 GetItem,這時也可以用 Query 直接對 partition key 取資料
params := &dynamodb.QueryInput{
TableName: aws.String(table), // Required
ConsistentRead: aws.Bool(false),
KeyConditionExpression: aws.String("#user_id = :user_id"),
ExpressionAttributeNames: map[string]*string{
"#user_id": aws.String("user_id"), // Required
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":user_id": { // Required
S: aws.String(user_id),
},
},
}
Scan
用來取這個 Table 的全部資料, 不可以設定條件 (where)
轉換 dynamodb 格式
從 dynamodb 取出來的資料有它自已的格式,在取的時候有時候蠻麻煩的,sdk 有提供轉換格式的函式
如果用 Marshal/Unmarshal User struct,它會優先依照 tag dynamodbav
將值 Map 到 struct 欄位,其次才是 tag json
GetItem (struct)
type User struct {
Name string `json:"name" dynamodbav:"user_name"` // 避開 dynamodb 保留字
}
var u User
err = dynamodbattribute.UnmarshalMap(resp.Item, &u)
var m map[string]interface{}
err = dynamodbattribute.UnmarshalMap(resp.Item, &m)
BatchGetItem
// Specific table (user_contacts)
var m []map[string]interface{}
err = dynamodbattribute.UnmarshalListOfMaps(resp.Responses["user_contacts"], &m)
// All table
var m map[string][]map[string]interface{}
m = make(map[string][]map[string]interface{})
for k, v := range resp.Responses {
var tmp []map[string]interface{}
err = dynamodbattribute.UnmarshalListOfMaps(v, &tmp)
if err != nil {
return nil, err
}
m[k] = tmp
}
Query
var m []map[string]interface{}
err = dynamodbattribute.UnmarshalListOfMaps(resp.Items, &m)
S3
PutObject
params := &s3.PutObjectInput{
Bucket: aws.String("bucket_name"),
Key: aws.String("file_name"),
Body: bytes.NewReader([]byte("json_str")),
}
svc, err := GetS3Instance()
resp, err := svc.PutObject(params)
resp :
{
ETag: "\"b8468dbe0941b5164253860813663edf\""
}
需要注意的是成功上傳後的檔案預設的 ACL 都是 private, 除非你的 bucket 有設定對放開放, 不然就要指定 ACL, 可參考官方文件
DeleteObject
params := &s3.DeleteObjectInput{
Bucket: aws.String("bucket_name"),
Key: aws.String("file_name"),
}
svc, err := GetS3Instance()
resp, err := svc.DeleteObject(params) // 即使成功 resp 不會有回傳值
無法直接刪除一個 folder
DeleteObjects
params := &s3.DeleteObjectsInput{
Bucket: aws.String(bucket),
Delete: &s3.Delete{
Objects: []*s3.ObjectIdentifier{
{
Key: aws.String("objectkey1"),
},
{
Key: aws.String("objectkey2"),
},
},
}
svc, err := GetS3Instance()
resp, err = svc.DeleteObjects(params)
如果要刪除整個 folder, 要用 loop 再搭配 listObject 刪除, 直到 listObject 取不到東西為止
ListObject
params := &s3.ListObjectsInput{
Bucket: aws.String(bucket),
Prefix: aws.String(path),
MaxKeys: aws.Int64(2), // [Optional] 限制一次拿出來的數量, 最多 1,000 (同時也是預設值)
}
result, err = s.Service.ListObjects(params)
result:
{
Contents: [
{
ETag: "\"e******************************8\"",
Key: "test-dir/2.png",
LastModified: 2017-11-20 10:20:50 +0000 UTC,
Owner: {
DisplayName: "testqa",
ID: "b**************************************************************6"
},
Size: 14688,
StorageClass: "STANDARD"
},
{
...
}
],
IsTruncated: false,
Marker: "",
MaxKeys: 1000,
Name: "test-bucket",
Prefix: "test-dir"
}
最多一次只能取出 1000 個 item
GetObject
params := &s3.GetObjectInput{
Bucket: aws.String("bucket_name"),
Key: aws.String("file_name"),
}
svc, err := GetS3Instance()
resp, err = svc.GetObject(params)
json_str, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(json_str))
// Dump resp
(*s3.GetObjectOutput)(0xc420468000)({
AcceptRanges: "bytes",
Body: buffer(0xc42034e040),
ContentLength: 633,
ContentType: "binary/octet-stream",
ETag: "\"34d8d42271944aa866145dbeb550dd86\"",
LastModified: 2016-09-26 08:12:23 +0000 UTC,
Metadata: {
}
})
HeadObject
可以用來判斷 object 是否存在在 s3
params := &s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
}
res, err = svc.HeadObject(params)
HeadObjectOutput
{
AcceptRanges: "bytes",
ContentLength: 80936,
ContentType: "image/jpeg",
ETag: "\"7a6e371115538ae1a8b836d1cfd8fc3b\"",
LastModified: 2018-02-12 09:37:14 +0000 UTC,
Metadata: {
}
}
GetObjectRequest (pre-signed url for downloading - GET)
svc, err := GetS3Instance()
req, _ := svc.GetObjectRequest(&s3.GetObjectInput{
Bucket: aws.String("bucket_name"),
Key: aws.String("file_path"),
})
pre_url, err = req.Presign(time.Duration(10) * time.Second) // within 10 seconds for downloading file
PutObjectRequest (pre-signed url for uploading - PUT)
svc, err := GetS3Instance()
req, _ := svc.PutObjectRequest(&s3.PutObjectInput{
Bucket: aws.String("bucket_name"),
Key: aws.String("file_path"),
})
pre_url, err := req.Presign(15 * time.Minute) // within 15 minutes for uploading file
關於 ACL, 不知道為什麼參數加上 ACL 指定 public-read
當上傳時 AWS 會回 403 錯誤訊息為 SignatureDoesNotMatch
, 後來解法是上傳成功後再去 call PutObjectAcl
改變 ACL
curl 測試是否可以上傳
curl -v -T /tmp/test.mp4 "https://my_bucket.s3-us-west-2.amazonaws.com/videos/test.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAJXSKW3C6...略..."
CopyObject
可從一個 bucket 裡的檔案 copy 到另一個 bucket 下
svc, err := GetS3Instance()
_, err = svc.CopyObject(&s3.CopyObjectInput{
CopySource: aws.String(from_path), // 注意!! 來源的組成是 {bucket}/{file_path}
Bucket: aws.String(bucket),
Key: aws.String(to_path),
ACL: aws.String("public-read"), // optional
})
需要注意的是成功上傳後的檔案預設的 ACL 都是 private, 除非你的 bucket 有設定對放開放, 不然就要指定 ACL, 可參考官方文件
PutObjectAcl
svc, err := GetS3Instance()
params := &s3.PutObjectAclInput{
Bucket: aws.String(bucket),
Key: aws.String(file_path),
ACL: aws.String("public-read"),
}
_, err = svc.PutObjectAcl(params)
CloudFront
pre-signed url
產生的 pre-signed url 是 custom domain 而不是 aws s3 的 domain
前置作業請參考 aws cloudfront - pre-signed url + custom domain 設定
以上完成後,到這裡應該已經將 private key 上傳到主機了, 就可以開始實作 :
file_url := "https://cdn.your-custom-domain.com/test.jpg" // custom domain + S3 file
key_id := "APK**************Y2A" // Access Key ID
priv_key, err := sign.LoadPEMPrivKeyFile("/tmp/cloudfront-private_key-pk-APK**************Y2A.pem") // Private key path
if err != nil {
logs.Debug("load pem err: %s", err.Error())
} else {
signer := sign.NewURLSigner(key_id, priv_key)
signed_url, err := signer.Sign(file_url, time.Now().Add(15*time.Second))
if err != nil {
logs.Debug("Failed to sign url, err: %s\n", err.Error())
}
logs.Info("signed_url: %s", signed_url)
}
signed url :
https://cdn.your-custom-domain.com/test.jpg?Expires=1500302808&Signature=mhh8YmrYMs91Cc4qoTeDSUjOeQChe-U7Ksm0Ue92WJufMlKkEAOHR3GeoEaoc3nSpitA5KV-4op6EePTfYG8DMqr-J8Oh55gCNGMjicaiMdz~VOCEoSUTeYgLFnj-dQT5OGjdg~iELDX5LROZ2UL~5vJgSKrlgiH2VLp4WMO~AoDe~CiZAWtQ49Jbrx1XZtVX3i9lCDAL4881psx8xt7W4dANJ0uo1oelBo5P0BhM0v400un9UT4FG-ZYrXB1iDYszwxhLx4TWZSa2MWXWTJyXzeZwcVcbulvdP7apokPC5aMrLaPfel6v22HSFAEP62Unety01SN4HWYtLCW7v9VQ__&Key-Pair-Id=APKAIZQ4PTQ4P7ZNQY2A
SQS
Send Message / Receive Message / Delete Message
參考 : golang-aws-sqs-example
Send Message Batch Request
var entries []*sqs.SendMessageBatchRequestEntry
for i := 1; i <= 5; i++ {
f := sqs.SendMessageBatchRequestEntry{ // Required
Id: aws.String(fmt.Sprintf("Message_%d", i)), // Required, 數字 英文 - _ 而且 ID 不可重覆
MessageBody: aws.String("message body"), // Required
}
entries = append(entries, &f)
}
params := &sqs.SendMessageBatchInput{
Entries: entries,
QueueUrl: aws.String(QueueUrl), // Required
}
resp, err := svc.SendMessageBatch(params)
Get queue attributes
params := &sqs.GetQueueAttributesInput{
QueueUrl: aws.String(QueueUrl), // Required
AttributeNames: []*string{
aws.String("All"), // Required, 填要取得的欄位,`All` 是全取
// More values...
},
}
resp, err := svc.GetQueueAttributes(params)
Set queue attributes
params := &sqs.SetQueueAttributesInput{
Attributes: map[string]*string{
"ReceiveMessageWaitTimeSeconds": aws.String("0"),
},
QueueUrl: aws.String(QueueUrl), // Required
}
_, err := svc.SetQueueAttributes(params) // 成功不會返回內容
設定 RedrivePolicy (retry 機制)
Attributes: map[string]*string{
// 刪除
"RedrivePolicy": aws.String(""),
// 最多一個 message 收到 3 次,超過就會送到 dead letter queue
"RedrivePolicy": aws.String("{\"maxReceiveCount\":\"3\", \"deadLetterTargetArn\":\"arn:aws:sqs:ap-northeast-1:3**********2:MyDeadLetterQueue\"}"),
},
Change visibility timeout
// Change visibility timeout
change_params := &sqs.ChangeMessageVisibilityInput{
QueueUrl: aws.String(QueueUrl), // Required
ReceiptHandle: message.ReceiptHandle, // Required
VisibilityTimeout: aws.Int64(0), // Required
}
_, err = w.Svc.ChangeMessageVisibility(change_params) // 成功不會返回內容
CloudWatch
PutMetricData
自已推一些數據讓 cloudwatch 幫你監控;上報 metric,不需要去 CloudWatch 設定,如果是新的 metric,它自已會新增
支援一次上報多個 metric data, 所以以下設計成多個
// Metric collection
metric_collection := map[string]float64{}{
"success": 1,
}
// (optional) Dimensions
dimensions := map[stirng]string{}{
"job_type": "curl",
}
// New metrict data input
params := &cloudwatch.PutMetricDataInput{
Namespace: aws.String(namespace), // Required
}
// Give value to every metric data.
for k, v := range metric_collection {
metric_data := &cloudwatch.MetricDatum{
MetricName: aws.String(k), // Required
Timestamp: aws.Time(time.Now()),
Value: aws.Float64(v),
}
if len(dimensions) > 0 {
for k, v := range dimensions {
dimension := &cloudwatch.Dimension{Name: aws.String(k), Value: aws.String(v)}
metric_data.Dimensions = append(metric_data.Dimensions, dimension)
}
}
params.MetricData = append(params.MetricData, metric_data)
}
_, err = c.Service.PutMetricData(params)
SES
AWS 為了防止自已的 mail server 被當作濫發 email 的工具,所以目前我們都是在 SES sandbox 模式下發信的,它有一些限制
- 要先到 SES 後台 Email Addresses 新增你的 email,認證後才可以寄信, 收信也是
- 一天最多 200 封信,每秒最多一封
要突破以上限制則需要另外向 AWS 申請
SendEmail
svc := ses.New(sess)
params := &ses.SendEmailInput{
Destination: &ses.Destination{ // Required
// BccAddresses: []*string{
// aws.String("Address"), // Required
// },
// CcAddresses: []*string{
// aws.String("Address"), // Required
// },
ToAddresses: []*string{
aws.String("test+to@gmail.com"), // Required, 如果傳進 slice 改用 aws.StringSlice
},
},
Message: &ses.Message{ // Required
Body: &ses.Body{ // Required !! Html / Text 擇一使用就好
// Html: &ses.Content{
// Data: aws.String("Test html content"), // Required
// Charset: aws.String("utf-8"),
// },
Text: &ses.Content{
Data: aws.String("Test raw content"), // Required
Charset: aws.String("utf-8"),
},
},
Subject: &ses.Content{ // Required
Data: aws.String("Test subject"), // Required
Charset: aws.String("utf-8"),
},
},
Source: aws.String("Test user <test_user@gmail.com>"), // Required
ReplyToAddresses: []*string{
aws.String("test+reply@gmail.com"), // Required
},
}
resp, err := svc.SendEmail(params)
if err != nil {
return errors.New("SES response error: " + err.Error())
}
resp :
(*ses.SendEmailOutput)(0xc42002c0a0)({
MessageId: "010101581e1837c2-e0c68369-e7c4-47e4-b01e-3f7f6afca529-000000"
})
Sendor (from) 只支援 Ascill, 如果要改用 utf-8 字元要改成
=?utf-8?B?V2ktRmnjgqvjg6Hjg6k=?= <noreply@example.com>
SNS
Topics - Publish to topic
先建立一個 Topic 然候再 Subscribe 它,選擇你要使用什麼收到你訂閱的東西,
最簡單是用 email 的方式 - 填上自已的 email 後,你需要收信驗證,驗證完後只要有人 publish 到這個 topic 就會收到 email 了
params := &sns.PublishInput{
Message: aws.String("message"), // Required
TopicArn: aws.String("arn:aws:sns:ap-northeast-1:4**********7:event_update"),
}
resp, err := svc.Publish(params)
if err != nil {
return
}
resp :
{
MessageId: "f56bf715-2584-5fe4-8f0a-a7b9c0c2c757"
}
Applications - Push Notification
先去 SNS 的 Applications 註冊 Push Notification 的服務,並把 ARN 記下來,
手機裡的 App 會有個 UUID (app 跟 gcm/apns 註冊拿到的),帶這個上來到 Server,
拿這個 UUID 向 SNS 註冊 Token (createPlatformEndpoint 帶上面註冊 SNS 的 ARN, 及 app UUID, enabled: true (enabled 預設是 false, 所以要改成 true)),會拿到 EndpointArn,
建議把這個 Token 存下來,以便日後再發送時使用,
註冊完後 AWS 的 SNS web UI 後台就有一筆 record ,也可以直接用 web UI 發送 notification 做測試,
每筆 record 後面都有 enabled 值,如果是 false 就代表不能推送,只要 SNS 推送一次但送不成功後就會把它改成 false,
後端要推送只要對 EndpointArn 發送 message 就可以了,格式可以選擇 raw 或 json
GCM 可以帶 title, 但 APNS 的 title 預設是 application name
GCM
{ "GCM": "{ \"notification\": { \"body\": \"test body\",\"title\": \"test titel\",\"icon\": \"test icon\" },\"data\": { \"custom_field\": \"custom_value\" } }" }
APNS or APNS_SANDBOX (dev)
{ "APNS":"{\"aps\":{\"alert\":\"Hello World!!\"},\"custom_field\": \"custom_value\"}" }
{ "APNS_SANDBOX":"{\"aps\":{\"alert\":\"Hello World!!\"},\"custom_field\": \"custom_value\"}" }
上面 payload 要注意的是最後總共要 json encode 兩次
(最外層 GCM
/APNS
key 的值已經先被 json encode 過一次了)
Code :
params := &sns.PublishInput{
Message: aws.String(message), // Required
TargetArn: aws.String(target_arn),
MessageStructure: aws.String("json"),
}
_, err = s.Service.Publish(params)
Rekognition
DetectLabels (image file)
ff, _ := os.Open("test.jpg")
defer ff.Close()
bin = make([]byte, 500000)
ff.Read(bin)
params := &rekognition.DetectLabelsInput{
Image: &rekognition.Image{
Bytes: []byte(bin),
},
MaxLabels: aws.Int64(5),
MinConfidence: aws.Float64(1.0),
}
resp, err = svc.DetectLabels(params)
DetectLabels (s3)
params := &rekognition.DetectLabelsInput{
Image: &rekognition.Image{
S3Object: &rekognition.S3Object{
Bucket: aws.String("bucket"),
Name: aws.String("file_path"),
},
},
MaxLabels: aws.Int64(5),
MinConfidence: aws.Float64(1.0),
}
resp, err = svc.DetectLabels(params)