亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

go?doudou開發(fā)單體RESTful服務(wù)快速上手教程

 更新時間:2022年12月07日 11:33:24   作者:武斌  
這篇文章主要為大家介紹了go?doudou開發(fā)單體RESTful服務(wù)快速上手教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

筆者2015年開始接觸go語言并采用go語言從事web項目開發(fā)至今,先后用過beego、gin、grpc等框架。這些框架非常優(yōu)秀,通過學(xué)習(xí)它們的源碼,也學(xué)到了很多。筆者之前在公司一直是單打獨斗,一個人就把前后端的活包了,用現(xiàn)成的框架其實也蠻好。只是后來帶了團隊,接了不少項目,開始接觸和學(xué)習(xí)敏捷開發(fā)、項目管理等方面的理論和實踐,發(fā)現(xiàn)前后端不同成員之間溝通和聯(lián)調(diào)也是需要很多成本的,特別是如果前端同事完全不懂后端,后端同事完全不懂前端的情況下會遇到不少頭疼的事。于是萌生了用go語言開發(fā)一套低代碼的、易于快速開發(fā)的、同時方便前后端同事溝通和聯(lián)調(diào)的微服務(wù)框架,這就是go-doudou微服務(wù)框架。go-doudou框架主要基于gorilla的mux路由庫做RESTful接口的快速生成,基于hashicorp公司開源的memberlist庫做服務(wù)注冊與發(fā)現(xiàn)和故障檢測,同時支持開發(fā)單體應(yīng)用和微服務(wù)應(yīng)用。本教程將通過一個用戶管理服務(wù)的案例來分幾篇文章介紹如何用go-doudou開發(fā)單體RESTful接口。

需求清單

  • 用戶注冊
  • 用戶登錄
  • 用戶詳情
  • 用戶分頁
  • 上傳頭像
  • 下載頭像

學(xué)習(xí)目標(biāo)

  • 用戶詳情、用戶分頁和上傳頭像需要采用jwt做權(quán)限校驗
  • 用戶注冊、用戶登錄和下載頭像接口可以公開訪問,無須鑒權(quán)
  • 提供在線接口文檔
  • 提供go語言客戶端SDK
  • 提供mock接口實現(xiàn)
  • 實現(xiàn)真實業(yè)務(wù)邏輯
  • go-doudou內(nèi)建的ddl表結(jié)構(gòu)同步工具
  • go-doudou內(nèi)建的dao層代碼生成和使用

開發(fā)環(huán)境準(zhǔn)備

  • docker環(huán)境: 推薦下載安裝docker官方的desktop軟件,官方安裝文檔地址
  • IDE:推薦goland,當(dāng)然vscode也可以

安裝go-doudou

  • 配置goproxy.cn代理,加速依賴下載
export GOPROXY=https://goproxy.cn,direct
  • 如果你用的go版本是1.16以下版本:
GO111MODULE=on  go get -v github.com/unionj-cloud/go-doudou@v0.8.6

如果你用的go是1.16及以上版本:

go get -v github.com/unionj-cloud/go-doudou@v0.8.6
  • goproxy.cn的同步會延遲一些,如果執(zhí)行以上命令失敗,可以關(guān)閉代理,科學(xué)上網(wǎng)
export GOPROXY=https://proxy.golang.org,direct
  • 以上辦法都不行,可以直接克隆同步到gitee的源碼,本地安裝
git clone git@gitee.com:unionj-cloud/go-doudou.git

切到根路徑下,執(zhí)行命令:

go install
  • 執(zhí)行命令go-doudou -v,如果輸出如下內(nèi)容,表示安裝成功:
?  ~ go-doudou -v
go-doudou version v0.8.6

初始化工程

執(zhí)行命令:

go-doudou svc init usersvc 

切到usersvc路徑下,可以看到生成了如下文件結(jié)構(gòu):

?  tutorials ll
total 0
drwxr-xr-x  9 wubin1989  staff   288B 10 24 20:05 usersvc
?  tutorials cd usersvc  
?  usersvc git:(master) ? ll
total 24
-rw-r--r--  1 wubin1989  staff   707B 10 24 20:05 Dockerfile
-rw-r--r--  1 wubin1989  staff   439B 10 24 20:05 go.mod
-rw-r--r--  1 wubin1989  staff   247B 10 24 20:05 svc.go
drwxr-xr-x  3 wubin1989  staff    96B 10 24 20:05 vo
  • svc.go文件:做接口設(shè)計和定義
  • vo文件夾:定義接口入?yún)⒑统鰠⒌慕Y(jié)構(gòu)體
  • Dockerfile:用于docker鏡像打包

定義接口

我們打開svc.go文件看一下:

