用户登录、发帖、回帖。
网上论坛相当于通过帖子(thread)进行对话的公告板。
由拥有特殊权限的版主(moderator)管理。
注册账号用户才能发帖和回帖,未注册账号用户只能查看帖子。
Web应用的工作流程:
(1)客户端向服务器发送HTTP请求;
(2)服务器处理HTTP请求;
(3)返回HTTP响应。
ChitChat的请求格式:
http://<servername>/<handlername>?<parameters>
http://<服务器名><处理器名>?<参数>
处理器名按层级划分,/thread/read。
应用参数以URL查询形式传递。
多路复用器(multiplexer),检查请求,并重定向至各处理器处理。
处理器解析信息,处理,将数据传递给模板引擎。
模板引擎将模板和数据生成返回给客户端的HTML。
ChitChat数据模型,包含4种数据结构,映射到关系型数据库。
package main
import (
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
files := http.FileServer(http.Dir(config.Static))
mux.Handle("/static/", http.StripPrefix("/static/", files))
mux.HandleFunc("/", index)
server := &http.Server{
Addr: "0.0.0.0:8080",
Handler: mux,
}
server.ListenAndServe()
}
http://localhost/static/css/bootstrap.min.css
映射为
<application root>/public/css/bootstrap.min.css
files := http.FileServer(http.Dir("/public"))
mux.Handle("/static/", http.StripPrefix("/static/", files))
package main
import (
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
files := http.FileServer(http.Dir(config.Static))
mux.Handle("/static/", http.StripPrefix("/static/", files))
mux.HandleFunc("/", index)
mux.HandleFunc("/err", err)
mux.HandleFunc("/login", login)
mux.HandleFunc("/logout", logout)
mux.HandleFunc("/signup", signup)
mux.HandleFunc("/signup_account", signupAccount)
mux.HandleFunc("/authenticate", authenticate)
mux.HandleFunc("/thread/new", newThread)
mux.HandleFunc("/thread/create", createThread)
mux.HandleFunc("/thread/post", postThread)
mux.HandleFunc("/thread/read", readThread)
server := &http.Server{
Addr: "0.0.0.0:8080",
Handler: mux,
}
server.ListenAndServe()
}
route_auth.go
// POST /authenticate
// Authenticate the user given the email and password
func authenticate(writer http.ResponseWriter, request *http.Request) {
err := request.ParseForm()
user, err := data.UserByEmail(request.PostFormValue("email"))
if err != nil {
danger(err, "Cannot find user")
}
if user.Password == data.Encrypt(request.PostFormValue("password")) {
session, err := user.CreateSession()
if err != nil {
danger(err, "Cannot create session")
}
cookie := http.Cookie{
Name: "_cookie",
Value: session.Uuid,
HttpOnly: true,
}
http.SetCookie(writer, &cookie)
http.Redirect(writer, request, "/", 302)
} else {
http.Redirect(writer, request, "/login", 302)
}
}
util.go
// Checks if the user is logged in and has a session, if not err is not nil
func session(writer http.ResponseWriter, request *http.Request) (sess data.Session, err error) {
cookie, err := request.Cookie("_cookie")
if err == nil {
sess = data.Session{Uuid: cookie.Value}
if ok, _ := sess.Check(); !ok {
err = errors.New("Invalid session")
}
}
return
}
util.go
func generateHTML(writer http.ResponseWriter, data interface{}, filenames ...string) {
var files []string
for _, file := range filenames {
files = append(files, fmt.Sprintf("templates/%s.html", file))
}
templates := template.Must(template.ParseFiles(files...))
templates.ExecuteTemplate(writer, "layout", data)
}
sudo apt-get install postgresql postgresql-contrib
//登入Postgres账号
sudo su postgres
//创建用户
createuser -interactive
//创建用户名命名的数据库
createdb USER_NAME
Postgres应用压缩包解压,Postgres.app文件放入Applications文件夹。
~/.profile或~/.bashrc文件添加路径
export PATH=$PATH:/Applications/Postgres.app/Contents/Versions/9.4/bin
https://www.enterprisedb.com/downloads/postgres-postgresql-downloads
setup.sql
drop table if exists posts;
drop table if exists threads;
drop table if exists sessions;
drop table if exists users;
create table users (
id serial primary key,
uuid varchar(64) not null unique,
name varchar(255),
email varchar(255) not null unique,
password varchar(255) not null,
created_at timestamp not null
);
create table sessions (
id serial primary key,
uuid varchar(64) not null unique,
email varchar(255),
user_id integer references users(id),
created_at timestamp not null
);
create table threads (
id serial primary key,
uuid varchar(64) not null unique,
topic text,
user_id integer references users(id),
created_at timestamp not null
);
create table posts (
id serial primary key,
uuid varchar(64) not null unique,
body text,
user_id integer references users(id),
thread_id integer references threads(id),
created_at timestamp not null
);
createdb -p 5433 -U postgres chitchat
psql -p 5433 -U postgres -d chitchat -f F:\study\go\gwp-master\Chapter_2_Go_ChitChat\chitchat\data\setup.sql
thread.go
type Thread struct {
Id int
Uuid string
Topic string
UserId int
CreatedAt time.Time
}
data.go
var Db *sql.DB
func init() {
var err error
Db, err = sql.Open("postgres", "host=localhost port=5433 user=postgres dbname=chitchat password=postgres sslmode=disable")
if err != nil {
log.Fatal(err)
}
err = Db.Ping()
if err != nil {
panic(err)
}
}
// starting up the server
server := &http.Server{
Addr: config.Address,
Handler: mux,
}
server.ListenAndServe()
(1)客户端向服务器发送请求;
(2)多路复用器收到请求,重定向至正确的处理器;
(3)处理器对请求进行处理;
(4)需要访问数据库的情况下,处理器使用一或多个数据结构(根据数据库中数据建模);
(5)处理器调用与数据结构有关函数或方法时,数据结构背后的模型与数据库连接并进行相应操作;
(6)请求处理完毕时,处理器调用模板引擎,有时会向模板引擎传递一些通过模型获取到的数据;
(7)模板引擎语法分析模板文件列表创建模板结构,与处理器传递数据合并生成最终HTML;
(8)生成HTML作为响应回传至客户端。
template/error.html
{{ define "content" }}
<p>{{ . }}</p>
{{ end }}
template/index.html
{{ define "content" }}
<h2><a href="/thread/new">Start a thread</a> or join one below!</h2>
{{ range . }}
<p><i>{{ .Topic }}</i> Started by {{ .User.Name }} - {{ .CreatedAtDate }} - {{ .NumReplies }} posts.</p>
<p><a href="/thread/read?id={{ .Uuid }}">Read more</a></p>
<br/>
{{ end }}
{{ end }}
template/layout.html
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<title>ChitChat</title>
</head>
<body>
{{ template "navbar" . }}
{{ template "content" . }}
</body>
</html>
{{ end }}
template/login.html
{{ define "content" }}
<form action="/authenticate" method="post">
<h2><i>ChitChat</i></h2>
<input type="email" name="email" placeholder="Email address" required autofocus>
<br/>
<input type="password" name="password" placeholder="Password" required>
<br/>
<button type="submit">Sign in</button>
<br/>
<a href="/signup">Sign up</a>
</form>
{{ end }}
template/login.layout.html
{{ define "layout" }}
<!DOCTYPE html>
<html>
<head>
<title>ChitChat</title>
</head>
<body>
{{ template "content" . }}
</body>
</html>
{{ end }}
template/new.thread.html
{{ define "content" }}
<form action="/thread/create" method="post">
<p>Start a new thread with the following topic</p>
<textarea name="topic" id="topic" placeholder="Thread topic here" rows="4"></textarea>
<br/>
<br/>
<button type="submit">Start this thread</button>
</form>
{{ end }}
template/private.navbar.html
{{ define "navbar" }}
<a href="/"><i>ChitChat</i></a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
{{ end }}
template/private.thread.html
{{ define "content" }}
<h2><i>{{ .Topic }}</i> Started by {{ .User.Name }} - {{ .CreatedAtDate }}</h2>
{{ range .Posts }}
<p>{{ .Body }}</p>
<p>{{ .User.Name }} - {{ .CreatedAtDate }}</p>
<br/>
{{ end }}
<form action="/thread/post" method="post">
<textarea name="body" id="body" placeholder="Write your reply here" rows="3"></textarea>
<input type="hidden" name="uuid" value="{{ .Uuid }}">
<br/>
<button type="submit">Reply</button>
</form>
{{ end }}
template/public.navbar.html
{{ define "navbar" }}
<a href="/"><i>ChitChat</i></a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/login">Login</a></li>
</ul>
{{ end }}
template/public.thread.html
{{ define "content" }}
<h2><i>{{ .Topic }}</i> Started by {{ .User.Name }} - {{ .CreatedAtDate }}</h2>
{{ range .Posts }}
<p>{{ .Body }}</p>
<p>{{ .User.Name }} - {{ .CreatedAtDate }}</p>
<br/>
{{ end }}
{{ end }}
template/signup.html
{{ define "content" }}
<form action="/signup_account" method="post">
<h2><i>ChitChat</i></h2>
<p>Sign up for an account below</p>
<input id="name" type="text" name="name" placeholder="Name" required autofocus>
<br/>
<input type="email" name="email" placeholder="Email address" required>
<br/>
<input type="password" name="password" placeholder="Password" required>
<br/>
<button type="submit">Sign up</button>
</form>
{{ end }}
data/setup.sql
drop table if exists posts;
drop table if exists threads;
drop table if exists sessions;
drop table if exists users;
create table users (
id serial primary key,
uuid varchar(64) not null unique,
name varchar(255),
email varchar(255) not null unique,
password varchar(255) not null,
created_at timestamp not null
);
create table sessions (
id serial primary key,
uuid varchar(64) not null unique,
email varchar(255),
user_id integer references users(id),
created_at timestamp not null
);
create table threads (
id serial primary key,
uuid varchar(64) not null unique,
topic text,
user_id integer references users(id),
created_at timestamp not null
);
create table posts (
id serial primary key,
uuid varchar(64) not null unique,
body text,
user_id integer references users(id),
thread_id integer references threads(id),
created_at timestamp not null
);
创建数据库和表
createdb -p 5433 -U postgres chitchat
psql -p 5433 -U postgres -d chitchat -f F:\study\go\chitchat\data\setup.sql
data/data.go
package data
import (
"crypto/rand"
"crypto/sha1"
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
//数据库连接池句柄
var Db *sql.DB
//数据库连接
func init() {
var err error
Db, err = sql.Open("postgres", "host=localhost port=5433 user=postgres dbname=chitchat password=postgres sslmode=disable")
if err != nil {
log.Fatal(err)
}
err = Db.Ping()
if err != nil {
panic(err)
}
}
//RFC 4122随机UUID(Universally Unique IDentifier
func createUUID() (uuid string) {
u := new([16]byte)
if _, err := rand.Read(u[:]); err != nil {
log.Fatalln("Cannot generate UUID", err)
}
u[8] = (u[8] | 0x40) & 0x7F
u[6] = (u[6] & 0xF) | (0x4 << 4)
uuid = fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
return
}
//SHA-1散列明文
func Encrypt(plaintext string) (cryptext string) {
cryptext = fmt.Sprintf("%x", sha1.Sum([]byte(plaintext)))
return
}
data/user.go
package data
import (
"time"
)
//用户
type User struct {
Id int
Uuid string
Name string
Email string
Password string
CreatedAt time.Time
}
// Create a new session for an existing user
func (user *User) CreateSession() (session Session, err error) {
statement := "insert into sessions (uuid, email, user_id, created_at) values ($1, $2, $3, $4) returning id, uuid, email, user_id, created_at"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
// use QueryRow to return a row and scan the returned id into the Session struct
err = stmt.QueryRow(createUUID(), user.Email, user.Id, time.Now()).
Scan(&session.Id, &session.Uuid, &session.Email, &session.UserId, &session.CreatedAt)
return
}
// Get the session for an existing user
func (user *User) Session() (session Session, err error) {
err = Db.QueryRow("SELECT id, uuid, email, user_id, created_at FROM sessions WHERE user_id = $1", user.Id).
Scan(&session.Id, &session.Uuid, &session.Email, &session.UserId, &session.CreatedAt)
return
}
// Create a new user, save user info into the database
func (user *User) Create() (err error) {
// Postgres does not automatically return the last insert id, because it would be wrong to assume
// you're always using a sequence.You need to use the RETURNING keyword in your insert to get this
// information from postgres.
statement := "insert into users (uuid, name, email, password, created_at) values ($1, $2, $3, $4, $5) returning id, uuid, created_at"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
// use QueryRow to return a row and scan the returned id into the User struct
err = stmt.QueryRow(createUUID(), user.Name, user.Email, Encrypt(user.Password), time.Now()).
Scan(&user.Id, &user.Uuid, &user.CreatedAt)
return
}
// Delete user from database
func (user *User) Delete() (err error) {
stmt, err := Db.Prepare("delete from users where id = $1")
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(user.Id)
return
}
// Update user information in the database
func (user *User) Update() (err error) {
stmt, err := Db.Prepare("update users set name = $2, email = $3 where id = $1")
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(user.Id, user.Name, user.Email)
return
}
// Delete all users from database
func UserDeleteAll() (err error) {
_, err = Db.Exec("delete from users")
return
}
// Get all users in the database and returns it
func Users() (users []User, err error) {
rows, err := Db.Query("SELECT id, uuid, name, email, password, created_at FROM users")
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
user := User{}
if err = rows.Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt); err != nil {
return
}
users = append(users, user)
}
return
}
// Get a single user given the UUID
func UserNoPasswordByID(id int) (user User, err error) {
err = Db.QueryRow("SELECT id, uuid, name, email, created_at FROM users WHERE id = $1", id).
Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.CreatedAt)
return
}
// Get a single user given the UUID
func UserByID(id int) (user User, err error) {
err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE id = $1", id).
Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt)
return
}
// Get a single user given the UUID
func UserByUUID(uuid string) (user User, err error) {
err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE uuid = $1", uuid).
Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt)
return
}
// Get a single user given the email
func UserByEmail(email string) (user User, err error) {
err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE email = $1", email).
Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt)
return
}
// Create a new thread
func (user *User) CreateThread(topic string) (thread Thread, err error) {
statement := "insert into threads (uuid, topic, user_id, created_at) values ($1, $2, $3, $4) returning id, uuid, topic, user_id, created_at"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
// use QueryRow to return a row and scan the returned id into the Session struct
err = stmt.QueryRow(createUUID(), topic, user.Id, time.Now()).
Scan(&thread.Id, &thread.Uuid, &thread.Topic, &thread.UserId, &thread.CreatedAt)
return
}
// Create a new post to a thread
func (user *User) CreatePost(thread Thread, body string) (post Post, err error) {
statement := "insert into posts (uuid, body, user_id, thread_id, created_at) values ($1, $2, $3, $4, $5) returning id, uuid, body, user_id, thread_id, created_at"
stmt, err := Db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
// use QueryRow to return a row and scan the returned id into the Session struct
err = stmt.QueryRow(createUUID(), body, user.Id, thread.Id, time.Now()).Scan(&post.Id, &post.Uuid, &post.Body, &post.UserId, &post.ThreadId, &post.CreatedAt)
return
}
data/thread.go
package data
import (
"time"
)
//主线
type Thread struct {
Id int
Uuid string
Topic string //话题
UserId int
CreatedAt time.Time
}
// format the CreatedAt date to display nicely on the screen
func (thread *Thread) CreatedAtDate() string {
return thread.CreatedAt.Format("Jan 2, 2006 at 3:04pm")
}
// get the number of posts in a thread
func (thread *Thread) NumReplies() (count int) {
rows, err := Db.Query("SELECT count(*) FROM posts where thread_id = $1", thread.Id)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
if err = rows.Scan(&count); err != nil {
return
}
}
return
}
// get posts to a thread
func (thread *Thread) Posts() (posts []Post, err error) {
rows, err := Db.Query("SELECT id, uuid, body, user_id, thread_id, created_at FROM posts where thread_id = $1", thread.Id)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
post := Post{}
if err = rows.Scan(&post.Id, &post.Uuid, &post.Body, &post.UserId, &post.ThreadId, &post.CreatedAt); err != nil {
return
}
posts = append(posts, post)
}
return
}
// Get all threads in the database and returns it
func Threads() (threads []Thread, err error) {
rows, err := Db.Query("SELECT id, uuid, topic, user_id, created_at FROM threads ORDER BY created_at DESC")
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
thread := Thread{}
if err = rows.Scan(&thread.Id, &thread.Uuid, &thread.Topic, &thread.UserId, &thread.CreatedAt); err != nil {
return
}
threads = append(threads, thread)
}
return
}
// Get a thread by the UUID
func ThreadByUUID(uuid string) (thread Thread, err error) {
err = Db.QueryRow("SELECT id, uuid, topic, user_id, created_at FROM threads WHERE uuid = $1", uuid).
Scan(&thread.Id, &thread.Uuid, &thread.Topic, &thread.UserId, &thread.CreatedAt)
return
}
// Get the user who started this thread
func (thread *Thread) User() (user User) {
user, _ = UserNoPasswordByID(thread.UserId)
return
}
// Delete all threads from database
func ThreadDeleteAll() (err error) {
_, err = Db.Exec("delete from threads")
return
}
data/post.go
package data
import (
"time"
)
//帖子
type Post struct {
Id int
Uuid string
Body string //内容
UserId int
ThreadId int
CreatedAt time.Time
}
func (post *Post) CreatedAtDate() string {
return post.CreatedAt.Format("Jan 2, 2006 at 3:04pm")
}
// Get the user who wrote the post
func (post *Post) User() (user User) {
user, _ = UserNoPasswordByID(post.UserId)
return
}
data/session.go
package data
import (
"time"
)
//会话
type Session struct {
Id int
Uuid string
Email string
UserId int
CreatedAt time.Time
}
// Check if session is valid in the database
func (session *Session) Check() (valid bool, err error) {
err = Db.QueryRow("SELECT id, uuid, email, user_id, created_at FROM sessions WHERE uuid = $1", session.Uuid).
Scan(&session.Id, &session.Uuid, &session.Email, &session.UserId, &session.CreatedAt)
if err != nil {
valid = false
return
}
if session.Id != 0 {
valid = true
}
return
}
// Check if session is valid in the database
func CheckSeeeionByUUID(uuid string) bool {
count := 0
Db.QueryRow("SELECT count(*) FROM sessions WHERE uuid = $1 LIMIT 1", uuid).Scan(&count)
return count == 1
}
// Check if session is valid in the database
func SessionByUUID(uuid string) (session Session, err error) {
err = Db.QueryRow("SELECT id, uuid, email, user_id, created_at FROM sessions WHERE uuid = $1", uuid).
Scan(&session.Id, &session.Uuid, &session.Email, &session.UserId, &session.CreatedAt)
return
}
func SessionDeleteByUUID(uuid string) (err error) {
stmt, err := Db.Prepare("delete from sessions where uuid = $1")
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(uuid)
return
}
// Delete session from database
func (session *Session) DeleteByUUID() (err error) {
stmt, err := Db.Prepare("delete from sessions where uuid = $1")
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(session.Uuid)
return
}
// Get the user from the session
func (session *Session) User() (user User, err error) {
user, _ = UserNoPasswordByID(session.UserId)
return
}
// Delete all sessions from database
func SessionDeleteAll() (err error) {
_, err = Db.Exec("delete from sessions")
return
}
config.json
{
"Address" : "127.0.0.1:8080",
"ReadTimeout" : 10,
"WriteTimeout" : 600
}
util.go
package main
import (
"chitchat/data"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"os"
)
type Configuration struct {
Address string
ReadTimeout int64
WriteTimeout int64
}
var config Configuration
var logger *log.Logger
func loadConfig() {
fp, err := os.Open("config.json")
if err != nil {
log.Fatalln("Cannot open config file", err)
}
defer fp.Close()
decoder := json.NewDecoder(fp)
err = decoder.Decode(&config)
if err != nil {
log.Fatalln("Cannot get configuration from file", err)
}
}
func init() {
loadConfig()
fp, err := os.OpenFile("chitchat.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open log file", err)
}
logger = log.New(fp, "INFO ", log.Ldate|log.Ltime|log.Lshortfile)
}
// Convenience function to redirect to the error message page
func error_message(writer http.ResponseWriter, request *http.Request, msg string) {
http.Redirect(writer, request, "/err?msg="+msg, http.StatusFound)
}
// Checks if the user is logged in and has a session, if not err is not nil
func session(request *http.Request) bool {
if cookie, err := request.Cookie("_cookie"); err == nil {
return data.CheckSeeeionByUUID(cookie.Value)
}
return false
}
func generateHTML(writer http.ResponseWriter, data interface{}, filenames ...string) {
var files []string
for _, file := range filenames {
files = append(files, fmt.Sprintf("templates/%s.html", file))
}
templates := template.Must(template.ParseFiles(files...))
templates.ExecuteTemplate(writer, "layout", data)
}
func logError(args ...interface{}) {
logger.SetPrefix("ERROR ")
logger.Println(args...)
}
route.go
package main
import (
"chitchat/data"
"fmt"
"net/http"
)
func index(writer http.ResponseWriter, request *http.Request) {
if threads, err := data.Threads(); err != nil {
error_message(writer, request, "Cannot get threads")
} else {
if !session(request) {
generateHTML(writer, threads, "layout", "public.navbar", "index")
} else {
generateHTML(writer, threads, "layout", "private.navbar", "index")
}
}
}
// GET /err?msg=
// shows the error message page
func err(writer http.ResponseWriter, request *http.Request) {
if vals := request.URL.Query(); !session(request) {
generateHTML(writer, vals.Get("msg"), "layout", "public.navbar", "error")
} else {
generateHTML(writer, vals.Get("msg"), "layout", "private.navbar", "error")
}
}
// GET /login
// Show the login page
func login(writer http.ResponseWriter, request *http.Request) {
generateHTML(writer, nil, "login.layout", "login")
}
// GET /logout
// Logs the user out
func logout(writer http.ResponseWriter, request *http.Request) {
if cookie, err := request.Cookie("_cookie"); err == nil {
data.SessionDeleteByUUID(cookie.Value)
}
http.Redirect(writer, request, "/", http.StatusFound)
}
// GET /signup
// Show the signup page
func signup(writer http.ResponseWriter, request *http.Request) {
generateHTML(writer, nil, "login.layout", "signup")
}
// POST /signup
// Create the user account
func signupAccount(writer http.ResponseWriter, request *http.Request) {
user := data.User{
Name: request.PostFormValue("name"),
Email: request.PostFormValue("email"),
Password: request.PostFormValue("password"),
}
if err := user.Create(); err != nil {
logError(err, "Cannot create user")
}
http.Redirect(writer, request, "/login", http.StatusFound)
}
// POST /authenticate
// Authenticate the user given the email and password
func authenticate(writer http.ResponseWriter, request *http.Request) {
user, err := data.UserByEmail(request.PostFormValue("email"))
if err != nil {
logError(err, "Cannot find user")
}
if user.Password == data.Encrypt(request.PostFormValue("password")) {
session, err := user.CreateSession()
if err != nil {
logError(err, "Cannot create session")
}
cookie := http.Cookie{
Name: "_cookie",
Value: session.Uuid,
HttpOnly: true,
}
http.SetCookie(writer, &cookie)
http.Redirect(writer, request, "/", http.StatusFound)
} else {
http.Redirect(writer, request, "/login", http.StatusFound)
}
}
// GET /thread/new
// Show the new thread form page
func newThread(writer http.ResponseWriter, request *http.Request) {
if !session(request) {
http.Redirect(writer, request, "/login", http.StatusFound)
} else {
generateHTML(writer, nil, "layout", "private.navbar", "new.thread")
}
}
// POST /thread/create
// Create the user account
func createThread(writer http.ResponseWriter, request *http.Request) {
if !session(request) {
http.Redirect(writer, request, "/login", http.StatusFound)
} else {
cookie, _ := request.Cookie("_cookie")
session, _ := data.SessionByUUID(cookie.Value)
user, err := data.UserNoPasswordByID(session.UserId)
if err != nil {
logError(err, "Cannot get user from session")
}
topic := request.PostFormValue("topic")
if _, err := user.CreateThread(topic); err != nil {
logError(err, "Cannot create thread")
}
http.Redirect(writer, request, "/", http.StatusFound)
}
}
// POST /thread/post
// Create the post
func postThread(writer http.ResponseWriter, request *http.Request) {
if !session(request) {
http.Redirect(writer, request, "/login", http.StatusFound)
} else {
cookie, _ := request.Cookie("_cookie")
session, _ := data.SessionByUUID(cookie.Value)
user, err := data.UserByID(session.UserId)
if err != nil {
logError(err, "Cannot get user from session")
}
body := request.PostFormValue("body")
uuid := request.PostFormValue("uuid")
thread, err := data.ThreadByUUID(uuid)
if err != nil {
error_message(writer, request, "Cannot read thread")
}
if _, err := user.CreatePost(thread, body); err != nil {
logError(err, "Cannot create post")
}
url := fmt.Sprint("/thread/read?id=", uuid)
http.Redirect(writer, request, url, http.StatusFound)
}
}
// GET /thread/read
// Show the details of the thread, including the posts and the form to write a post
func readThread(writer http.ResponseWriter, request *http.Request) {
vals := request.URL.Query()
uuid := vals.Get("id")
thread, err := data.ThreadByUUID(uuid)
if err != nil {
error_message(writer, request, "Cannot read thread")
} else {
if !session(request) {
generateHTML(writer, &thread, "layout", "public.navbar", "public.thread")
} else {
generateHTML(writer, &thread, "layout", "private.navbar", "private.thread")
}
}
}
main.go
package main
import (
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
// index
mux.HandleFunc("/", index)
// error
mux.HandleFunc("/err", err)
// defined in route_auth.go
mux.HandleFunc("/login", login)
mux.HandleFunc("/logout", logout)
mux.HandleFunc("/signup", signup)
mux.HandleFunc("/signup_account", signupAccount)
mux.HandleFunc("/authenticate", authenticate)
// defined in route_thread.go
mux.HandleFunc("/thread/new", newThread)
mux.HandleFunc("/thread/create", createThread)
mux.HandleFunc("/thread/post", postThread)
mux.HandleFunc("/thread/read", readThread)
// starting up the server
server := &http.Server{
Addr: config.Address,
Handler: mux,
ReadTimeout: time.Duration(config.ReadTimeout * int64(time.Second)),
WriteTimeout: time.Duration(config.WriteTimeout * int64(time.Second)),
MaxHeaderBytes: 1 << 20,
}
server.ListenAndServe()
}
运行及测试
go mod init chitchat
go get github.com/lib/pq
go run main.go route.go utils.go
curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/err?msg=test
curl -i http://127.0.0.1:8080/login
curl -i http://127.0.0.1:8080/logout
curl -i http://127.0.0.1:8080/signup
//创建用户
curl -v -d"name=test111&email=test111@136.com&password=test111" http://127.0.0.1:8080/signup_account
//用户验证,获取cookie并保存
curl -v -d"name=test111&email=test111@136.com&password=test111" -c cookie.txt http://127.0.0.1:8080/authenticate
curl -i -b cookie.txt http://127.0.0.1:8080/thread/new
//创建主题
curl -i -b cookie.txt -d"topic=mytest" http://127.0.0.1:8080/thread/create
//发布帖子
//uuid指thread的uuid,curl -i http://127.0.0.1:8080/可以查看
curl -i -b cookie.txt -d"body=contents&uuid=31115747-5b29-4a9f-55c4-2cf92c6e6150" http://127.0.0.1:8080/thread/post
//查看主题下的所有帖子
curl -i -b cookie.txt http://127.0.0.1:8080/thread/read?id=31115747-5b29-4a9f-55c4-2cf92c6e6150
//查看数据表
psql -p 5433 -U postgres -d chitchat -c "select * from users;"
psql -p 5433 -U postgres -d chitchat -c "select * from threads;"
psql -p 5433 -U postgres -d chitchat -c "select * from posts;"
psql -p 5433 -U postgres -d chitchat -c "select * from sessions;"
//sessions查看会话是否存在
psql -p 5433 -U postgres -d chitchat -c "SELECT count(*) FROM sessions WHERE uuid = '84cb28ba-2af4-466a-5ba1-cc0f6471d6dc' LIMIT 1;"