[GCP] GCEにGo Revelの環境を構築してデプロイ

### 環境構築
1.ローカルにGCloudをインストール
$ curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-363.0.0-linux-x86_64.tar.gz
$ tar -zxvf google-cloud-sdk-363.0.0-linux-x86_64.tar.gz
$ ./google-cloud-sdk/install.sh
$ ./google-cloud-sdk/bin/gcloud init
Your Google Cloud SDK is configured and ready to use!
$ pwd
/home/vagrant/gcp/

$ cd ~
$ sudo vi .bash_profile

source /home/vagrant/gcp/gcpgoogle-cloud-sdk/completion.bash.inc
source /home/vagrant/gcp/gcpgoogle-cloud-sdk/path.bash.inc
$ source ~/.bashrc

2.GCPでGCEの作成
Region: asia-northeast1(Tokyo)
Zone: asia-northeast1-a
Machine Series: E2
Machine Type: e2-micro(2 vCPU, 1GB memory)
Boot disk: Ubuntu20.04LTS ※defaultだとDebianになっているので、 ubuntuに変更する
Access Scope: Allow default access
Firewall: Allow HTTP traffic
-> create
-> 設定した内容で作られているかinstancesのviewで確認できる

gcloudによる接続確認
$ gcloud compute ssh instance-3

3.GCE(ubuntu20.04)にGoのインストール
$ sudo apt install golang-go
$ sudo apt-get install –reinstall ca-certificates
$ git config –global http.sslverify false
$ go get github.com/revel/revel
$ go get github.com/revel/cmd/revel
$ go get github.com/cbonello/revel-csrf
$ go get github.com/vansante/go-ffprobe
$ go get github.com/aws/aws-sdk-go
$ go get github.com/go-sql-driver/mysql

$ cd go/src/github.com
$ mkdir me

4.GCEにデプロイ
$ gcloud compute scp –recurse go/src/github.com/me/prd instance-3:/home/vagrant/go/src/github.com/me –zone asia-northeast1-a

5.GCEにmysqlインストール
$ sudo apt update
$ sudo apt install mysql-server
$ sudo mysql –defaults-file=/etc/mysql/debian.cnf
mysql> ALTER USER ‘root’@’localhost’ IDENTIFIED WITH mysql_native_password BY ‘${new password}’;
$ mysql -u root -p
mysql> create database test;
mysql> use test;

create table users(
id int primary key auto_increment,
name varchar(255) unique,
email varchar(255),
password varchar(255),
filepath varchar(255),
message text
);
insert user
mysql> insert into users(name, password) values (‘user1’, ‘5f4dcc3b5aa765d61d8327deb882cf99’);

sudo vi /home/vagrant/go/src/github.com/me/prd/conf/app.conf
http.port = 80

firewall設定
GCPホーム > ネットワーキング > VPCネットワーク > ファイアウォールルール

項目 入力例
名前 default-allow-9000
説明 Allow 9000 from anywhere
ログ オフ
ネットワーク default
優先度 1000
送信 / 受信 上り
一致したときのアクション 許可
ターゲット タグ use-9000
IP 範囲 0.0.0.0/0
プロトコルとポート tcp:9000

GCE
GCPホーム > コンピューティング > Compute Engine > VMインスタンス
ファイアウォール ネットワークタグ に先ほどのターゲットタグである「use-9000」追記

/home/vagrant/go/bin/revel run -a prd
${externalIP}:9000 で動作確認

ぎゃああああああああああああああああああああああああああああ

[Go Revel] Paginationを実装したい2

### previous, nextを書いていく
app.go

	var start, end, previous, next int
	
	// p はcurrentのint
	previous = p - 1
	next = p + 1

	return c.Render(content, previous, next)

html

    <nav class="pagination" role="navigation" aria-label="pagination">
      <a href="/paging?page={{.previous}}" class="pagination-previous">Previous</a>
      <a href="/paging?page={{.next}}" class="pagination-next">Next page</a>
    </nav>

ただし、previousとnextは、currentが最初もしくは最後のページの場合、非表示にする
app.go

	current = p
	previous = p - 1
	next = p + 1
    <nav class="pagination" role="navigation" aria-label="pagination">
      {{if ne .current 1}}
      <a href="/paging?page={{.previous}}" class="pagination-previous is-left">Previous</a>
      {{end}}
      <a href="/paging?page={{.next}}" class="pagination-next is-right">Next page</a>
    </nav>

うわああああああああああ、条件分岐が永遠増えていく…

これは次行こう

[Go Revel] Paginationを実装したい1

1. まずgetパラメータでpageの値を取得する
string型での取得

	var page int
	var s string=c.Params.Get("page")
	page, _ = strconv.Atoi(s)
	fmt.Println(page)

http://192.168.34.10:9000/paging?page=1
-> 1