package service
import (
	"context"
	v3 "github.com/unionj-cloud/go-doudou/openapi/v3"
	"os"
	"usersvc/vo"
)
// Usersvc 用戶管理服務(wù)
// 調(diào)用用戶詳情、用戶分頁和上傳頭像接口需要帶上Bearer Token請求頭
// 用戶注冊、用戶登錄和下載頭像接口可以公開訪問,無須鑒權(quán)
type Usersvc interface {
	// PageUsers 用戶分頁接口
	// 展示如何定義POST請求且Content-Type為application/json的接口
	PageUsers(ctx context.Context,
		// 分頁請求參數(shù)
		query vo.PageQuery) (
		// 分頁結(jié)果
		data vo.PageRet,
		// 錯誤信息
		err error)
	// GetUser 用戶詳情接口
	// 展示如何定義帶查詢字符串參數(shù)的GET請求接口
	GetUser(ctx context.Context,
		// 用戶ID
		userId int) (
		// 用戶詳情
		data vo.UserVo,
		// 錯誤信息
		err error)
	// PublicSignUp 用戶注冊接口
	// 展示如何定義POST請求且Content-Type是application/x-www-form-urlencoded的接口
	PublicSignUp(ctx context.Context,
		// 用戶名
		username string,
		// 密碼
		password string,
		// 圖形驗證碼
		code string,
	) (
		// 成功返回OK
		data string, err error)
	// PublicLogIn 用戶登錄接口
	// 展示如何鑒權(quán)并返回token
	PublicLogIn(ctx context.Context,
		// 用戶名
		username string,
		// 密碼
		password string) (
		// token
		data string, err error)
	// UploadAvatar 上傳頭像接口
	// 展示如何定義文件上傳接口
	// 函數(shù)簽名的入?yún)⒗锉仨氁兄辽僖粋€[]*v3.FileModel或者*v3.FileModel類型的參數(shù)
	UploadAvatar(ctx context.Context,
		// 用戶頭像
		avatar *v3.FileModel) (
		// 成功返回OK
		data string, err error)
	// GetPublicDownloadAvatar 下載頭像接口
	// 展示如何定義文件下載接口
	// 函數(shù)簽名的出參里必須有且只有一個*os.File類型的參數(shù)
	GetPublicDownloadAvatar(ctx context.Context,
		// 用戶ID
		userId int) (
		// 文件二進制流
		data *os.File, err error)
}

以上代碼里每個方法都有注釋。請仔細閱讀。接口定義支持文檔注釋,只支持go語言常見的//注釋。這些注釋會作為OpenAPI3.0規(guī)范里的description參數(shù)值導(dǎo)出到生成的json文檔和go-doudou內(nèi)建的在線文檔里,下文會做演示。

生成代碼

執(zhí)行如下命令,即可生成啟動一個服務(wù)所需的全部代碼

go-doudou svc http --handler -c go --doc

解釋一下命令中的flag參數(shù):

  • --handler:表示需要生成http handler接口實現(xiàn),就是把解析http請求參數(shù)和編碼返回值的代碼都生成出來
  • -c:表示生成服務(wù)接口的客戶端SDK,目前只支持go。如果不需要生成客戶端SDK,可以不設(shè)置這個flag,因為相對其他代碼來說,生成過程比較耗時
  • --doc:表示生成OpenAPI3.0規(guī)范的json文檔 這行命令是筆者常用的命令,推薦大家也這樣使用。并且這行命令可以在每次修改了svc.go文件里的接口定義以后執(zhí)行,可以增量的生成代碼。規(guī)則是:
  • handler.go文件和OpenAPI3.0規(guī)范的json文檔總是會重新生成
  • handlerimpl.go文件和svcimpl.go文件只會增量生成,不會修改現(xiàn)有代碼
  • 其他文件都會先判斷同名文件是否存在,如果存在就跳過

為了確保依賴都已經(jīng)下載下來,最好再執(zhí)行一下這個命令:

go mod tidy

我們來看一下此時的項目結(jié)構(gòu):

?  usersvc git:(master) ? ll
total 296
-rw-r--r--  1 wubin1989  staff   707B 10 24 20:05 Dockerfile
drwxr-xr-x  3 wubin1989  staff    96B 10 24 23:10 client
drwxr-xr-x  3 wubin1989  staff    96B 10 24 23:10 cmd
drwxr-xr-x  3 wubin1989  staff    96B 10 24 23:10 config
drwxr-xr-x  3 wubin1989  staff    96B 10 24 23:10 db
-rw-r--r--  1 wubin1989  staff   514B 10 24 23:10 go.mod
-rw-r--r--  1 wubin1989  staff   115K 10 24 23:10 go.sum
-rw-r--r--  1 wubin1989  staff   1.7K 10 24 23:21 svc.go
-rw-r--r--  1 wubin1989  staff   1.6K 10 25 09:18 svcimpl.go
drwxr-xr-x  3 wubin1989  staff    96B 10 24 23:10 transport
-rwxr-xr-x  1 wubin1989  staff   5.9K 10 25 09:18 usersvc_openapi3.go
-rwxr-xr-x  1 wubin1989  staff   5.7K 10 25 09:18 usersvc_openapi3.json
drwxr-xr-x  3 wubin1989  staff    96B 10 24 23:07 vo
  • Dockerfile文件:用于打包docker鏡像
  • client包:生成的go客戶端代碼
  • cmd包:里面有main.go文件,用于啟動服務(wù)
  • config包:用于加載配置
  • db包:用于連接數(shù)據(jù)庫
  • svc.go文件:設(shè)計接口
  • svcimpl.go文件:里面有mock的接口實現(xiàn),后續(xù)在里面根據(jù)業(yè)務(wù)需求編寫真實的業(yè)務(wù)邏輯
  • transport包:里面是http handler接口和實現(xiàn),負責(zé)具體的接口入?yún)⒔馕龊统鰠⑿蛄谢?/li>
  • usersvc_openapi3.go文件:用于在線接口文檔功能
  • usersvc_openapi3.json文件:遵循OpenAPI 3.0規(guī)范的接口文檔
  • vo包:里面是接口的入?yún)⒑统鰠⒔Y(jié)構(gòu)體類型

