go 连接服务器 并存放图片_基于 Go 语言开发在线论坛(二):通过模型类与MySQL数据库交互...
在這篇教程中,我們將在 MySQL 中創建一個?chitchat?數據庫作為論壇項目的數據庫,然后在 Go 項目中編寫模型類與之進行交互。你可以本地安裝 MySQL 數據庫,也可以基于 Docker 容器運行(后續會介紹容器化啟動方法)。
1、項目初始化
開始之前,我們先來初始化項目目錄,我們將項目名設置為?chitchat,所以在?~/Development/go/src/github.com/xueyuanjun?目錄下創建這個項目目錄,然后初始化目錄結構如下:
重點看下紅框內,各個子目錄/文件的作用介紹如下:
main.go:應用入口文件
config.json:全局配置文件
handlers:用于存放處理器代碼(可類比為 MVC 模式中的控制器目錄)
logs:用于存放日志文件
models:用于存放與數據庫交互的模型類
public:用于存放前端資源文件,比如圖片、CSS、JavaScript 等
routes:用于存放路由文件和路由器實現代碼
views:用于存放視圖模板文件
接下來,我們在?chitchat?目錄下運行如下命令初始化?go.mod,因為我們后續通過 Go Module 來管理依賴:
go mod init github.com/xueyuanjun/chitchat2、創建數據表
開始正式編碼之前,現在?chitchat?數據庫中創建數據表,對應的 SQL 語句如下:
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);在 MySQL 客戶端連接到?chitchat?數據庫,運行上述 SQL 語句創建所有數據表:
3、與數據庫交互
1)數據庫驅動
數據表創建完成后,接下來,如何在 Go 應用代碼中與數據庫交互呢?Go 語言開發組并沒有為此提供官方的數據庫驅動實現,只是提供了數據庫交互接口,我們可以通過實現這些接口的第三方擴展包完成與 MySQL 數據庫的交互,本項目選擇的擴展包是?go-mysql-driver?。
我們可以在 Go 應用中編寫模型類基于這個擴展包提供的方法與 MySQL 交互完成增刪改查操作,開始之前,可以運行如下命令安裝這個依賴:
go get github.com/go-sql-driver/mysql2)數據庫連接
然后在?chitchat/models?目錄下創建?db.go,并編寫數據庫連接初始化方法以及生成 UUID、哈希加密方法:
package modelsimport ( "crypto/rand" "crypto/sha1" "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "log")var Db *sql.DBfunc init() { var err error Db, err = sql.Open("mysql", "root:root@/chitchat?charset=utf8&parseTime=true") if err != nil { log.Fatal(err) } return}// create a random UUID with from RFC 4122// adapted from http://github.com/nu7hatch/gouuidfunc createUUID() (uuid string) { u := new([16]byte) _, err := rand.Read(u[:]) if err != nil { log.Fatalln("Cannot generate UUID", err) } // 0x40 is reserved variant from RFC 4122 u[8] = (u[8] | 0x40) & 0x7F // Set the four most significant bits (bits 12 through 15) of the // time_hi_and_version field to the 4-bit version number. 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}// hash plaintext with SHA-1func Encrypt(plaintext string) (cryptext string) { cryptext = fmt.Sprintf("%x", sha1.Sum([]byte(plaintext))) return}其中,Db?變量代表數據庫連接池,通過?init?方法在 Web 應用啟動時自動初始化數據庫連接,這樣,我們就可以在應用中通過?Db?變量對數據庫進行增刪改查操作了,這也是該變量首字母大寫的原因,方便在?models?包之外被引用,具體的操作實現我們放到獨立的模型文件中處理。
注:這里通過 sql.Open 初始化數據庫連接,我們寫死了數據庫連接配置,在實際生產環境,這塊配置值應該從配置文件或系統環境變量獲取。
3)用戶相關模型類
有了代表數據庫連接池的?Db?變量之后,就可以為每個數據表編寫對應的模型類實現增刪改查操作了,首先在?models?目錄下創建?user.go?用于定義用戶模型類?User?與?users?表進行交互,以及與?sessions?表進行關聯:
package modelsimport "time"type User struct { Id int Uuid string Name string Email string Password string CreatedAt time.Time}// Create a new session for an existing userfunc (user *User) CreateSession() (session Session, err error) { statement := "insert into sessions (uuid, email, user_id, created_at) values (?, ?, ?, ?)" stmtin, err := Db.Prepare(statement) if err != nil { return } defer stmtin.Close() uuid := createUUID() stmtin.Exec(uuid, user.Email, user.Id, time.Now()) stmtout, err := Db.Prepare("select id, uuid, email, user_id, created_at from sessions where uuid = ?") if err != nil { return } defer stmtout.Close() // use QueryRow to return a row and scan the returned id into the Session struct err = stmtout.QueryRow(uuid).Scan(&session.Id, &session.Uuid, &session.Email, &session.UserId, &session.CreatedAt) return}// Get the session for an existing userfunc (user *User) Session() (session Session, err error) { session = Session{} err = Db.QueryRow("SELECT id, uuid, email, user_id, created_at FROM sessions WHERE user_id = ?", user.Id). Scan(&session.Id, &session.Uuid, &session.Email, &session.UserId, &session.CreatedAt) return}// Create a new user, save user info into the databasefunc (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 (?, ?, ?, ?, ?)" stmtin, err := Db.Prepare(statement) if err != nil { return } defer stmtin.Close() uuid := createUUID() stmtin.Exec(uuid, user.Name, user.Email, Encrypt(user.Password), time.Now()) stmtout, err := Db.Prepare("select id, uuid, created_at from users where uuid = ?") if err != nil { return } defer stmtout.Close() // use QueryRow to return a row and scan the returned id into the User struct err = stmtout.QueryRow(uuid).Scan(&user.Id, &user.Uuid, &user.CreatedAt) return}// Delete user from databasefunc (user *User) Delete() (err error) { statement := "delete from users where id = ?" stmt, err := Db.Prepare(statement) if err != nil { return } defer stmt.Close() _, err = stmt.Exec(user.Id) return}// Update user information in the databasefunc (user *User) Update() (err error) { statement := "update users set name = ?, email = ? where id = ?" stmt, err := Db.Prepare(statement) if err != nil { return } defer stmt.Close() _, err = stmt.Exec(user.Name, user.Email, user.Id) return}// Delete all users from databasefunc UserDeleteAll() (err error) { statement := "delete from users" _, err = Db.Exec(statement) return}// Get all users in the database and returns itfunc Users() (users []User, err error) { rows, err := Db.Query("SELECT id, uuid, name, email, password, created_at FROM users") if err != nil { return } 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) } rows.Close() return}// Get a single user given the emailfunc UserByEmail(email string) (user User, err error) { user = User{} err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE email = ?", email). Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt) return}// Get a single user given the UUIDfunc UserByUUID(uuid string) (user User, err error) { user = User{} err = Db.QueryRow("SELECT id, uuid, name, email, password, created_at FROM users WHERE uuid = ?", uuid). Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.Password, &user.CreatedAt) return}創建?session.go?用于定義會話模型類?Session:
package modelsimport "time"type Session struct { Id int Uuid string Email string UserId int CreatedAt time.Time}// Check if session is valid in the databasefunc (session *Session) Check() (valid bool, err error) { err = Db.QueryRow("SELECT id, uuid, email, user_id, created_at FROM sessions WHERE uuid = ?", 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}// Delete session from databasefunc (session *Session) DeleteByUUID() (err error) { statement := "delete from sessions where uuid = ?" stmt, err := Db.Prepare(statement) if err != nil { return } defer stmt.Close() _, err = stmt.Exec(session.Uuid) return}// Get the user from the sessionfunc (session *Session) User() (user User, err error) { user = User{} err = Db.QueryRow("SELECT id, uuid, name, email, created_at FROM users WHERE id = ?", session.UserId). Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.CreatedAt) return}// Delete all sessions from databasefunc SessionDeleteAll() (err error) { statement := "delete from sessions" _, err = Db.Exec(statement) return}這里面定義了基于?Db?數據庫連接實例實現用戶模型和會話模型相關的增刪改查操作,具體的語法可以參考?go-mysql-driver?的?官方文檔,這里不詳細展開,具體細節,我們留到后面專門介紹數據庫模塊時再詳細說明。
4)主題相關模型類
編寫好用戶相關模型類后,接下來在同級目錄下創建?thread.go,定義群組模型類?Thread?與?threads?表進行交互:
package modelsimport "time"type Thread struct { Id int Uuid string Topic string UserId int CreatedAt time.Time}// format the CreatedAt date to display nicely on the screenfunc (thread *Thread) CreatedAtDate() string { return thread.CreatedAt.Format("Jan 2, 2006 at 3:04pm")}// get the number of posts in a threadfunc (thread *Thread) NumReplies() (count int) { rows, err := Db.Query("SELECT count(*) FROM posts where thread_id = ?", thread.Id) if err != nil { return } for rows.Next() { if err = rows.Scan(&count); err != nil { return } } rows.Close() return}// get posts to a threadfunc (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 = ?", thread.Id) if err != nil { return } 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) } rows.Close() return}// Get all threads in the database and returns itfunc 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 } for rows.Next() { conv := Thread{} if err = rows.Scan(&conv.Id, &conv.Uuid, &conv.Topic, &conv.UserId, &conv.CreatedAt); err != nil { return } threads = append(threads, conv) } rows.Close() return}// Get a thread by the UUIDfunc ThreadByUUID(uuid string) (conv Thread, err error) { conv = Thread{} err = Db.QueryRow("SELECT id, uuid, topic, user_id, created_at FROM threads WHERE uuid = ?", uuid). Scan(&conv.Id, &conv.Uuid, &conv.Topic, &conv.UserId, &conv.CreatedAt) return}// Get the user who started this threadfunc (thread *Thread) User() (user User) { user = User{} Db.QueryRow("SELECT id, uuid, name, email, created_at FROM users WHERE id = ?", thread.UserId). Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.CreatedAt) return}以及?post.go?編寫主題模型類與?posts?表進行交互:
package modelsimport "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 postfunc (post *Post) User() (user User) { user = User{} Db.QueryRow("SELECT id, uuid, name, email, created_at FROM users WHERE id = ?", post.UserId). Scan(&user.Id, &user.Uuid, &user.Name, &user.Email, &user.CreatedAt) return}此外,我們到?user.go?中為?User?模型新增如下兩個方法與?Thread、Post?模型進行關聯,用于創建新的群組和主題:
// Create a new threadfunc (user *User) CreateThread(topic string) (conv Thread, err error) { statement := "insert into threads (uuid, topic, user_id, created_at) values (?, ?, ?, ?)" stmtin, err := Db.Prepare(statement) if err != nil { return } defer stmtin.Close() uuid := createUUID() stmtin.Exec(uuid, topic, user.Id, time.Now()) stmtout, err := Db.Prepare("select id, uuid, topic, user_id, created_at from threads where uuid = ?") if err != nil { return } defer stmtout.Close() // use QueryRow to return a row and scan the returned id into the Session struct err = stmtout.QueryRow(uuid).Scan(&conv.Id, &conv.Uuid, &conv.Topic, &conv.UserId, &conv.CreatedAt) return}// Create a new post to a threadfunc (user *User) CreatePost(conv Thread, body string) (post Post, err error) { statement := "insert into posts (uuid, body, user_id, thread_id, created_at) values (?, ?, ?, ?, ?)" stmtin, err := Db.Prepare(statement) if err != nil { return } defer stmtin.Close() uuid := createUUID() stmtin.Exec(uuid, body, user.Id, conv.Id, time.Now()) stmtout, err := Db.Prepare("select id, uuid, body, user_id, thread_id, created_at from posts where uuid = ?") if err != nil { return } defer stmtout.Close() // use QueryRow to return a row and scan the returned id into the Session struct err = stmtout.QueryRow(uuid).Scan(&post.Id, &post.Uuid, &post.Body, &post.UserId, &post.ThreadId, &post.CreatedAt) return}4、小結
在上述編寫的模型類中,模型類與數據表是如何映射的呢?這個由?go-mysql-driver?底層實現,每次從數據庫查詢到結果之后,可以通過?Scan?方法將數據表字段值映射到對應的結構體模型類,而將模型類保存到數據庫時,又可以基于字段映射關系將結構體屬性值轉化為對應的數據表字段值。對應的底層交互邏輯如下所示:
再次重申,本項目旨在幫助大家快速熟悉 Go Web 項目的完整開發流程和代碼組織架構,具體的技術實現細節留到后面單獨的數據庫模塊詳細介紹,不過,如果你之前有過其他語言的 Web 開發經驗,比如 PHP、Python、Java,基本上看一眼這些代碼就能知道個大概了,因為他們的實現思路都是相通的,無非是語言的語法不同而已,所以這也是學院君反復強調新人應該花個三五年時間先精通一門語言,再學習其他語言的原因。
底層數據庫交互邏輯定義好了之后,接下來,我們就可以編寫上層實現代碼了,下一篇學院君將給大家演示在線論壇項目上層路由和處理器方法的實現。
推薦閱讀
基于 Go 語言編寫在線論壇(一):整體設計與數據模型
喜歡本文的朋友,歡迎關注“Go語言中文網”:
Go語言中文網啟用微信學習交流群,歡迎加微信:274768166,投稿亦歡迎
總結
以上是生活随笔為你收集整理的go 连接服务器 并存放图片_基于 Go 语言开发在线论坛(二):通过模型类与MySQL数据库交互...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 六年级计算机应用计划,2017六年级信息
- 下一篇: java文件传输连接方式_Java 学习