1. 目的
想在發(fā)送郵件的過(guò)程中帶上附件,查了公司郵件open API的文檔,發(fā)現(xiàn)只需在調(diào)用接口時(shí)帶上attachmentList參數(shù),內(nèi)容是文件id即可; 文件id可以通過(guò)現(xiàn)成接口(稱(chēng)為A)拿到,具體步驟就是調(diào)用接口A,上傳文件(文件流的方式),即可在返回值中獲取文件id,再將文件id放入郵件open API接口中即可。
把需求拆解一下,分為以下幾塊:1. [前端] 做一個(gè)上傳文件的組件,需要 實(shí)現(xiàn)文件上傳+獲取文件上傳后的返回值 的功能 2. [后端] 包一個(gè)文件上傳接口,需要實(shí)現(xiàn) 接收文件+調(diào)用接口A上傳文件+獲取接口A返回值+返回結(jié)果 的功能。
2. 前端代碼
首先展示一下前端代碼,這里用的是備受吐槽的飛冰UI組件+React框架。
這里用到了飛冰的上傳文件組件
# render部分
<Upload.Dragger
listType='text'
action={uploadApi}
beforeUpload={this.handleAttachValidate}
onSuccess={this.handleUploadSuccess}
onRemove={this.handleRemoveFile}
onError={this.handleUploadError}
/>
# 代碼實(shí)現(xiàn)
# 驗(yàn)證上傳文件的格式以及文件個(gè)數(shù)
handleAttachValidate = (file) => {
let fileName = file.name
let returnFlag = true
let attachmentList = this.state.form.attachmentList
if (file.size >= 1048576) {
Message.error({
title: '文件大小不能超過(guò)1M',
align: 'cc cc'
});
returnFlag = false
}
attachmentList.forEach(function (item) {
if (item.name == fileName) {
Message.error({
title: '文件名有重復(fù)',
align: 'cc cc'
});
returnFlag = false
}
})
if (attachmentList.length >= 5) {
Message.error({
title: '附件個(gè)數(shù)不能大于5個(gè)',
align: 'cc cc'
});
returnFlag = false
}
return returnFlag
}
# 刪除已上傳的文件 (只需將文件id刪除即可)
handleRemoveFile = (file) => {
let fileName = file.name
if (file.response != null && file.response.success && file.response.data.uploadRes) {
let attachmentPath = file.response.data.Attachment.AttachmentPath
let form= this.state.form
let index = -1
for (var i = 0; i < form.attachmentList.length; i++) {
if (form.attachmentList[i].name == fileName && form.attachmentList[i].AttachmentPath == attachmentPath) {
index = i
break
}
}
if (index > -1) {
form.attachmentList.splice(index, 1)
}
this.setState({ form: form})
}
}
# 上傳失敗的提示
handleUploadError = (file) => {
if (file.response != null && file.response.error != null) {
Message.error({
title: file.response.error.toString(),
align: 'cc cc'
})
}
}
# 上傳成功后,將文件id填入表單
handleUploadSuccess = (file, value) => {
let res = file.response
let form= this.state.form
if (res.success && res.data.uploadRes) {
form.attachmentList.push({ name: file.name, AttachmentName: res.data.Attachment.AttachmentName, AttachmentPath: res.data.Attachment.AttachmentPath })
} else {
Message.error({
title: file.name.toString() + '上傳失敗',
align: 'cc cc'
});
}
this.setState({ form: form})
}
3. 后端代碼
后端主要用的是Django,直接上代碼吧
- controller層
@request_fixture #自己寫(xiě)的error controller裝飾器
def post_attachment_upload(request):
"""
上傳郵件附件
"""
if request.method != 'POST': # 規(guī)定必須是POST方法
return error_wrapper(type=ErrorCode.REQ_METHOD_ERROR)
else:
files = request.FILES.getlist('file', None) # Django獲取文件的方式
if not files:
return error_wrapper(error='無(wú)上傳文件')
else:
res = post_attachment_upload_impl(files)
if res.get('uploadRes'):
return success_wrapper(data=res)
else:
return error_wrapper(error=res.get('desc'))
- 業(yè)務(wù)代碼
#!/usr/bin/python env
# -*- coding: utf-8 -*-
import os
import requests
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from app.common_app.constants import API
from utils.file_utils import file_to_bytes
def post_attachment_upload_impl(files):
file_name = str(files[0])
file_path = default_storage.save(file_name, ContentFile(files[0].read())) # 暫時(shí)存下文件,并獲取文件路徑
file_bytes = file_to_bytes(file_path) # 文件轉(zhuǎn)碼
os.remove(file_path) # 刪除文件
body = dict(AttachmentList=[dict(AttachmentName=file_name, AttachmentContent=file_bytes)])
res = requests.post(url=API.MAIL_UPLOAD_ATTACHMENT, json=body).json()
if res.get('ResultCode') == 1:
if res.get('AttachmentList') and len(res.get('AttachmentList')) > 0:
return dict(uploadRes=True, Attachment=res.get('AttachmentList')[0], desc='上傳成功')
else:
return dict(uploadRes=False, desc='上傳失敗')
else:
return dict(uploadRes=False, desc=res.get('ResultMsg'))
def file_to_bytes(file): # 文件轉(zhuǎn)碼成base64
with open(file, 'rb') as f:
s = f.read()
res = base64.b64encode(s)
return str(res.decode())
大功告成!