啟動服務(wù)

go-doudou svc run

我們可以看到如下輸出:

?  usersvc git:(master) ? go-doudou svc run                       
INFO[2021-12-28 22:39:35] Initializing logging reporter                
INFO[2021-12-28 22:39:35] ================ Registered Routes ================ 
INFO[2021-12-28 22:39:35] +----------------------+--------+-------------------------+ 
INFO[2021-12-28 22:39:35] |         NAME         | METHOD |         PATTERN         | 
INFO[2021-12-28 22:39:35] +----------------------+--------+-------------------------+ 
INFO[2021-12-28 22:39:35] | PageUsers            | POST   | /page/users             | 
INFO[2021-12-28 22:39:35] | User                 | GET    | /user                   | 
INFO[2021-12-28 22:39:35] | PublicSignUp         | POST   | /public/sign/up         | 
INFO[2021-12-28 22:39:35] | PublicLogIn          | POST   | /public/log/in          | 
INFO[2021-12-28 22:39:35] | UploadAvatar         | POST   | /upload/avatar          | 
INFO[2021-12-28 22:39:35] | PublicDownloadAvatar | GET    | /public/download/avatar | 
INFO[2021-12-28 22:39:35] | GetDoc               | GET    | /go-doudou/doc          | 
INFO[2021-12-28 22:39:35] | GetOpenAPI           | GET    | /go-doudou/openapi.json | 
INFO[2021-12-28 22:39:35] | Prometheus           | GET    | /go-doudou/prometheus   | 
INFO[2021-12-28 22:39:35] | GetRegistry          | GET    | /go-doudou/registry     | 
INFO[2021-12-28 22:39:35] +----------------------+--------+-------------------------+ 
INFO[2021-12-28 22:39:35] =================================================== 
INFO[2021-12-28 22:39:35] Started in 233.424µs                         
INFO[2021-12-28 22:39:35] Http server is listening on :6060      

當(dāng)出現(xiàn)"Http server is listening on :6060"時,表示服務(wù)已經(jīng)啟動,并且我們已經(jīng)有了mock的服務(wù)接口實現(xiàn)。例如,我們可以執(zhí)行如下命令請求/user接口,看看返回什么數(shù)據(jù):

?  usersvc git:(master) ? http http://localhost:6060/user
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 109
Content-Type: application/json; charset=UTF-8
Date: Mon, 01 Nov 2021 15:21:10 GMT
Vary: Accept-Encoding
{
    "data": {
        "Dept": "ZkkCmcLU",
        "Id": -1941954111002502016,
        "Name": "aiMtQ",
        "Phone": "XMAqXf"
    }
}

此時你可能注意到返回的數(shù)據(jù)的字段名稱是首字母大寫的,這可能不是你想要的。在vo包下的vo.go文件里有一行go generate命令:

//go:generate go-doudou name --file $GOFILE

這行命令里用到了go-doudou框架內(nèi)置的一個工具name。它可以根據(jù)指定的命名規(guī)則生成結(jié)構(gòu)體字段后面的json標(biāo)簽。默認生成策略是首字母小寫的駝峰命名策略,同時支持蛇形命名。未導(dǎo)出的字段會跳過,只修改導(dǎo)出字段的json標(biāo)簽。命令行執(zhí)行命令:

go generate ./...

然后重啟服務(wù),請求/user接口,可以看到字段名稱已經(jīng)變成首字母小寫的駝峰命名了。

?  usersvc git:(master) ? http http://localhost:6060/user
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 114
Content-Type: application/json; charset=UTF-8
Date: Tue, 02 Nov 2021 08:25:39 GMT
Vary: Accept-Encoding
{
    "data": {
        "dept": "wGAEEeveHp",
        "id": -816946940349962228,
        "name": "hquwOKl",
        "phone": "AriWmKYB"
    }
}

關(guān)于name工具的更多用法,請參考文檔。 此時,因為vo包里的結(jié)構(gòu)體修改了json標(biāo)簽,所以O(shè)penAPI文檔需要重新生成,否則在線文檔里的字段名稱還是修改前的。需要執(zhí)行如下命令:

