【Go言語】WebフレームワークBeegoを使ってファイルをアップロードする方法

# サンプル

今回は、このように`static`フォルダ内の`uploadフォルダ`に画像をアップロードするアプリを作っていきます。
#### タスクの登録画面から必要な情報を入力して、登録ボタンをクリックします。
![BeegTodo登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/df6dc266-baf7-5e9a-8471-f6b59107fc45.png)

すると、`static`フォルダ内の`uploadフォルダ`に画像をアップロードされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/4b117910-2ee0-73b1-9e9b-0c39c743ffec.png)

# ディレクトリ構造
ツリー構造は下記のようになっています。

```
MY-FIRST-BEEGO-PROJECT
├─ conf
│  └─ app.conf
├─ controllers
│  └─ default.go
├─ crypt
│  └─ crypt.go
├─ models
│  └─ todo.go
├─ routers
│  └─ router.go
└─ static
   ├─ css
   ├─ img
   ├─ js
   │  ├─ form.js
   │  ├─ login.js
   │  ├─ reload.min.js
   │  ├─ todoCreate.js
   │  ├─ todoCard.js
   │  └─ todoList.js
   ├─ lib
   │  └─ bootstrap
   │     ├─ css
   │     │  ├─ bootstrap-grid.min.css
   │     │  └─ bootstrap.min.css
   │     ├─ js
   │     │  ├─ bootstrap-bundle.min.js
   │     │  └─ bootstrap.min.js
   │     ├─ jquery
   │     │  └─ jquery.js
   │     └─ upload
   ├─ views
   │  ├─ mytodo
   │  │  ├─ addTodo.tpl
   │  │  └─ myList.tpl
   │  ├─ index.tpl
   │  ├─ subIndex.tpl
   │  └─ thirdIndex.tpl
   └─ main.go
```


# Beegoの関数の命名規則
Beegoの関数名の命名規則について忘れがちなので記載しておきます。

:::note warn
#### 関数名の先頭文字は`大文字`で以降は`小文字`となります。

```sample.go
func (c *SampleController) Post () {
    //何らかの処理をここに記載する
}
```
![Beego関数の命名規則について.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/03bf9763-6fc9-a6b4-725b-24ef2b93d810.png)

:::

# フロント側のコード
では、ファイルのアップロード項目を含むコードを書いていきます。
```views/mytodo/addTodo.tpl
<!DOCTYPE>
<html>
    <head>
        <title>Todo登録</title>
        <!--共通-->
        <link rel="stylesheet" type="text/css" href="/static/lib/bootstrap/css/bootstrap.min.css"/>
        <script type="text/javascript" src="/static/lib/jquery/jquery.js"></script>
        <!--個別-->
        <script type="text/javascript" src="/static/js/taskCreate.js"></script>        
    </head>
    <body>
        <h1>Todo登録</h1>
        <div class="container">
            サンプル
            <form enctype="multipart/form-data">
                <div class="form-group mb-3">
                    <label>タスク名</label>
                    <input type="text" id="myTask" name="myTask" class="form-control border border-dark" required/>
                </div>
                <div class="form-group mb-3">
                    <label for="exampleFormControlTextarea1" class="form-label">タスク詳細</label>
                    <textarea class="form-control" id="myTaskDescription" name="myTaskDescription" rows="3"></textarea>
                </div>
                <div class="form-group mb-3">
                    <label>優先度</label>
                    <select id="myTaskPriority" name="myTaskPriority" class="form-select form-select-lg mb-3" aria-label=".form-select-lg">
                        <option>-- 選択してください --</option>
                        <option value="1">高</option>
                        <option value="2">中</option>
                        <option value="3">低</option>
                    </select>
                </div>
                <div class="form-group mb-3">
                    <label>タスク画像</label>
                    <input type="file" id="myTaskImage" name="myTaskImage" class="form-control border border-dark"/>
                </div>
                <div class="form-group mb-3">
                    <button id="todoCreate" class="btn btn-primary">
                        登録<br/><small>Create</small>
                    </button>
                    <button id="backToTopPage" class="btn btn-secondary">
                        一覧に戻る<br/><small>Back To TopPage</small>
                    </button>
                </div>
            </form>
        </div>
    </body>
</html>
```

