Skip to content

Commit

Permalink
Merge pull request #5 from showwin/add_golang
Browse files Browse the repository at this point in the history
Add Go implementation
  • Loading branch information
showwin authored Jun 17, 2018
2 parents c810a19 + daee5d0 commit 329fce7
Show file tree
Hide file tree
Showing 18 changed files with 607 additions and 9 deletions.
9 changes: 9 additions & 0 deletions Dockerfile_app
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ RUN git clone https://github.com/pyenv/pyenv.git ~/.pyenv && \
pyenv install 3.6.5 && pyenv global 3.6.5 && \
cd && curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python get-pip.py && rm get-pip.py

# Go のインストール
RUN sudo wget https://dl.google.com/go/go1.10.2.linux-amd64.tar.gz && \
sudo tar -C /usr/local -xzf go1.10.2.linux-amd64.tar.gz && \
sudo rm go1.10.2.linux-amd64.tar.gz
ENV PATH $PATH:/usr/local/go/bin
ENV GOROOT /usr/local/go
ENV GOPATH $HOME/.local/go
ENV PATH $PATH:$GOROOT/bin

# アプリケーション
RUN mkdir /home/ishocon/data /home/ishocon/webapp
COPY admin/ishocon2.dump.tar.bz2 /home/ishocon/data/ishocon2.dump.tar.bz2
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ ISHOCONとは `Iikanjina SHOwwin CONtest` の略で、[ISUCON](http://isucon.net

## 問題詳細
* マニュアル: [ISHOCON2マニュアル](https://github.com/showwin/ISHOCON2/blob/master/doc/manual.md)
* アプリケーションAMI: `ami-fcba6b9d`
* アプリケーションAMI: `ami-63a8601c`
* ベンチマーカーAMI: `ami-eb9c4d8a`
* インスタンスタイプ: `c4.large` (アプリ、ベンチ共に)
* 参考実装言語: Ruby, Python
* 参考実装言語: Ruby, Python, Go

* AWSではなく手元で実行したい場合には [Docker を使ってローカルで環境を整える](https://github.com/showwin/ISHOCON2/blob/master/doc/local_manual.md) をご覧ください。

Expand Down
1 change: 0 additions & 1 deletion doc/local_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
```
$ git clone git@github.com:showwin/ISHOCON2.git
$ cd ISHOCON2
$ docker-compose build
$ docker-compose up
# app_1 と bench_1 のログに 'setup completed.' と出たら起動完了
```
Expand Down
9 changes: 9 additions & 0 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ $ cd ~/webapp/python
$ uwsgi --ini app.ini
```

#### Go の場合

```
$ cd ~/webapp/go
$ go get -t -d -v ./...
$ go build -o webapp *.go
$ ./webapp
```

これでブラウザからアプリケーションが見れるようになるので、IPアドレスにアクセスしてみましょう。
HTTPS でのみアクセスできることに注意してください。ブラウザによっては証明書のエラーが表示されますが、無視してページを表示してください。

Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3.0'
services:
app:
image: showwin/ishocon2_app:0.9.1
image: showwin/ishocon2_app:1.1.0
command: /docker/start_app.sh
volumes:
- storage_app:/var/lib/mysql
Expand All @@ -15,7 +15,7 @@ services:
- /var/lib/mysql

bench:
image: showwin/ishocon2_bench:0.9.1
image: showwin/ishocon2_bench:1.1.0
command: /docker/start_bench.sh
volumes:
- storage_bench:/var/lib/mysql
Expand Down
4 changes: 2 additions & 2 deletions docker/start_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ sudo service nginx start
sudo service mysql start
sudo chown -R mysql:mysql /var/lib/mysql /var/run/mysqld
sudo service mysql start # 正しく起動
sudo mysql -u root -pishocon -e 'CREATE DATABASE ishocon2;' && \
sudo mysql -u root -pishocon -e "CREATE USER ishocon IDENTIFIED BY 'ishocon';" && \
sudo mysql -u root -pishocon -e 'CREATE DATABASE IF NOT EXISTS ishocon2;' && \
sudo mysql -u root -pishocon -e "CREATE USER IF NOT EXISTS ishocon IDENTIFIED BY 'ishocon';" && \
sudo mysql -u root -pishocon -e 'GRANT ALL ON *.* TO ishocon;' && \
cd ~/data && tar -jxvf ishocon2.dump.tar.bz2 && sudo mysql -u root -pishocon ishocon2 < ~/data/ishocon2.dump

Expand Down
4 changes: 2 additions & 2 deletions docker/start_bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
service mysql start # なぜか失敗する(調査中)
chown -R mysql:mysql /var/lib/mysql /var/run/mysqld
service mysql start # 正しく起動
mysql -u root -pishocon -e 'CREATE DATABASE ishocon2;' && \
mysql -u root -pishocon -e "CREATE USER ishocon IDENTIFIED BY 'ishocon';" && \
mysql -u root -pishocon -e 'CREATE DATABASE IF NOT EXISTS ishocon2;' && \
mysql -u root -pishocon -e "CREATE USER IF NOT EXISTS ishocon IDENTIFIED BY 'ishocon';" && \
mysql -u root -pishocon -e 'GRANT ALL ON *.* TO ishocon;' && \
cd /admin && tar -jxvf ishocon2.dump.tar.bz2 && mysql -u root -pishocon ishocon2 < /admin/ishocon2.dump

Expand Down
116 changes: 116 additions & 0 deletions webapp/go/candidate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package main

// Candidate Model
type Candidate struct {
ID int
Name string
PoliticalParty string
Sex string
}

// CandidateElectionResult type
type CandidateElectionResult struct {
ID int
Name string
PoliticalParty string
Sex string
VoteCount int
}

// PartyElectionResult type
type PartyElectionResult struct {
PoliticalParty string
VoteCount int
}

func getAllCandidate() (candidates []Candidate) {
rows, err := db.Query("SELECT * FROM candidates")
if err != nil {
panic(err.Error())
}
defer rows.Close()

for rows.Next() {
c := Candidate{}
err = rows.Scan(&c.ID, &c.Name, &c.PoliticalParty, &c.Sex)
if err != nil {
panic(err.Error())
}
candidates = append(candidates, c)
}
return
}

func getCandidate(candidateID int) (c Candidate, err error) {
row := db.QueryRow("SELECT * FROM candidates WHERE id = ?", candidateID)
err = row.Scan(&c.ID, &c.Name, &c.PoliticalParty, &c.Sex)
return
}

func getCandidateByName(name string) (c Candidate, err error) {
row := db.QueryRow("SELECT * FROM candidates WHERE name = ?", name)
err = row.Scan(&c.ID, &c.Name, &c.PoliticalParty, &c.Sex)
return
}

func getAllPartyName() (partyNames []string) {
rows, err := db.Query("SELECT political_party FROM candidates GROUP BY political_party")
if err != nil {
panic(err.Error())
}
defer rows.Close()

for rows.Next() {
var name string
err = rows.Scan(&name)
if err != nil {
panic(err.Error())
}
partyNames = append(partyNames, name)
}
return
}

func getCandidatesByPoliticalParty(party string) (candidates []Candidate) {
rows, err := db.Query("SELECT * FROM candidates WHERE political_party = ?", party)
if err != nil {
panic(err.Error())
}
defer rows.Close()

for rows.Next() {
c := Candidate{}
err = rows.Scan(&c.ID, &c.Name, &c.PoliticalParty, &c.Sex)
if err != nil {
panic(err.Error())
}
candidates = append(candidates, c)
}
return
}

func getElectionResult() (result []CandidateElectionResult) {
rows, err := db.Query(`
SELECT c.id, c.name, c.political_party, c.sex, IFNULL(v.count, 0)
FROM candidates AS c
LEFT OUTER JOIN
(SELECT candidate_id, COUNT(*) AS count
FROM votes
GROUP BY candidate_id) AS v
ON c.id = v.candidate_id
ORDER BY v.count DESC`)
if err != nil {
panic(err.Error())
}
defer rows.Close()

for rows.Next() {
r := CandidateElectionResult{}
err = rows.Scan(&r.ID, &r.Name, &r.PoliticalParty, &r.Sex, &r.VoteCount)
if err != nil {
panic(err.Error())
}
result = append(result, r)
}
return
}
189 changes: 189 additions & 0 deletions webapp/go/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"database/sql"
"html/template"
"net/http"
"os"
"sort"
"strconv"

"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/contrib/static"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}

func main() {
// database setting
user := getEnv("ISHOCON2_DB_USER", "ishocon")
pass := getEnv("ISHOCON2_DB_PASSWORD", "ishocon")
dbname := getEnv("ISHOCON2_DB_NAME", "ishocon2")
db, _ = sql.Open("mysql", user+":"+pass+"@/"+dbname)
db.SetMaxIdleConns(5)

gin.SetMode(gin.DebugMode)
r := gin.Default()
r.Use(static.Serve("/css", static.LocalFile("public/css", true)))
layout := "templates/layout.tmpl"

// session store
store := sessions.NewCookieStore([]byte("mysession"))
store.Options(sessions.Options{HttpOnly: true})
r.Use(sessions.Sessions("showwin_happy", store))

// GET /
r.GET("/", func(c *gin.Context) {
electionResults := getElectionResult()

// 上位10人と最下位のみ表示
tmp := make([]CandidateElectionResult, len(electionResults))
copy(tmp, electionResults)
candidates := tmp[:10]
candidates = append(candidates, tmp[len(tmp)-1])

partyNames := getAllPartyName()
partyResultMap := map[string]int{}
for _, name := range partyNames {
partyResultMap[name] = 0
}
for _, r := range electionResults {
partyResultMap[r.PoliticalParty] += r.VoteCount
}
partyResults := []PartyElectionResult{}
for name, count := range partyResultMap {
r := PartyElectionResult{}
r.PoliticalParty = name
r.VoteCount = count
partyResults = append(partyResults, r)
}
// 投票数でソート
sort.Slice(partyResults, func(i, j int) bool { return partyResults[i].VoteCount > partyResults[j].VoteCount })

sexRatio := map[string]int{
"men": 0,
"women": 0,
}
for _, r := range electionResults {
if r.Sex == "男" {
sexRatio["men"] += r.VoteCount
} else if r.Sex == "女" {
sexRatio["women"] += r.VoteCount
}
}

funcs := template.FuncMap{"indexPlus1": func(i int) int { return i + 1 }}
r.SetHTMLTemplate(template.Must(template.New("main").Funcs(funcs).ParseFiles(layout, "templates/index.tmpl")))
c.HTML(http.StatusOK, "base", gin.H{
"candidates": candidates,
"parties": partyResults,
"sexRatio": sexRatio,
})
})

// GET /candidates/:candidateID(int)
r.GET("/candidates/:candidateID", func(c *gin.Context) {
candidateID, _ := strconv.Atoi(c.Param("candidateID"))
candidate, err := getCandidate(candidateID)
if err != nil {
c.Redirect(http.StatusFound, "/")
}
votes := getVoteCountByCandidateID(candidateID)
candidateIDs := []int{candidateID}
keywords := getVoiceOfSupporter(candidateIDs)

r.SetHTMLTemplate(template.Must(template.ParseFiles(layout, "templates/candidate.tmpl")))
c.HTML(http.StatusOK, "base", gin.H{
"candidate": candidate,
"votes": votes,
"keywords": keywords,
})
})

// GET /political_parties/:name(string)
r.GET("/political_parties/:name", func(c *gin.Context) {
partyName := c.Param("name")
var votes int
electionResults := getElectionResult()
for _, r := range electionResults {
if r.PoliticalParty == partyName {
votes += r.VoteCount
}
}

candidates := getCandidatesByPoliticalParty(partyName)
candidateIDs := []int{}
for _, c := range candidates {
candidateIDs = append(candidateIDs, c.ID)
}
keywords := getVoiceOfSupporter(candidateIDs)

r.SetHTMLTemplate(template.Must(template.ParseFiles(layout, "templates/political_party.tmpl")))
c.HTML(http.StatusOK, "base", gin.H{
"politicalParty": partyName,
"votes": votes,
"candidates": candidates,
"keywords": keywords,
})
})

// GET /vote
r.GET("/vote", func(c *gin.Context) {
candidates := getAllCandidate()

r.SetHTMLTemplate(template.Must(template.ParseFiles(layout, "templates/vote.tmpl")))
c.HTML(http.StatusOK, "base", gin.H{
"candidates": candidates,
"message": "",
})
})

// POST /vote
r.POST("/vote", func(c *gin.Context) {
user, userErr := getUser(c.PostForm("name"), c.PostForm("address"), c.PostForm("mynumber"))
candidate, cndErr := getCandidateByName(c.PostForm("candidate"))
votedCount := getUserVotedCount(user.ID)
candidates := getAllCandidate()
voteCount, _ := strconv.Atoi(c.PostForm("vote_count"))

var message string
r.SetHTMLTemplate(template.Must(template.ParseFiles(layout, "templates/vote.tmpl")))
if userErr != nil {
message = "個人情報に誤りがあります"
} else if user.Votes < voteCount+votedCount {
message = "投票数が上限を超えています"
} else if c.PostForm("candidate") == "" {
message = "候補者を記入してください"
} else if cndErr != nil {
message = "候補者を正しく記入してください"
} else if c.PostForm("keyword") == "" {
message = "投票理由を記入してください"
} else {
for i := 1; i <= voteCount; i++ {
createVote(user.ID, candidate.ID, c.PostForm("keyword"))
}
message = "投票に成功しました"
}
c.HTML(http.StatusOK, "base", gin.H{
"candidates": candidates,
"message": message,
})
})

r.GET("/initialize", func(c *gin.Context) {
db.Exec("DELETE FROM votes")

c.String(http.StatusOK, "Finish")
})

r.Run(":8080")
}
Loading

0 comments on commit 329fce7

Please sign in to comment.