サンプル
登録画面から入力項目を入力して登録ボタンをクリックします。
上記の機能を作っていきます。
ER図
モデルクラスはRegister
とTask
の2種類です。
Register
モデルには、ユーザ情報を、Task
モデルにはタスク情報を入れています。
登録済みのユーザのみがタスクを登録できるので、Register
モデルとTask
モデルは1対多の関係となります。
ディレクトリ構成
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
モデルクラスの修正
リレーショナルが1対多になるように、Task
モデルの中にRegister
モデルをポインタ型で保持しておくようにしました。
on_delete(do_nothing)
は、登録されたユーザー(Register)
が削除された場合にそのユーザーに関連するタスクが削除されないように指定しています。
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)"`
}
// 20250211追加
type Task struct {
TaskId int64 `orm:"auto"`
TaskName string `orm:"size(20)"`
TaskDescription string `orm:"size(100)"`
TaskPriority string `orm:"size(5)"`
TaskImage string `orm:"size(100)"`
Register *Register `orm:"rel(fk);on_delete(do_nothing)"` // 外部キーリレーション
}
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
// Taskモデルの登録
orm.RegisterModel(new(Task))
// テーブルが存在しない場合に作成(強制的にマイグレーション)
orm.RunSyncdb("default", false, true)
}
フロント側のコード
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>
<input type="hidden" id="userId" name="userId" value=""/>
</div>
<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(){
//Session StorageからユーザIDを取得する
const userId = sessionStorage.getItem("userId");
let inputValueInUserId = document.getElementById("userId");
inputValueInUserId.value = userId;
//一覧に戻るボタンをクリックしたときの処理
$('#backToTopPage').on('click',function(){
location.replace("/sub");
});
//登録ボタンをクリックしたときの処理
$('#todoCreate').on('click',function(e){
//POST通信するための設定
e.preventDefault();
//オプションタグの選択項目を取得
const selectedTaskPriority = document.getElementById("myTaskPriority");
//フォームデータの作成
let formData = new FormData();
formData.append('userId',$("#userId").val());
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("タスクの登録に失敗しました。");
}
});
});
});
サーバ側のコード
targetTask.Register
はmodels.Register型
のフィールドですが、これはポインタ型であり、初期化されていない状態ではnilになります。
nilの状態でtargetTask.Register.Id
にアクセスしようとすると、nilポインタの参照エラーが発生します。
解決方法:
targetTask.Register
を初期化する必要があります。
以下のように、targetTask.Register
に新しいmodels.Register
のインスタンスを代入してください。
// Registerを初期化
targetTask.Register = &models.Register{}
以上を踏まえたコードがこちらです↓
controllers/default.go
// タスク登録用コントローラ
func (c *TodoAddController) Post() {
//フォームデータから入力情報を取得する
userId := c.GetString("userId") //20250211
task := c.GetString("myTask")
taskDescription := c.GetString("myTaskDescription")
taskPriority := c.GetString("myTaskPriority")
fmt.Println("フォーム画面から取得したユーザIDは、", userId) //20250211
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)
//----Taskテーブルへ登録する処理----
//ORMを取得する
o := orm.NewOrm()
//Taskモデルのインスタンスを生成する
var targetTask models.Task
// Registerを初期化
targetTask.Register = &models.Register{}
//トランザクション開始
tx, err := o.Begin()
if err != nil {
fmt.Println("トランザクション開始エラー", err)
c.Data["json"] = map[string]string{
"status": "失敗",
"message": "トランザクションの開始に失敗しました。",
}
}
//ユーザIDをStringからInt64型へ変換する
numUserId, _ := strconv.ParseInt(userId, 10, 64)
fmt.Println("整数型に変換したIDは、", numUserId)
targetTask.Register.Id = numUserId //ユーザID
fmt.Println("登録したRegisterId", targetTask.Register.Id)
targetTask.TaskName = task
fmt.Println("登録したTaskName", targetTask.TaskName)
targetTask.TaskDescription = taskDescription
fmt.Println("登録したTaskName", targetTask.TaskDescription)
targetTask.TaskPriority = taskPriority
fmt.Println("登録したTaskName", targetTask.TaskPriority) //taskPriority
targetTask.TaskImage = "http://localhost:8080/upload/" + fileName //fileName
fmt.Println("登録したRegisterId", targetTask.Register.Id)
fmt.Println("登録したTaskName", targetTask.TaskName)
fmt.Println("登録したTaskName", targetTask.TaskDescription)
fmt.Println("登録したTaskName", targetTask.TaskPriority)
fmt.Println("登録したTaskName", targetTask.TaskImage)
_, err = tx.Insert(&targetTask)
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("トランザクションコミットに失敗しました。")
c.Data["json"] = map[string]string{
"status": "失敗",
"message": "トランザクションコミットに失敗しました。",
}
c.ServeJSON()
return
}
// 成功レスポンスを返す
c.Ctx.ResponseWriter.WriteHeader(200)
c.Ctx.WriteString("タスク登録が成功しました")
//c.SaveFile("uploadname", "static/upload/"+h.Filename)
}
全体のコードはこちら↓
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"
"strconv"
)
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(®isterUser)
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(®isteredUser)
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)
fmt.Println("データベースから取得したユーザ情報は、", targetUser.Id)
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]interface{}{
"status": "成功",
"message": "ログインに成功しました。",
"user": map[string]interface{}{
"id": targetUser.Id,
"name": targetUser.Name,
"email": targetUser.Email,
},
}
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() {
//フォームデータから入力情報を取得する
userId := c.GetString("userId") //20250211
task := c.GetString("myTask")
taskDescription := c.GetString("myTaskDescription")
taskPriority := c.GetString("myTaskPriority")
fmt.Println("フォーム画面から取得したユーザIDは、", userId) //20250211
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)
//----Taskテーブルへ登録する処理----
//ORMを取得する
o := orm.NewOrm()
//Taskモデルのインスタンスを生成する
var targetTask models.Task
// Registerを初期化
targetTask.Register = &models.Register{}
//トランザクション開始
tx, err := o.Begin()
if err != nil {
fmt.Println("トランザクション開始エラー", err)
c.Data["json"] = map[string]string{
"status": "失敗",
"message": "トランザクションの開始に失敗しました。",
}
}
//ユーザIDをStringからInt64型へ変換する
numUserId, _ := strconv.ParseInt(userId, 10, 64)
fmt.Println("整数型に変換したIDは、", numUserId)
targetTask.Register.Id = numUserId //ユーザID
fmt.Println("登録したRegisterId", targetTask.Register.Id)
targetTask.TaskName = task
fmt.Println("登録したTaskName", targetTask.TaskName)
targetTask.TaskDescription = taskDescription
fmt.Println("登録したTaskName", targetTask.TaskDescription)
targetTask.TaskPriority = taskPriority
fmt.Println("登録したTaskName", targetTask.TaskPriority) //taskPriority
targetTask.TaskImage = "http://localhost:8080/upload/" + fileName //fileName
fmt.Println("登録したRegisterId", targetTask.Register.Id)
fmt.Println("登録したTaskName", targetTask.TaskName)
fmt.Println("登録したTaskName", targetTask.TaskDescription)
fmt.Println("登録したTaskName", targetTask.TaskPriority)
fmt.Println("登録したTaskName", targetTask.TaskImage)
_, err = tx.Insert(&targetTask)
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("トランザクションコミットに失敗しました。")
c.Data["json"] = map[string]string{
"status": "失敗",
"message": "トランザクションコミットに失敗しました。",
}
c.ServeJSON()
return
}
// 成功レスポンスを返す
c.Ctx.ResponseWriter.WriteHeader(200)
c.Ctx.WriteString("タスク登録が成功しました")
//c.SaveFile("uploadname", "static/upload/"+h.Filename)
}