【Go言語】WebフレームワークBeegoでユーザ情報をハッシュ化して登録する方法

# サンプル
#### 画面でユーザ情報を入力して送信ボタンをクリックします。
![Beego登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/a456b860-365a-27aa-cba9-f5c579da6c81.png)

#### データベースに登録されると`Email`をキーにして単一のユーザ情報を画面側に返却します。
![Beego登録デバッグ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/edf01bc4-de9e-7fe9-3000-8908a372a87b.png)

#### PostgreSQLを確認するとちゃんと登録されています。
![Beego登録PostgreSQL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/d7cfc8e9-37f8-0b1d-7e01-4849992c35dd.png)

このような画面を作っていきます。

# この記事で会う使う技術
| 技術                     | パッケージ等        |
|:-----------             |------------:|
| トランザクション         | −        |
| ORMマッパー              | −        |
| パスワードハッシュ化       | golang.org/x/crypto/bcrypt        |

パスワードハッシュ化に関するパッケージはこちら↓

https://pkg.go.dev/golang.org/x/crypto/bcrypt




# Beegoのバージョン
| Beego | バージョン        |
|:-----------|------------:|
| Beego       | 2.0        |

# ディレクトリ構造
パスワードをハッシュ化するクラス`crypt.go`を追加しました。
そのほかに、`todo.go`クラスや`default.go`クラスも変更しました。

詳しくは後述します。
```
My-First-Beego-Project
├─ conf
│  └─ app.conf
├─ controllers
│  └─ default.go
├─ crypto
│  └─ crypt.go
├─ models
│  └─ todo.go
└─ routers
   └─ router.go
static
├─ css
├─ img
├─ js
│  ├─ form.js
│  └─ reload.min.js
├─ lib
│  └─ boostrap
│     ├─ css
│     │  ├─ bootstrap-grid.min.css
│     │  └─ bootstrap.min.css
│     └─ js
│        ├─ bootstrap-bundle.min.js
│        └─ bootstrap.min.js
├─ jquery
│     └─ jquery.js
├─ views
│  ├─ index.tpl
│  ├─ subIndex.tpl
│  └─ thirdIndex.tpl
└─ main.go
```

# フロント側の画面
こちらは特に変えていませんが、載せておきます。

```views/thirdIndex.tpl
<!DOCTYPE>
<html>
    <head>
        <title>3つめのページ</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/form.js"></script>
    </head>
    <body>
        <h2>取得したURLクエリパラメータは：{{.ID}}</h2>
        <form>
            <div class="form-group">
                <label>名前</label>
                <input type="text" id="name" name="name" class="form-control border border-dark" required/>
            </div>
            <div class="form-group">
                <label>E-mail</label>
                <input type="email" id="email" name="email" class="form-control border border-dark" required/>
            </div>
            <div class="form-group">
                <label>パスワード</label>
                <input type="password" id="password" name="password" class="form-control border border-dark" required/>
            </div>
            <div class="mt-4">
                <button type="button" id="submitBtn" class="btn btn-primary">
                    送信
                </button>
            </div>
        </form>

        <div>
        送信したデータ
        <div id="requestedName"></div>
        <div id="requestedEmail"></div>
        <div id="requestedPassword"></div>
        </div>
    </body>
</html>
```

# サーバ側の画面
パスワードをハッシュするためのパッケージ`golang.org/x/crypto/bcrypt`をインストールしておきます。
```
go get golang.org/x/crypto/bcrypt
```

インストールしたら、`crypt.go`クラスを作成しておきます。
```crypt/crypt.go
package crypto

import (
	"golang.org/x/crypto/bcrypt"
)

// パスワードのハッシュ化処理
func GeneratedPasswordHashed(password string) (string, error) {
	hash, error := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	return string(hash), error
}

// 入力されたパスワードとデータベースのパスワードの比較
func ComparedHashedPassword(hasedPassword, password string) error {
	return bcrypt.CompareHashAndPassword([]byte(hasedPassword), []byte(password))
}

```

次にモデルクラスです。

```models/todo.go
package models // ここでパッケージ名を指定

import (
	"time"

	"github.com/beego/beego/v2/client/orm"
	_ "github.com/lib/pq" // PostgreSQL ドライバをインポート
)

type Register struct {
	Id        int64     `orm:"auto"`
	Name      string    `orm:"size(20)"`
	Email     string    `orm:"size(50)"`
	Password  string    `orm:"size(100)"`
	CreatedAt time.Time `orm:"auto_now;type(datetime)"`
}

func init() {
	// PostgreSQLのドライバ登録
	orm.RegisterDriver("postgres", orm.DRPostgres)

	// データベース接続設定
	orm.RegisterDataBase("default", "postgres", "user=postgres password=postgres host=localhost port=5432 dbname=beegoDb sslmode=disable")

	// モデルの登録
	orm.RegisterModel(new(Register)) // Todo

	// テーブルが存在しない場合に作成（強制的にマイグレーション）
	orm.RunSyncdb("default", false, true)
}

```

さいごに、`default.go`を変更します。

```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"
)

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
}

// 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 *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()
}

```

# `Beego ver2.0`でのトランザクションの方法

`Beego ver 2.0`でトランザクションをする方法は下記の手順になります。
#### `o := orm.NewOrm()`でOrmオブジェクトを作成する
```sample.go
o := orm.NewOrm()
```
#### `tx,err := o.Begin()`でトランザクションを開始する


:::note info
Beego v2のORM（beego/v2）では、トランザクション管理は少し異なり、o.Begin()を使う代わりにo.Begin()で返された<b>tx（トランザクション）</b>を使います。
`tx`を使ってInsert、Commit、Rollbackなどの操作を行います。
:::

```sample.go
tx, err := o.Begin()
```
#### DBに挿入したい`Model`インスタンスを作成する
```sample.go
var registerUser models.Register
```

#### データ更新が失敗した場合ロールバックを行う

```sample.go
	_, err = tx.Insert(&registerUser)
	if err != nil {
		tx.Rollback()
		fmt.Println("エラー発生", err)
		c.Data["json"] = map[string]string{
			"status":  "失敗",
			"message": "ユーザ登録に失敗しました。",
		}
		c.ServeJSON()
		return
	}
```

#### データ更新成功後はコミットする

```sample.go
err = tx.Commit()
```

# 登録したユーザ情報を画面側に渡す方法

`o.QueryTable("モデル名").Filter("カラム名", カラム値).One(&モデルポインタ)`を使ってデータを取得します。

:::note info
`o.QueryTable("モデル名").Filter("カラム名", カラム値).One(&モデルポインタ)` の引数として <font color="red"><b>ポインタ型</b></font>で渡します。

そのため、今回はOne() メソッドは、データを構造体にマッピングするためにポインタを必要とします。

ですので、<font color="red"><b>&</b></font>registeredUser とする必要があります。

変数errがQueryTableの実行結果でエラーが発生した場合、err != nilが評価され、そのエラーメッセージが表示されます。
:::

```sample.go
//登録したデータを取得する
var registeredUser models.Register
err = o.QueryTable("register").Filter("Email", email).One(&registeredUser)
```
この場合は、`email`をキーにしてデータベースから登録したユーザーの情報を取得します。





# 参考にしたサイト

https://zenn.dev/ajapa/articles/5b115f53e76f3a

https://qiita.com/ken0renaissance/items/7de8b7d0171058cbef5a

https://pkg.go.dev/github.com/ECacheFS/beego/orm#section-readme

https://engineers.weddingpark.co.jp/golang-revel/

https://qiita.com/muff1225/items/f660270694f29597df22

https://qiita.com/hatai/items/f31914f37dc6c53b2bce