go-doudou svc http --doc

然后我們重啟一下服務(wù),在地址欄輸入http://localhost:6060/go-doudou/doc, 再輸入http basic用戶名admin,密碼admin,看一下在線文檔是什么效果:

在線文檔里的接口說明和參數(shù)說明都取自svc.go的接口方法注釋和參數(shù)注釋。

數(shù)據(jù)庫和表結(jié)構(gòu)準(zhǔn)備

為了支持中文字符,需先在根目錄下創(chuàng)建mysql配置文件my/custom.cnf,貼進去如下內(nèi)容:

[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
[mysqld]
character_set_server=utf8mb4
collation-server=utf8mb4_general_ci
default-authentication-plugin=mysql_native_password
init_connect='SET NAMES utf8mb4'

在根目錄下創(chuàng)建數(shù)據(jù)庫初始化腳本sqlscripts/init.sql,貼進去如下內(nèi)容:

CREATE SCHEMA `tutorial` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;
CREATE TABLE `tutorial`.`t_user`
(
    `id`        INT         NOT NULL AUTO_INCREMENT,
    `username`  VARCHAR(45) NOT NULL COMMENT '用戶名',
    `password`  VARCHAR(60) NOT NULL COMMENT '密碼',
    `name`      VARCHAR(45) NOT NULL COMMENT '真實姓名',
    `phone`     VARCHAR(45) NOT NULL COMMENT '手機號',
    `dept`      VARCHAR(45) NOT NULL COMMENT '所屬部門',
    `create_at` DATETIME    NULL DEFAULT current_timestamp,
    `update_at` DATETIME    NULL DEFAULT current_timestamp on update current_timestamp,
    `delete_at` DATETIME    NULL,
    PRIMARY KEY (`id`)
);
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (2, 'peter', '$2a$14$VaQLa/GbLAhRZvvTlgE8OOQgsBY4RDAJC5jkz13kjP9RlntdKBZVW', '張三豐', '13552053960', '技術(shù)部', '2021-12-28 06:41:00', '2021-12-28 14:59:20', null, 'out/wolf-wolves-snow-wolf-landscape-985ca149f06cd03b9f0ed8dfe326afdb.jpg');
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (4, 'john', '$2a$14$AKCs.u9vFUOCe5VwcmdfwOAkeiDtQYEgIB/nSU8/eemYwd91.qU.i', '李世民', '13552053961', '行政部', '2021-12-28 12:12:32', '2021-12-28 14:59:20', null, '');
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (5, 'lucy', '$2a$14$n0.l54axUqnKGagylQLu7ee.yDrtLubxzM1qmOaHK9Ft2P09YtQUS', '朱元璋', '13552053962', '銷售部', '2021-12-28 12:13:17', '2021-12-28 14:59:20', null, '');
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (6, 'jack', '$2a$14$jFCwiZHcD7.DL/teao.Dl.HAFwk8wM2f1riH1fG2f52WYKqSiGZlC', '張無忌', '', '總裁辦', '2021-12-28 12:14:19', '2021-12-28 14:59:20', null, '');

在根目錄下創(chuàng)建docker-compose.yml文件,貼進入如下內(nèi)容:

version: '3.9'
services:
  db:
    container_name: db
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 1234
    ports:
      - 3306:3306
    volumes:
      - $PWD/my:/etc/mysql/conf.d
      - $PWD/sqlscripts:/docker-entrypoint-initdb.d
    networks:
      - tutorial
networks:
  tutorial:
    driver: bridge

在根目錄下執(zhí)行docker compose命令,即可啟動mysql數(shù)據(jù)庫容器:

docker-compose -f docker-compose.yml up -d

可以通過docker ps命令查看正在運行的容器

?  usersvc git:(master) ? docker ps        
CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS          PORTS                                                  NAMES
df6af6362c41   mysql:5.7   "docker-entrypoint.s…"   13 minutes ago   Up 13 minutes   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   db

生成domain和dao層代碼

因為我們初始化的schema名稱是tutorial,所以我們先要把.env文件里的環(huán)境變量DB_SCHEMA的值改成tutorial

DB_SCHEMA=tutorial

執(zhí)行如下命令,生成domain和dao層代碼:

go-doudou ddl -r --dao --pre=t_

解釋一下:

  • -r:表示從數(shù)據(jù)庫表結(jié)構(gòu)生成go結(jié)構(gòu)體
  • --dao:表示生成dao層代碼
  • --pre:表示表名稱有前綴t_ 此時,你可以看到項目里多了兩個目錄:

具體用法請參考ddl文檔 這里我們看一下dao/base.go文件里提供了哪些CRUD方法,后面實現(xiàn)具體業(yè)務(wù)邏輯的時候會用到:

package dao
import (
	"context"
	"github.com/unionj-cloud/go-doudou/ddl/query"
)
type Base interface {
	Insert(ctx context.Context, data interface{}) (int64, error)
	Upsert(ctx context.Context, data interface{}) (int64, error)
	UpsertNoneZero(ctx context.Context, data interface{}) (int64, error)
	DeleteMany(ctx context.Context, where query.Q) (int64, error)
	Update(ctx context.Context, data interface{}) (int64, error)
	UpdateNoneZero(ctx context.Context, data interface{}) (int64, error)
	UpdateMany(ctx context.Context, data interface{}, where query.Q) (int64, error)
	UpdateManyNoneZero(ctx context.Context, data interface{}, where query.Q) (int64, error)
	Get(ctx context.Context, id interface{}) (interface{}, error)
	SelectMany(ctx context.Context, where ...query.Q) (interface{}, error)
	CountMany(ctx context.Context, where ...query.Q) (int, error)
	PageMany(ctx context.Context, page query.Page, where ...query.Q) (query.PageRet, error)
}

再修改一下svcimpl.go文件的UsersvcImpl結(jié)構(gòu)體

type UsersvcImpl struct {
	conf *config.Config
	db   *sqlx.DB
}

以及NewUsersvc方法

func NewUsersvc(conf *config.Config, db *sqlx.DB) Usersvc {
	return &UsersvcImpl{
		conf,
		db,
	}
}

生成的main方法里已經(jīng)為我們注入了mysql連接實例,所以不用改

svc := service.NewUsersvc(conf, conn)

后面我們直接在接口實現(xiàn)里面調(diào)用UsersvcImpl結(jié)構(gòu)體的db屬性即可

用戶注冊接口

修改domain

因為通常來說用戶名都必須是唯一的,所以我們需要改一下domain/user.go文件:

Username string     `dd:"type:varchar(45);extra:comment '用戶名';unique"`

再執(zhí)行ddl命令

go-doudou ddl --pre=t_

這行命令沒有-r參數(shù)了,表示從go結(jié)構(gòu)體更新到表結(jié)構(gòu)。

PublicSignUp方法實現(xiàn)

要實現(xiàn)注冊邏輯,我們需要先給dao層代碼加一個方法CheckUsernameExists,判斷一下傳進來的用戶名是否已經(jīng)被注冊。先改一下dao/userdao.go文件

package dao
import "context"
type UserDao interface {
	Base
	CheckUsernameExists(ctx context.Context, username string) (bool, error)
}

再新建一個文件dao/userdaoimplext.go文件,加入如下代碼

package dao
import (
	"context"
	"github.com/unionj-cloud/go-doudou/ddl/query"
	"usersvc/domain"
)
func (receiver UserDaoImpl) CheckUsernameExists(ctx context.Context, username string) (bool, error) {
	many, err := receiver.SelectMany(ctx, query.C().Col("username").Eq(username))
	if err != nil {
		return false, err
	}
	users := many.([]domain.User)
	if len(users) > 0 {
		return true, nil
	}
	return false, nil
}

這樣就實現(xiàn)了對生成的dao層代碼的自定義擴展。以后如果user實體的字段新增或者減少,只需要刪除userdaosql.go文件,再次執(zhí)行go-doudou ddl --dao --pre=t_命令,重新生成userdaosql.go文件即可,已存在的dao層文件不會被修改。 然后就是SignUp方法的具體實現(xiàn)了

func (receiver *UsersvcImpl) PublicSignUp(ctx context.Context, username string, password string, code string) (data string, err error) {
	hashPassword, _ := lib.HashPassword(password)
	userDao := dao.NewUserDao(receiver.db)
	var exists bool
	exists, err = userDao.CheckUsernameExists(ctx, username)
	if err != nil {
		panic(err)
	}
	if exists {
		panic(lib.ErrUsernameExists)
	}
	_, err = userDao.Insert(ctx, domain.User{
		Username: username,
		Password: hashPassword,
	})
	if err != nil {
		panic(err)
	}
	return "OK", nil
}

遇到報錯,可以直接panic,也可以return "", lib.ErrUsernameExists。因為已經(jīng)加了ddhttp.Recover中間件,可以自動從panic里恢復(fù),并返回錯誤信息給前端。需要注意的是,http狀態(tài)碼為500,不是200。只要從接口方法里返回了error類型的參數(shù),生成的http handler代碼里默認設(shè)置的http狀態(tài)碼就是500。如果想自定義修改默認生成的http handler里的代碼,是完全可以的。當(dāng)有接口定義新增或者修改的時候,再次執(zhí)行命令go-doudou svc http --handler -c go --doc不會覆蓋已存在的代碼,只會增量生成代碼。

Postman測試

測試一下接口,這是第一次請求

這是第二次請求

用戶登錄接口

PublicLogIn方法實現(xiàn)

func (receiver *UsersvcImpl) PublicLogIn(ctx context.Context, username string, password string) (data string, err error) {
	userDao := dao.NewUserDao(receiver.db)
	many, err := userDao.SelectMany(ctx, query.C().Col("username").Eq(username).And(query.C().Col("delete_at").IsNull()))
	if err != nil {
		return "", err
	}
	users := many.([]domain.User)
	if len(users) == 0 || !lib.CheckPasswordHash(password, users[0].Password) {
		panic(lib.ErrUsernameOrPasswordIncorrect)
	}
	now := time.Now()
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"userId": users[0].Id,
		"exp":    now.Add(10 * time.Minute).Unix(),
		//"iat":    now.Unix(),
		//"nbf":    now.Unix(),
	})
	return token.SignedString(receiver.conf.JWTConf.Secret)
}