```static/js/taskCreate.js
$(document).ready(function(){
    //一覧に戻るボタンをクリックしたときの処理
    $('#backToTopPage').on('click',function(){
        location.replace("/myTodo/myList");
    });

    //登録ボタンをクリックしたときの処理
    $('#todoCreate').on('click',function(e){
        //POST通信するための設定
        e.preventDefault();

        //オプションタグの選択項目を取得
        const selectedTaskPriority = document.getElementById("myTaskPriority");

        //フォームデータの作成
        let formData = new FormData();
        formData.append('myTask',$("#myTask").val());
        formData.append('myTaskDescription',$("#myTaskDescription").val());
        formData.append('myTaskPriority',$("#myTaskPriority").val());   //  $("#myTaskPriority").val()
        formData.append('myTaskImage',$("#myTaskImage")[0].files[0]);
        const fileInfo = $("#myTaskImage").val();
        console.log(fileInfo);

        $.ajax({
            url:"/controllers/todoAdd",
            method:"POST",
            data:formData,
            processData:false,
            contentType:false,
            success:function(response){
                alert("タスクの登録に成功しました。");
            },
            error:function(response){
                alert("タスクの登録に失敗しました。");
            }
        });
    });

});
```


# サーバ側のコード

ファイルアップロード機能について試行錯誤したのでメモしておきます。

:::note info

`SaveFile `メソッドが使えない原因として、`c.SaveFile `が直接 `beego.Controller `に存在しない可能性があります。
実際、SaveFile は beego.Controller で使う場合は、ファイルの処理方法が少し異なる場合があります。

ここでは、ファイルの保存に関連する方法を説明しておきます。

<b>解決策:</b> `c.SaveFile `ではなく、`c.SaveFile `の代わりに `os `パッケージを使って直接ファイルの保存を行う方法
#### 1. ファイルを保存するために os パッケージを使う
`SaveFile `メソッドを使わず、os を使ってファイルを保存する方法に変更します。

#### 変更点の説明
`os.MkdirAll `を使って保存先ディレクトリを作成: static/upload/ ディレクトリが存在しない場合に作成します。これにより、ディレクトリがない場合でもエラーにならないようにします。

ファイルの保存に os.Create を使用: os.Create を使ってファイルを保存します。これにより、ファイルを保存するための` os.File `を取得できます。

ファイルの内容をコピーする: アップロードされたファイルを` f.ReadFrom(f) `を使って保存します。この手順は、`SaveFile `メソッドと同じ目的を果たしますが、手動でファイルの内容を処理しています。

:::


#### 蒸気を踏まえて作成したコード

```controllers/default.go
package controllers

import (
	"fmt"
	"github.com/astaxie/beego"
	"os"
	"path/filepath"
)

// タスク登録
type TodoAddController struct {
	beego.Controller
}

// タスク登録用コントローラ
func (c *TodoAddController) POST() {
	// フォームデータから入力情報を取得する
	task := c.GetString("myTask")
	taskDescription := c.GetString("myTaskDescription")
	taskPriority := c.GetString("myTaskPriority")

	fmt.Println("フォーム画面から取得したタスク名は、", task)
	fmt.Println("フォーム画面から取得したタスク説明は、", taskDescription)
	fmt.Println("フォーム画面から取得した優先度は、", taskPriority)

	// ファイルをアップロード
	f, h, err := c.GetFile("myTaskImage")
	if err != nil {
		fmt.Println("ファイルエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像アップロード失敗")
		return
	}
	defer f.Close()

	// 保存先ディレクトリの作成
	uploadPath := "static/upload/"
	err = os.MkdirAll(uploadPath, os.ModePerm) // ディレクトリがない場合に作成
	if err != nil {
		fmt.Println("ディレクトリ作成エラー:", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ディレクトリ作成に失敗しました")
		return
	}

	// 画像ファイル名を取得して保存
	fileName := h.Filename
	savePath := filepath.Join(uploadPath, fileName)

	// ファイルを保存
	outFile, err := os.Create(savePath)
	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存に失敗しました")
		return
	}
	defer outFile.Close()

	// アップロードされたファイルの内容を保存
	_, err = f.Seek(0, 0) // ファイルの最初に戻す
	if err != nil {
		fmt.Println("ファイル読み込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル読み込みに失敗しました")
		return
	}

	_, err = outFile.ReadFrom(f)
	if err != nil {
		fmt.Println("ファイル書き込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル書き込みに失敗しました")
		return
	}

	// ファイル名を表示
	fmt.Println("ファイル名は", fileName)

	// 成功レスポンスを返す
	c.Ctx.ResponseWriter.WriteHeader(200)
	c.Ctx.WriteString("タスク登録が成功しました")
}

```

