問答社區是一種常見的社交化應用,允許用户發佈問題、回答問題並相互交流。隨着互聯網的發展,問答社區已經成為人們獲取知識和分享經驗的重要平台。
本文將介紹如何使用 Gin 和 Gorm 構建一個簡單的問答社區。本社區包含以下功能:
- 用户註冊和登錄
- 問題發佈和回答
- 問題列表和詳情
- 答案列表和詳情
- 用户信息和回答列表
數據庫設計
一共有users、questions、answers三個表,如下所示:
CREATE DATABASE IF NOT EXISTS qasys DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
create table qasys.users
(
id bigint unsigned auto_increment primary key,
username varchar(255) not null comment '用户名',
password varchar(255) not null comment '密碼',
email varchar(255) not null comment '郵箱',
created_at datetime null,
updated_at datetime null,
deleted_at datetime null,
constraint email unique (email),
constraint username unique (username)
);
create table qasys.questions
(
id bigint unsigned auto_increment primary key,
user_id int not null,
title varchar(255) not null comment '標題',
content text not null comment '問題詳情',
created_at datetime null,
updated_at datetime null,
deleted_at datetime null
);
create index questions_user_id_index on qasys.questions (user_id);
create table qasys.answers
(
id bigint unsigned auto_increment primary key,
question_id int not null comment '問題id',
user_id int not null comment '用户id',
content text not null comment '答案',
created_at datetime null,
updated_at datetime null,
deleted_at datetime null
);
create index answers_question_id_index on qasys.answers (question_id);
create index answers_user_id_index on qasys.answers (user_id);
準備環境
- 確保您的環境已經安裝了 Go 和 一個mysql服務,並把上面的表導入mysql。
- 安裝一個腳手架sponge(集成了gin+gorm),支持在windows、mac、linux環境下,點擊查看 安裝sponge説明。
- 安裝完成後打開終端,啓動sponge UI界面服務:
sponge run
在瀏覽器訪問 http://localhost:24631,進入sponge生成代碼的UI界面。
創建問答社區服務
進入sponge的UI界面:
- 點擊左邊菜單欄【SQL】-->【創建web服務】;
- 選擇數據庫
mysql,填寫數據庫dsn,然後點擊按鈕獲取表名,選擇表名(可多選); - 填寫其他參數,鼠標放在問號
?位置可以查看參數説明;
填寫完參數後,點擊按鈕下載代碼生成web服務完整項目代碼,如下圖所示:
這是創建的web服務代碼目錄,已經包含了users, questions, answers三個表的CRUD api所有代碼,包含了Gin和Gorm的初始化和配置代碼,開箱即用。
.
├─ cmd
│ └─ qa
│ ├─ initial
│ └─ main.go
├─ configs
├─ deployments
│ ├─ binary
│ ├─ docker-compose
│ └─ kubernetes
├─ docs
├─ internal
│ ├─ cache
│ ├─ config
│ ├─ dao
│ ├─ ecode
│ ├─ handler
│ ├─ model
│ ├─ routers
│ ├─ server
│ └─ types
└─ scripts
解壓代碼文件,打開終端,切換到web服務代碼目錄,執行命令:
# 生成swagger文檔
make docs
# 編譯和運行服務
make run
在瀏覽器打開 http://localhost:8080/swagger/index.html,可以在頁面上進行增刪改查api測試,如下圖所示:
從上圖中可以看到,使用sponge生成服務已經完成了大部分api了,還有註冊登錄api、鑑權還沒實現,接下完成未實現的功能。
添加註冊登錄api
1. 定義請求參數和返回結果結構體
進入目錄internal/types,打開文件users_types.go,添加註冊和登錄的請求和返回結構體代碼:
// RegisterRequest login request params
type RegisterRequest struct {
Email string `json:"email" binding:"email"` // 郵件
Username string `json:"username" binding:"min=2"` // 用户名
Password string `json:"password" binding:"min=6"` // 密碼
}
// RegisterRespond data
type RegisterRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
ID uint64 `json:"id"`
} `json:"data"` // return data
}
// LoginRequest login request params
type LoginRequest struct {
Username string `json:"username" binding:"min=2"` // 用户名
Password string `json:"password" binding:"min=6"` // 密碼
}
// LoginRespond data
type LoginRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
ID uint64 `json:"id"`
Token string `json:"token"`
} `json:"data"` // return data
}
2. 定義錯誤碼
進入目錄internal/ecode,打開文件users_http.go,添加兩行行代碼,定義註冊和登錄錯誤碼:
var (
usersNO = 49
usersName = "users"
usersBaseCode = errcode.HCode(usersNO)
// ...
ErrRegisterUsers = errcode.NewError(usersBaseCode+10, "註冊失敗")
ErrLoginUsers = errcode.NewError(usersBaseCode+11, "登錄失敗")
// for each error code added, add +1 to the previous error code
)
3. 定義handler函數
進入目錄internal/handler,打開文件users.go,添加註冊和登錄方法,並填寫swagger註解:
// Register 註冊
// @Summary 註冊
// @Description register
// @Tags auth
// @accept json
// @Produce json
// @Param data body types.RegisterRequest true "login information"
// @Success 200 {object} types.RegisterRespond{}
// @Router /api/v1/auth/register [post]
func (h *usersHandler) Register(c *gin.Context) {
}
// Login 登錄
// @Summary 登錄
// @Description login
// @Tags auth
// @accept json
// @Produce json
// @Param data body types.LoginRequest true "login information"
// @Success 200 {object} types.LoginRespond{}
// @Router /api/v1/teacher/login [post]
func (h *usersHandler) Login(c *gin.Context) {
}
然後把Register和Login方法添加到UsersHandler接口:
type UsersHandler interface {
// ...
Register(c *gin.Context)
Login(c *gin.Context)
}
4. 註冊路由
進入目錄internal/routers,打開文件users.go,把Register和Login路由註冊進來:
func noAuthUsersRouter(group *gin.RouterGroup) {
h := handler.NewUsersHandler()
group.POST("/auth/register", h.Register)
group.POST("/auth/login", h.Login)
}
然後把noAuthUsersRouter函數添加到routers.go的註冊路由函數下,如下所示:
func registerRouters(r *gin.Engine, groupPath string, routerFns []func(*gin.RouterGroup), handlers ...gin.HandlerFunc) {
rg := r.Group(groupPath, handlers...)
noAuthUsersRouter(rg)
for _, fn := range routerFns {
fn(rg)
}
}
5. 編寫業務邏輯代碼
進入目錄internal/handler,打開文件users.go,編寫註冊和登錄的業務邏輯代碼:
func (h *usersHandler) Register(c *gin.Context) {
req := &types.RegisterRequest{}
err := c.ShouldBindJSON(req)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
ctx := middleware.WrapCtx(c)
password, err := gocrypto.HashAndSaltPassword(req.Password)
if err != nil {
logger.Error("gocrypto.HashAndSaltPassword error", logger.Err(err), middleware.CtxRequestIDField(ctx))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
users := &model.Users{
Username: req.Username,
Password: password,
Email: req.Email,
}
err = h.iDao.Create(ctx, users)
if err != nil {
logger.Error("Create error", logger.Err(err), logger.Any("form", req), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
response.Success(c, gin.H{"id": users.ID})
}
func (h *usersHandler) Login(c *gin.Context) {
req := &types.LoginRequest{}
err := c.ShouldBindJSON(req)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
ctx := middleware.WrapCtx(c)
condition := &query.Conditions{
Columns: []query.Column{
{
Name: "username",
Exp: "=",
Value: req.Username,
},
},
}
user, err := h.iDao.GetByCondition(ctx, condition)
if err != nil {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("Login not found", logger.Err(err), logger.Any("form", req), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.ErrLoginUsers)
} else {
logger.Error("Login error", logger.Err(err), logger.Any("form", req), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
}
return
}
// 驗證密碼
if !gocrypto.VerifyPassword(req.Password, user.Password) {
logger.Warn("password error", middleware.CtxRequestIDField(ctx))
response.Error(c, ecode.ErrLoginUsers)
}
// 生成token
token, err := jwt.GenerateToken(utils.Uint64ToStr(user.ID), user.Username)
if err != nil {
logger.Error("jwt.GenerateToken error", logger.Err(err), middleware.CtxRequestIDField(ctx))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
}
// TODO: 存儲token到緩存
response.Success(c, gin.H{
"id": user.ID,
"token": token,
})
}
6. 開啓api鑑權
有了註冊和登錄api,其他api需要添加jwt鑑權,sponge生成的所有api默認是沒有加入jwt鑑權的,只需開啓即可,進入目錄internal/routers,分別打開questions.go,answers.go,users.go代碼,把默認註釋代碼去掉//反註釋,表示下面所有路由都開啓jwt鑑權,如下所示:
group.Use(middleware.Auth())
然後在需要鑑權的api的swagger註解中添加下面説明,這樣在swagger頁面請求api時,請求頭會帶上token,後端會從請求獲取token值並驗證token是否有效。
// @Security BearerAuth
7. 測試api
編寫完業務邏輯代碼後,在終端執行命令:
# 生成swagger文檔
make docs
# 編譯和運行服務
make run
在瀏覽器刷新 http://localhost:8080/swagger/index.html,在頁面上可以看到註冊和登錄api,在頁面測試註冊和登錄api,獲取到token之後,把Bearer token填寫到Authorize處,測試其他api是否可以正常調用。
8. 部署
默認支持部署到服務器、docker、k8s三種方式:
方式一:部署服務到遠程linux服務
# 如果需要更新服務,再次執行此命令
make deploy-binary USER=root PWD=123456 IP=192.168.1.10
方式二:部署到docker
# 構建鏡像
make image-build REPO_HOST=myRepo.com TAG=1.0
# 推送鏡像,這裏參數REPO_HOST和TAG,與構建鏡像的參數REPO_HOST和TAG一樣。
make image-push REPO_HOST=myRepo.com TAG=1.0
# 複製 deployments/docker-compose 目錄下的文件到目標服務器,修改鏡像地址後啓動服務
docker-compose up -d
方式三: 部署到k8s
# 構建鏡像
make image-build REPO_HOST=myRepo.com TAG=1.0
# 推送鏡像,這裏參數REPO_HOST和TAG,與構建鏡像的參數REPO_HOST和TAG一樣。
make image-push REPO_HOST=myRepo.com TAG=1.0
# 複製 deployments/kubernetes 目錄下的文件到目標服務器,修改鏡像地址後,按順序執行腳本
kubectl apply -f ./*namespace.yml
kubectl apply -f ./
總結
sponge集成了Gin 和 Gorm 強大的web後端服務開發框架,可以幫助開發者快速且輕鬆構建 RESTful API 服務,這是sponge github地址。
本文介紹瞭如何使用 Gin 和 Gorm 構建一個簡單的問答社區,問答社區包含了一些基本功能,可以作為基礎擴展到更復雜的應用。