概要
フロント:React
、サーバーサイド:golang
で記事投稿サイトを作っていました。
画像投稿機能の実装をまとめます。
web frameworkにはそれぞれ、axios
とgin
を用いています。
実装
フロント(React)
post.tsx
interface ArticleState {
files: File[];
}
class Post extends React.Component<{}, ArticleState> {
constructor(props: {}) {
super(props);
this.state = {
files: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleOnDrop = this.handleOnDrop.bind(this);
}
handleOnDrop(acceptedFiles: File[]) {
this.setState({files: this.state.files.concat(acceptedFiles)});
}
async handleSubmit() {
const formData = new FormData();
for (var i in this.state.files) {
formData.append('images[]', this.state.files[i]);
}
const postArticleImage = postArticleImageFactory();
const resImageNames = await postArticleImage(formData);
}
render() {
return (
<Dropzone accept="image/*" onDrop={this.handleOnDrop}>
//省略
<Form.Button content="Submit" onClick={this.handleSubmit} />
);
}
}
- 画像投稿フォーム(ここでは
Dropzone
)から画像を投稿します。 - 投稿ボタンがクリックされると、
formData
に画像を詰めてapiにrequestを飛ばすメソッドを呼びます。
articleAPI.tsx
export const postArticleImageFactory = () => {
const postArticleImage = async (formData: FormData) => {
try {
const response = await axios.post('/api/post/image', formData, {
headers: {'Content-Type': 'multipart/form-data'},
});
if (response.status !== 200) {
throw new Error('Server Error');
}
return response.data;
} catch (err) {
throw err;
}
};
return postArticleImage;
};
-
headers: {'Content-Type': 'multipart/form-data'}
を設定しないとapi通信でエラーが発生するので、設定しています。
バックエンド(Golang)
handler.go
router := gin.Default()
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "OPTIONS"},
AllowHeaders: []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return true
},
MaxAge: 15 * time.Second,
}))
api := router.Group("/api")
{
api.POST("/post/image", func(c *gin.Context) {
cntlr.PostImageController(c)
})
}
-
gin
を利用してrequestを受け取っています。
service.go
func (s Service) PostImageService(c *gin.Context) []util.ImageName {
form, _ := c.MultipartForm()
files := form.File["images[]"]
var imageNames []util.ImageName
imageName := util.ImageName{}
for _, file := range files {
u, err := uuid.NewRandom()
if err != nil {
fmt.Println(err)
}
uu := u.String()
s.dao.PostImageToS3Dao(file, uu)
imageName.NAME = uu
imageNames = append(imageNames, imageName)
}
return imageNames
}
-
gin.Context
から画像ファイルを取り出しています。 - S3に保存する画像名を一意にする為、uuidを設定しています。(万が一画像名が同じだとバケットで上書きされる為)
s3.go
func (objs *S3) PostImageToS3(file *multipart.FileHeader, imageName string) error {
creds := credentials.NewStaticCredentials(objs.APPID, objs.SECRET, "")
cfg := aws.NewConfig().WithRegion("ap-northeast-1").WithCredentials(creds)
svc := s3.New(session.New(), cfg)
f, err := file.Open()
if err != nil {
log.Println(err)
}
defer f.Close()
size := file.Size
buffer := make([]byte, size)
f.Read(buffer)
fileBytes := bytes.NewReader(buffer)
fileType := http.DetectContentType(buffer)
path := "/media/" + imageName
params := &s3.PutObjectInput{
Bucket: aws.String("<Bucket Name>"),
Key: aws.String(path),
Body: fileBytes,
ContentLength: aws.Int64(size),
ContentType: aws.String(fileType),
}
resp, err := svc.PutObject(params)
fmt.Printf("response %s", awsutil.StringValue(resp))
return err
}
- S3に保存するメソッドです。
- テストしやすいようにS3に保存する目的だけのメソッドです(ゆえに返り値はerror)
ソースコード全貌は以下のgithubを参照してください
https://github.com/jpskgc/article