這段代碼的邏輯是先根據(jù)入?yún)sername查出來數(shù)據(jù)庫中的用戶,如果沒查到或者密碼不對,返回“用戶名或密碼錯誤”的報錯,如果密碼對了,則簽發(fā)token返回。用的jwt庫是golang-jwt/jwt

Postman測試

上傳頭像接口

修改domain

表里面少了一個avatar字段,現(xiàn)在我們加上:

Avatar   string     `dd:"type:varchar(255);extra:comment '用戶頭像'"`

因為是新增了字段,所以要先刪除dao/userdaosql.go文件,再執(zhí)行ddl命令

go-doudou ddl --dao --pre=t_

如果增刪的字段比較多,涉及多個實體,可以通過如下命令一次刪掉所有*sql.go文件,再重新生成

rm -rf dao/*sql.go

修改.env配置

加入三行配置。JWT_為前綴的是JWT token校驗相關(guān)的配置。Biz_為前綴的是實際業(yè)務(wù)相關(guān)的配置。

JWT_SECRET=secret
JWT_IGNORE_URL=/public/sign/up,/public/log/in,/public/get/download/avatar,/public/**
BIZ_OUTPUT=out

JWT_IGNORE_URL的值設(shè)置成/public/**就可以了,我都列上,是想說明這里同時支持通配符匹配和完整匹配。 同時,config/config.go文件也需要相應(yīng)的改動。當(dāng)然也可以直接調(diào)用os.Getenv方法。

package config
import (
	"github.com/kelseyhightower/envconfig"
	"github.com/sirupsen/logrus"
)
type Config struct {
	DbConf  DbConfig
	JWTConf JWTConf
	BizConf BizConf
}
type BizConf struct {
	Output string
}
type JWTConf struct {
	Secret    []byte
	IgnoreUrl []string `split_words:"true"`
}
type DbConfig struct {
	Driver  string `default:"mysql"`
	Host    string `default:"localhost"`
	Port    string `default:"3306"`
	User    string
	Passwd  string
	Schema  string
	Charset string `default:"utf8mb4"`
}
func LoadFromEnv() *Config {
	var dbconf DbConfig
	err := envconfig.Process("db", &dbconf)
	if err != nil {
		logrus.Panicln("Error processing env", err)
	}
	var jwtConf JWTConf
	err = envconfig.Process("jwt", &jwtConf)
	if err != nil {
		logrus.Panicln("Error processing env", err)
	}
	var bizConf BizConf
	err = envconfig.Process("biz", &bizConf)
	if err != nil {
		logrus.Panicln("Error processing env", err)
	}
	return &Config{
		dbconf,
		jwtConf,
		bizConf,
	}
}

JWT校驗中間件

因為go-doudou的http router采用的是gorilla/mux,所以與gorilla/mux的middleware是完全兼容的,自定義中間件的寫法也是完全一樣的。

package middleware
import (
	"context"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gobwas/glob"
	"net/http"
	"os"
	"strings"
)
type ctxKey int
const userIdKey ctxKey = ctxKey(0)
func NewContext(ctx context.Context, id int) context.Context {
	return context.WithValue(ctx, userIdKey, id)
}
func FromContext(ctx context.Context) (int, bool) {
	userId, ok := ctx.Value(userIdKey).(int)
	return userId, ok
}
func Jwt(inner http.Handler) http.Handler {
	g := glob.MustCompile(fmt.Sprintf("{%s}", os.Getenv("JWT_IGNORE_URL")))
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if g.Match(r.RequestURI) {
			inner.ServeHTTP(w, r)
			return
		}
		authHeader := r.Header.Get("Authorization")
		tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
			}
			return []byte(os.Getenv("JWT_SECRET")), nil
		})
		if err != nil || !token.Valid {
			w.WriteHeader(401)
			w.Write([]byte("Unauthorised.\n"))
			return
		}
		claims := token.Claims.(jwt.MapClaims)
		if userId, exists := claims["userId"]; !exists {
			w.WriteHeader(401)
			w.Write([]byte("Unauthorised.\n"))
			return
		} else {
			inner.ServeHTTP(w, r.WithContext(NewContext(r.Context(), int(userId.(float64)))))
		}
	})
}

UploadAvatar方法實現(xiàn)

func (receiver *UsersvcImpl) UploadAvatar(ctx context.Context, avatar *v3.FileModel) (data string, err error) {
	defer avatar.Close()
	_ = os.MkdirAll(receiver.conf.BizConf.Output, os.ModePerm)
	out := filepath.Join(receiver.conf.BizConf.Output, avatar.Filename)
	var f *os.File
	f, err = os.OpenFile(out, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	_, err = io.Copy(f, avatar.Reader)
	if err != nil {
		panic(err)
	}
	userId, _ := middleware.FromContext(ctx)
	userDao := dao.NewUserDao(receiver.db)
	_, err = userDao.UpdateNoneZero(ctx, domain.User{
		Id:     userId,
		Avatar: out,
	})
	if err != nil {
		panic(err)
	}
	return "OK", nil
}

這里需要注意的是,defer avatar.Close()這行代碼一定要盡早寫上,這是釋放文件描述符資源的代碼。

下載頭像接口

GetPublicDownloadAvatar方法實現(xiàn)

func (receiver *UsersvcImpl) GetPublicDownloadAvatar(ctx context.Context, userId int) (data *os.File, err error) {
	userDao := dao.NewUserDao(receiver.db)
	var get interface{}
	get, err = userDao.Get(ctx, userId)
	if err != nil {
		panic(err)
	}
	return os.Open(get.(domain.User).Avatar)
}

用戶詳情接口

GetUser方法實現(xiàn)

func (receiver *UsersvcImpl) GetUser(ctx context.Context, userId int) (data vo.UserVo, err error) {
	userDao := dao.NewUserDao(receiver.db)
	var get interface{}
	get, err = userDao.Get(ctx, userId)
	if err != nil {
		panic(err)
	}
	user := get.(domain.User)
	return vo.UserVo{
		Id:       user.Id,
		Username: user.Username,
		Name:     user.Name,
		Phone:    user.Phone,
		Dept:     user.Dept,
	}, nil
}

Postman測試

用戶分頁接口

導(dǎo)入測試數(shù)據(jù)

INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (2, 'peter', '$2a$14$VaQLa/GbLAhRZvvTlgE8OOQgsBY4RDAJC5jkz13kjP9RlntdKBZVW', '張三豐', '13552053960', '技術(shù)部', '2021-12-28 06:41:00', '2021-12-28 14:59:20', null, 'out/wolf-wolves-snow-wolf-landscape-985ca149f06cd03b9f0ed8dfe326afdb.jpg');
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (4, 'john', '$2a$14$AKCs.u9vFUOCe5VwcmdfwOAkeiDtQYEgIB/nSU8/eemYwd91.qU.i', '李世民', '13552053961', '行政部', '2021-12-28 12:12:32', '2021-12-28 14:59:20', null, '');
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (5, 'lucy', '$2a$14$n0.l54axUqnKGagylQLu7ee.yDrtLubxzM1qmOaHK9Ft2P09YtQUS', '朱元璋', '13552053962', '銷售部', '2021-12-28 12:13:17', '2021-12-28 14:59:20', null, '');
INSERT INTO tutorial.t_user (id, username, password, name, phone, dept, create_at, update_at, delete_at, avatar) VALUES (6, 'jack', '$2a$14$jFCwiZHcD7.DL/teao.Dl.HAFwk8wM2f1riH1fG2f52WYKqSiGZlC', '張無忌', '', '總裁辦', '2021-12-28 12:14:19', '2021-12-28 14:59:20', null, '');

PageUsers方法實現(xiàn)

func (receiver *UsersvcImpl) PageUsers(ctx context.Context, pageQuery vo.PageQuery) (data vo.PageRet, err error) {
	userDao := dao.NewUserDao(receiver.db)
	var q query.Q
	q = query.C().Col("delete_at").IsNull()
	if stringutils.IsNotEmpty(pageQuery.Filter.Name) {
		q = q.And(query.C().Col("name").Like(fmt.Sprintf(`%s%%`, pageQuery.Filter.Name)))
	}
	if stringutils.IsNotEmpty(pageQuery.Filter.Dept) {
		q = q.And(query.C().Col("dept").Eq(pageQuery.Filter.Dept))
	}
	var page query.Page
	if len(pageQuery.Page.Orders) > 0 {
		for _, item := range pageQuery.Page.Orders {
			page = page.Order(query.Order{
				Col:  item.Col,
				Sort: sortenum.Sort(item.Sort),
			})
		}
	}
	if pageQuery.Page.PageNo == 0 {
		pageQuery.Page.PageNo = 1
	}
	page = page.Limit((pageQuery.Page.PageNo-1)*pageQuery.Page.Size, pageQuery.Page.Size)
	var ret query.PageRet
	ret, err = userDao.PageMany(ctx, page, q)
	if err != nil {
		panic(err)
	}
	var items []vo.UserVo
	for _, item := range ret.Items.([]domain.User) {
		var userVo vo.UserVo
		_ = copier.DeepCopy(item, &userVo)
		items = append(items, userVo)
	}
	data = vo.PageRet{
		Items:    items,
		PageNo:   ret.PageNo,
		PageSize: ret.PageSize,
		Total:    ret.Total,
		HasNext:  ret.HasNext,
	}
	return data, nil
}

Postman測試

服務(wù)部署

最后介紹一下docker-compose部署服務(wù) 首先修改Dockerfile

FROM golang:1.16.6-alpine AS builder
ENV GO111MODULE=on
ARG user
ENV HOST_USER=$user
ENV GOPROXY=https://goproxy.cn,direct
WORKDIR /repo
ADD go.mod .
ADD go.sum .
ADD . ./
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --no-cache bash tzdata
ENV TZ="Asia/Shanghai"
RUN go mod vendor
RUN export GDD_VER=$(go list -mod=vendor -m -f '{{ .Version }}' github.com/unionj-cloud/go-doudou) && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -ldflags="-X 'github.com/unionj-cloud/go-doudou/svc/config.BuildUser=$HOST_USER' -X 'github.com/unionj-cloud/go-doudou/svc/config.BuildTime=$(date)' -X 'github.com/unionj-cloud/go-doudou/svc/config.GddVer=$GDD_VER'" -mod vendor -o api cmd/main.go
ENTRYPOINT ["/repo/api"]

然后修改docker-compose.yml

version: '3.9'
services:
  db:
    container_name: db
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 1234
    ports:
      - 3306:3306
    volumes:
      - $PWD/my:/etc/mysql/conf.d
      - $PWD/sqlscripts:/docker-entrypoint-initdb.d
    networks:
      - tutorial
  usersvc:
    container_name: usersvc
    build:
      context: .
    environment:
      - GDD_BANNER=off
      - GDD_PORT=6060
      - DB_HOST=db
    expose:
      - "6060"
    ports:
      - "6060:6060"
    networks:
      - tutorial
    depends_on:
      - db
networks:
  tutorial:
    driver: bridge

最后執(zhí)行命令

docker-compose -f docker-compose.yml up -d

如果usersvc容器沒有啟動成功,可能是因為db容器還沒有完全啟動,可以多執(zhí)行幾遍上面的命令。

總結(jié)

到這里,我們達到了全部的學(xué)習(xí)目標(biāo),也實現(xiàn)了需求清單中的全部接口。教程的全部源碼都在這里。

以上就是go doudou開發(fā)單體RESTful服務(wù)快速上手教程的詳細內(nèi)容,更多關(guān)于go doudou單體RESTful服務(wù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言中g(shù)oroutine的使用

    Go語言中g(shù)oroutine的使用

    本文主要介紹了Go語言中g(shù)oroutine的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Go語言中關(guān)于set的實現(xiàn)思考分析

    Go語言中關(guān)于set的實現(xiàn)思考分析

    Go?開發(fā)過程中有時我們需要集合(set)這種容器,但?Go?本身未內(nèi)置這種數(shù)據(jù)容器,故常常我們需要自己實現(xiàn),下面我們就來看看具體有哪些實現(xiàn)方法吧
    2024-01-01
  • golang日志包logger的用法詳解

    golang日志包logger的用法詳解

    這篇文章主要介紹了golang日志包logger的用法詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • gorm update傳入struct對象,零值字段不更新的解決方案

    gorm update傳入struct對象,零值字段不更新的解決方案

    這篇文章主要介紹了gorm update傳入struct對象,零值字段不更新的解決方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 初探Golang數(shù)據(jù)結(jié)構(gòu)之Slice的使用

    初探Golang數(shù)據(jù)結(jié)構(gòu)之Slice的使用

    在學(xué)習(xí)Go語言時,一直對數(shù)組和切片的使用場景好奇,不明白為什么推薦使用切片來代替數(shù)組,所以本文就來和大家梳理一下Slice切片的相關(guān)知識吧
    2023-09-09
  • Go語言斷言和類型查詢的實現(xiàn)

    Go語言斷言和類型查詢的實現(xiàn)

    Go語言變量類型包含基礎(chǔ)類型和復(fù)合類型,類型斷言一般是對基礎(chǔ)類型的處理,本文主要介紹了Go語言斷言和類型查詢的實現(xiàn),感興趣的可以了解一下
    2024-01-01
  • Go語言中比較兩個map[string]interface{}是否相等

    Go語言中比較兩個map[string]interface{}是否相等

    本文主要介紹了Go語言中比較兩個map[string]interface{}是否相等,我們可以將其轉(zhuǎn)化成順序一樣的 slice ,然后再轉(zhuǎn)化未json,具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • 詳解go 中的 fmt 占位符

    詳解go 中的 fmt 占位符

    這篇文章主要介紹了go 中的 fmt 占位符,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-01-01
  • go env環(huán)境變量配置的使用

    go env環(huán)境變量配置的使用

    在安裝和使用Go時,必須要正確地配置環(huán)境變量,本文主要介紹了go env環(huán)境變量配置的使用,具有一定的參考價值,感興趣的可以了解一下
    2023-11-11
  • Go語言中l(wèi)og日志庫的介紹

    Go語言中l(wèi)og日志庫的介紹

    本文給大家介紹Go語言中l(wèi)og日志庫的概念使用技巧,log包定義了Logger類型,該類型提供了一些格式化輸出的方法,本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-10-10

最新評論