全体のコードは下記です。

```controllers/default.go
package controllers

import (
	"fmt"
	"time"

	"my-first-beego-project/models"
	_ "my-first-beego-project/models"

	"github.com/astaxie/beego"
	"github.com/beego/beego/v2/client/orm"

	"my-first-beego-project/crypto"

	"os"
	"path/filepath"
)

type MainController struct {
	beego.Controller
}

func (c *MainController) Get() {
	c.Data["Website"] = "beego.me"
	c.Data["Email"] = "astaxie@gmail.com"
	c.TplName = "index.tpl"
}

// 20250205追加
type SubController struct {
	beego.Controller
}

type ThirdController struct {
	beego.Controller
}

type UserController struct {
	beego.Controller
}

// ログイン画面
type LoginController struct {
	beego.Controller
}

// タスクの登録画面
type TodoCreateController struct {
	beego.Controller
}

// タスク登録
type TodoAddController struct {
	beego.Controller
}

// 20250205
func (c *SubController) Get() {
	c.Data["Page"] = "SubPage"
	c.TplName = "subIndex.tpl"
}

// @router :id[get]
func (this *ThirdController) Get() {
	this.Data["ID"] = this.Ctx.Input.Param(":id")
	this.TplName = "thirdIndex.tpl"
}

// ログイン画面コントローラ
func (c *LoginController) Get() {
	c.TplName = "login.tpl"
}

func (c *UserController) Post() {
	//フォームデータを取得する
	name := c.GetString("name")
	email := c.GetString("email")
	password := c.GetString("password")

	//デバッグ用
	//fmt.Println("フォーム画面から取得したNameは：", name)
	//fmt.Println("フォーム画面から取得したEmailは：", email)
	//fmt.Println("フォーム画面から取得したPasswordは：", password)

	c.Data["json"] = map[string]interface{}{
		"status":  "成功",
		"message": "フォーム画面からデータを受け取りました。",
		"requestData": map[string]string{
			"Name":     name,
			"Email":    email,
			"Password": password,
		},
	}
	/*
		c.Data["json"] = map[string]string{
			"status":"成功",
			"message":"フォーム画面からデータを受け取りました。",
		}
	*/
	//パスワードのハッシュ化
	generatedHashedPassword, error := crypto.GeneratedPasswordHashed(password)

	if error != nil {
		fmt.Println("パスワードを暗号化中にエラーが発生しました。", error)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "パスワードのハッシュ化に失敗しました。",
		}
		c.ServeJSON()
		return
	}
	//ORMを取得する
	o := orm.NewOrm()
	//モデルTodoのインスタンスを生成
	var registerUser models.Register //var todo models.Todo
	//トランザクション開始
	tx, err := o.Begin()

	if err != nil {
		fmt.Println("トランザクション開始エラー", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションの開始に失敗しました。",
		}
	}

	registerUser.Name = name
	registerUser.Email = email
	registerUser.Password = generatedHashedPassword
	registerUser.CreatedAt = time.Now()

	_, err = tx.Insert(&registerUser)
	if err != nil {
		tx.Rollback()
		fmt.Println("エラー発生", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザ登録に失敗しました。",
		}
		c.ServeJSON()
		return
	}
	//成功した場合コミット
	err = tx.Commit()

	if err != nil {
		fmt.Println("トランザクションのコミットに失敗しました。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "トランザクションのコミットに失敗しました。",
		}
		c.ServeJSON()
		return
	}

	//登録したデータを取得する
	var registeredUser models.Register
	err = o.QueryTable("register").Filter("Email", email).One(&registeredUser)

	if err != nil {
		fmt.Println("ユーザ情報の取得に失敗しました。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザー情報の取得に失敗しました。",
		}
		c.ServeJSON()
		return
	}

	fmt.Println("登録されたユーザは、", registeredUser.Name)
	fmt.Println("登録されたEmailは、", registeredUser.Email)

	//成功時のレスポンス
	c.Data["json"] = map[string]interface{}{
		"status":  "成功",
		"message": "ユーザーが正常に登録されました",
		"requestData": map[string]string{
			"Name":  registeredUser.Name,
			"Email": registeredUser.Email,
		},
	}

	//JSONデータを返却する
	c.ServeJSON()
}

func (c *LoginController) Post() {

	//フォームデータを取得する
	email := c.GetString("email")
	password := c.GetString("password")

	fmt.Println("ログイン画面から渡されたメールアドレスは、", email)
	fmt.Println("ログイン画面から渡されたパスワードは、", password)

	//ORMを定義する
	o := orm.NewOrm()

	//登録済みのデータを取得する
	var targetUser models.Register
	// `err` を宣言
	err := o.QueryTable("register").Filter("Email", email).One(&targetUser)

	if err != nil {
		fmt.Println("ユーザ情報を取得できませんでした。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザ情報をしゅとくできませんでした。",
		}
		c.ServeJSON()
		return
	}
	fmt.Println("ここまで1", err)
	err = crypto.ComparedHashedPassword(targetUser.Password, password)
	fmt.Println("ここまで2", err)
	if err != nil {
		fmt.Println("パスワードが一致しませんでした。", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "パスワードが一致しませんでした。",
		}
		c.ServeJSON()
		return
	}
	fmt.Println("ここまで3")
	c.Data["json"] = map[string]string{
		"status":  "成功",
		"message": "ログインに成功しました。",
	}
	c.ServeJSON()
}

func (c *TodoCreateController) Get() {
	//fmt.Println("TodoCreateControllerに来た")
	c.TplName = "mytodo/addTodo.tpl" //c.TplName = "mytodo/addTodo.tpl"
	//fmt.Println("TodoCreateControllerに来たよ")
}

// タスク登録用コントローラ
func (c *TodoAddController) Post() {
	//フォームデータから入力情報を取得する
	task := c.GetString("myTask")
	taskDescription := c.GetString("myTaskDescription")
	taskPriority := c.GetString("myTaskPriority")

	fmt.Println("フォーム画面から取得したタスク名は、", task)
	fmt.Println("フォーム画面から取得したタスク説明は、", taskDescription)
	fmt.Println("フォーム画面から取得したタスク画像は、は、", taskPriority)

	//ファイルをアップロード
	f, h, err := c.GetFile("myTaskImage")
	if err != nil {
		fmt.Println("ファイルエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像アップロード失敗")
		return
	}

	defer f.Close()
	// 画像を保存するパスを指定
	uploadPath := "static/upload/"
	err = os.MkdirAll(uploadPath, os.ModePerm) // ディレクトリがない場合に作成

	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存失敗")
		return
	}

	// ファイル名を表示
	//fmt.Println("ファイル名は", fileName)

	// 画像ファイル名を取得して保存
	fileName := h.Filename
	savePath := filepath.Join(uploadPath, fileName)

	// ファイルを保存
	outFile, err := os.Create(savePath)
	if err != nil {
		fmt.Println("ファイル保存エラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("画像保存に失敗しました")
		return
	}

	defer outFile.Close()

	// アップロードされたファイルの内容を保存
	_, err = f.Seek(0, 0) // ファイルの最初に戻す
	if err != nil {
		fmt.Println("ファイル読み込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル読み込みに失敗しました")
		return
	}

	_, err = outFile.ReadFrom(f)
	if err != nil {
		fmt.Println("ファイル書き込みエラー", err)
		c.Ctx.ResponseWriter.WriteHeader(500)
		c.Ctx.WriteString("ファイル書き込みに失敗しました")
		return
	}

	// ファイル名を表示
	fmt.Println("ファイル名は", fileName)

	// 成功レスポンスを返す
	c.Ctx.ResponseWriter.WriteHeader(200)
	c.Ctx.WriteString("タスク登録が成功しました")
	//c.SaveFile("uploadname", "static/upload/"+h.Filename)
}
```

さいごにルーティング設定です。

```router/router.go
package routers

import (
	"my-first-beego-project/controllers"

	"github.com/astaxie/beego"
)

func init() {
	beego.Router("/", &controllers.MainController{})
	//追加
	beego.Router("/sub", &controllers.SubController{})
	beego.Router("/:id", &controllers.ThirdController{})
	beego.Router("/controllers/user", &controllers.UserController{})
	beego.Router("/login", &controllers.LoginController{})
	beego.Router("/controllers/login", &controllers.LoginController{})
	beego.Router("/mytodo/addTodo", &controllers.TodoCreateController{})
	beego.Router("/controllers/todoAdd", &controllers.TodoAddController{})
}

```

以上です。