2. ページのスタートと終わりの値を計算
例えば、1ページ5つの値のみを表示するとする場合、l(limit)=5として、startとendの値を求めます。

	var l int = 5
	var start, end int
	start = (p - 1) * l
	end = start + l
	fmt.Println(start)
	fmt.Println(end)

http://192.168.34.10:9000/paging?page=2
2 // 2ページ目
5 // start
10 // end

3. 配列からスライス(要素を取り出す)

	text := [15][2]string{
		{"1","content"},
		{"2","content"},
		{"3","content"},
		{"4","content"},
		{"5","content"},
		{"6","content"},
		{"7","content"},
		{"8","content"},
		{"9","content"},
		{"10","content"},
		{"11","content"},
		{"12","content"},
		{"13","content"},
		{"14","content"},
		{"15","content"},
	}
	fmt.Println(text[start:end])

http://192.168.34.10:9000/paging?page=2
-> [[6 content] [7 content] [8 content] [9 content] [10 content]]

4.全部をつなげて、templeteに出力

func (c App) Paging() revel.Result {

	var p int
	var s string=c.Params.Get("page")
	p, _ = strconv.Atoi(s)
	fmt.Println(p)

	var l int = 5
	var start, end int
	start = (p - 1) * l
	end = start + l
	fmt.Println(start)
	fmt.Println(end)

	text := [15][2]string{
		{"1","content"},
		{"2","content"},
		{"3","content"},
		{"4","content"},
		{"5","content"},
		{"6","content"},
		{"7","content"},
		{"8","content"},
		{"9","content"},
		{"10","content"},
		{"11","content"},
		{"12","content"},
		{"13","content"},
		{"14","content"},
		{"15","content"},

	}
	fmt.Println(text[start:end])
	content := text[start:end]
	return c.Render(content)
}

html側

    <h1>paging</h1>
    <ul>
    {{range .content}}
    <li>{{ index . 0 }} {{.}}</li>
    {{end}}
    </ul>

問題は、view側のページネーションのbuttonをどう実装するか…
0から作るの大変だな..

[Go言語]Revelで連想配列の値をrangeで表示する

app.go

func (c App) Paging() revel.Result {

	text := [3][2]string{
		{"20211113","2021/11/13"},
		{"20211112","2021/11/12"},
		{"20211111","2021/11/11"},
	}
	fmt.Println(text)
	fmt.Println(text[0][1])
	return c.Render(text)
}

views
L indexを使う

    <ul>
    {{range .text}}
    <li>{{ index . 0 }} {{.}}</li>
    {{end}}
    </ul>

ウヒョーーーーーー

.[0] で表示できないので、かなり時間がかかりました。

[Go Revel] S3からファイルをDownloadする

まずHTMLとroutingを作ります。

    <button type=“button” onclick="location.href='/test/download'">download</button>
GET		/test 				  App.Test
GET     /test/download		  App.Download

続いてaws-sdk-goをget
$ go get -u github.com/aws/aws-sdk-go

最後にcontroller
app.go
L ファイルのリターンはc.RenderFileName(“${file_path}”, revel.Attachment)

import (
	"github.com/revel/revel"
	"os"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
func (c App) Download() revel.Result {


	creds := credentials.NewStaticCredentials("${AWS_ACCESS_KEY_ID}","${AWS_SECRET_ACCESS_KEY}","")
	sess := session.Must(session.NewSession(&aws.Config{
		Credentials: creds,
		Region: aws.String("ap-northeast-1"),
	}))

	f, err := os.Create("public/tmp/sample.json")
	if err != nil {
		log.Fatal(err)
	}

	bucketName := "${bucketName}"
	objectKey := "${file}"

	downloader := s3manager.NewDownloader(sess)
	n, err := downloader.Download(f, &s3.GetObjectInput{
		Bucket: aws.String(bucketName),
		Key: aws.String(objectKey),
	})

	if err != nil {
		log.Fatal(err)
	}
	log.Printf("DownloadedSize: %d byte", n)


	return c.RenderFileName("public/tmp/sample.json", revel.Attachment)
}

動作テスト

OKOK, これで議事録機能はできたかな
ファイルパスのところを、/public/tmp/sample.json って書いてハマってしまったorz
さあ続いてKVS

[Go Revel] ランダムな文字列作成

app.go

import (
	"github.com/revel/revel"
	"app/app/models"
	"fmt"
	_ "image/jpeg"
	"io/ioutil"
	"os"
	"net/smtp"
	"crypto/rand"
	"errors"
)

func (c App) Rand() revel.Result {

	random, _ := MakeRandomStr(8)
	fmt.Println(random)
	return c.Render()
}

func MakeRandomStr(digit uint32)(string, error){
	const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	b := make([]byte, digit)
	if _, err := rand.Read(b); err != nil {
		return "", errors.New("unexpected error...")
	}

	var result string
	for _, v := range b {
		result += string(letters[int(v)%len(letters)])
	}
	return result, nil
}

dJdJvsWf

OK, これでPasswordを作成する

[Go Revel] Mailtrapによるメール送信

gmailのsmtpで送信する場合と書き方はほぼ同じ

func (c App) Mail() revel.Result {

	name := "hpscript"
	email := "test@hpscript.com"
	comments := "桜木町の夜景は良いですね"

	auth := smtp.PlainAuth (
		"",
		"hogehoge", // Mailtrap Username
		"fugafuga", // Mailtrap Password
		"smtp.mailtrap.io",
	)

	msg := []byte("From: info@hpscript.com\r\n" +
		"To: " +email + "\r\n" +
		"Subject: SMTPテスト\r\n" +
		"\r\n" +
		"名前: " + name + "\r\n" +
		"メールアドレス" + email + "\r\n" +
		"問い合わせ内容: " + comments + "\r\n")

	err := smtp.SendMail(
		"smtp.mailtrap.io:25",
		auth,
		"info@hpscrpt.com",
		[]string{email}, // 宛先
		msg,
	)

	if err != nil {
		fmt.Println(err)
	}
	return c.Render()
}

OKOK

[Go Revel] メール送信の実装方法

まずメール送信用のルートを作成

GET		/mail				  App.Mail

app.go
– net/smtpをimport
– パスワードはアプリ固有のパスワード

import (
	"github.com/revel/revel"
	"net/smtp"
)
func (c App) Mail() revel.Result {

	name := "hpscript"
	email := "test@hpscript.com"
	comments := "桜木町の夜景は良いですね"

	auth := smtp.PlainAuth (
		"",
		"myaddress@gmail.com", // gmailのアドレス
		"hogehoge", // アプリ固有のパスワード
		"smtp.gmail.com",
	)

	msg := []byte("From: info@hpscript.com\r\n" +
		"To: info@hpscript.com\r\n" +
		"Subject: SMTPテスト\r\n" +
		"\r\n" +
		"名前: " + name + "\r\n" +
		"メールアドレス" + email + "\r\n" +
		"問い合わせ内容: " + comments + "\r\n")

	err := smtp.SendMail(
		"smtp.gmail.com:587",
		auth,
		"info@hpscrpt.com",
		[]string{"fugafuga@hotmail.com"}, // 宛先
		msg,
	)

	if err != nil {
		fmt.Println(err)
	}
	return c.Render()
}

これmailtrapでも送れそうだな

[Go Revel] MySQLのuniqueなvalidationでredirect

ユーザのsigninの際に、既に同じユーザ名の登録があれば、登録できないようにしたい。

mysql側ではnameにUnique keyをつけている

mysql> describe users;
+———-+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+————–+——+—–+———+—————-+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | UNI | NULL | |
| email | varchar(255) | YES | | NULL | |
| password | varchar(255) | YES | | NULL | |
| filepath | varchar(255) | YES | | NULL | |
| message | text | YES | | NULL | |
+———-+————–+——+—–+———+—————-+
6 rows in set (0.01 sec)

これだと、実際に同じnameで入力があった場合、mysql側にはinsertされないが、
ページがそのまま遷移してしまう。
そのため、同じnameの入力があった場合は、入力画面に戻るようにしたい

revelでc.Validation.${} でunique判定が見当たらないので、
実際にselectして、処理する

	result := []models.Users{}
	DB.Where("name = ?", name).First(&result)


	if len(result) != 0 {
		c.Flash.Error("Same username is registered!")
		c.Validation.Keep()
		c.FlashParams()
		return c.Redirect(App.Signin)
	}
	
	hashPassword := getMD5Hash(password)
	DB.Create(&models.Users{
		Name: name,
		Email: email,
		Password: hashPassword,
	})
	
	return c.Render()

これ2時間ぐらいかかった…
go revelとか、documentなさすぎやろ😖😖😖😖

[Go Revel] Templateで else ifを使う

html
– uploadファイルがあればuploadファイル、元からセットされてらファイルがあればその画像、なければplaceholdeの画像を表示する

              {{if .filepath}}
               <img src="{{ .filepath }}" width="150px" height="150px" class="img-icon preview1">
              {{else if .set_filepath}}
              <img src="{{ .set_filepath }}" width="150px" height="150px" class="img-icon preview1">
              {{else}}
              <img src="http://placehold.jp/150x150.png" class="img-icon">
              {{end}}

普通にif, else ifで実装できますね。
controllerがそこそこ複雑になってきた。
djangoもそうだが、controllerを1ファイルでまとめると、縦に長くなるので、controllerを分けたいという欲求が少なからずある