gf2-demo
gf2-demo copied to clipboard
A Go backend project demo using GoFrameV2. The goal of the project is to empower developers to concentrate on the development of business logic while ensuring swift and standardized project delivery.
gf2-demo
gf2-demo
æ¯ä¸ä¸ªåºäº GoFrameV2 ç¨æ¥å¿«éå¼åå端æå¡çèææ¶, ç®æ 使å¼åè
åªéå
³æ³¨ä¸å¡é»è¾çç¼å, å¿«éä¸è§èå°äº¤ä»é¡¹ç®.
ð Features
- ä¼åå·¥ç¨ç®å½ç»æ, 使æ¯æå¤ä¸ªå¯æ§è¡å½ä»¤
- å¤ç¯å¢ç®¡ç: å¼åç¯å¢ãæµè¯ç¯å¢ãç产ç¯å¢
- ç¼è¯çäºè¿å¶æ件å¯æå°å½ååºç¨ççæ¬ä¿¡æ¯
- ä¸é´ä»¶ç»ä¸æ¦æªååº, è§èååºæ ¼å¼, è§èä¸å¡é误ç
- å®å HTTP æå¡è®¿é®æ¥å¿ãHTTP æå¡é误æ¥å¿ãSQL æ¥å¿ãå¼åè æå°çæ¥å¿ãå ¶ä»å¯æ§è¡å½ä»¤çæ¥å¿é ç½®
- å°è£
Redis
常ç¨å·¥å ·åº:rediscache
,redislock
,redismq
,redisdelaymq
,redispubsub
- éè¿å·¥å ·èªå¨çææ°æ®åºå±ãæå¡æ¥å£å±ãæ§å¶å¨å±ä»£ç
- å®æ´çå¢å æ¹æ¥æ¥å£ç¤ºä¾åå®åçå¼åæµç¨ææ¡£, 帮å©å¼åè å¿«éä¸æ
- 项ç®é¨ç½²éµå¾ªä¸å¯ååºç¡è®¾æ½åå, ä¸è®ºæ¯ä¼ ç»åä½é¨ç½²è¿æ¯å®¹å¨äºé¨ç½²æ¹å¼
- éè¿
Makefile
管ç项ç®:make run
,make build
,make dao
,make service
ç - å¢å
golangci-lint
é ç½®æ件.golangci.yml
, ç»ä¸å¢é代ç é£æ ¼, ä¿éå¢é代ç è´¨é - éå个人å¼åè é«è´¨éå®æ项ç®, ä¹éåå¢éç»ä¸å端ææ¯æ¡æ¶, è§èé«æ管ç
ð Quick Start
å®è£
git clone --depth 1 [email protected]:windvalley/gf2-demo.git
cd gf2-demo
# å®è£
gf
make cli
# 导å
¥æµè¯ç¨mysqlåºè¡¨
mysql -uroot -p'123456' < manifest/sql/gf2_demo.sql
请æåå®è£ Go ç¯å¢, è¦æ± Go çæ¬:
1.15+
çæ´æ°(Live reload)
å¼åç¯å¢ä¸ä½¿ç¨.
cd gf2-demo
# è¿è¡ gf2-demo-api
make run
# è¿è¡ gf2-demo-cli
make run.cli
é»è®¤å è½½é ç½®æ件:
manifest/config/config.yaml
访é®æµè¯
$ curl -X GET -i 'localhost:9000/v1/demo/windvalley'
HTTP/1.1 200 OK
Content-Type: application/json
Server: GoFrame HTTP Server
Trace-Id: 506dccff4a08431731f5d0259180c3b8
Date: Sun, 12 Feb 2023 09:03:24 GMT
Content-Length: 130
{"code":"OK","message":"","traceid": "506dccff4a08431731f5d0259180c3b8","data":{"id":1,"fielda":"windvalley","created_at":"2008-08-08 08:08:08","updated_at":"2008-08-08 08:08:08"}}
ç¼è¯äºè¿å¶æ件
cd gf2-demo
# ç¼è¯ gf2-demo-api
make build
# ç¼è¯ gf2-demo-cli
make build.cli
ä¼çæå¦ä¸äºè¿å¶æ件:
bin
âââ darwin_amd64
â  âââ gf2-demo-api
â  âââ gf2-demo-cli
âââ linux_amd64
âââ gf2-demo-api
âââ gf2-demo-cli
æå°å¸®å©ä¿¡æ¯
$ bin/darwin_amd64/gf2-demo-api -h
USAGE
gf2-demo-api [OPTION]
OPTION
-v, --version print version info
-c, --config config file (default config.yaml)
-h, --help more information about this command
EXAMPLE
Dev:
./gf2-demo-api
Test:
./gf2-demo-api -c config.test.yaml
or
GF_GCFG_FILE=config.test.yaml GF_GERROR_BRIEF=true ./gf2-demo-api
Prod:
./gf2-demo-api -c config.prod.yaml
or
GF_GCFG_FILE=config.prod.yaml GF_GERROR_BRIEF=true ./gf2-demo-api
DESCRIPTION
An API server demo using GoFrame V2
Find more information at: https://github.com/windvalley/gf2-demo
ð Documentation
- å·¥ç¨ç®å½
- ç¯å¢ç®¡ç
- å¤å½ä»¤ç®¡ç
- é误ç 管ç
- æ¥å¿ç®¡ç
- é¾è·¯è·è¸ª
- çæ¬ç®¡ç
- Redis
- å¼åæµç¨
- 代ç è´¨é
-
项ç®é¨ç½²
- Systemctl
- Supervisor
- Docker
- ä¼é å ³éæµè¯
- ä½¿ç¨ Makefile 管ç项ç®
- åæ´é¡¹ç®å称
å·¥ç¨ç®å½ â
âââ CHANGELOG.md # çæ¬åæ´ç®¡ç
âââ Dockerfile # ç¨äºå¶ä½å®¹å¨éå
âââ Makefile # ç¨äºé¡¹ç®ç®¡ç
âââ README.md # 项ç®ææ¡£
âââ api # 对å¤æ¥å£å®ä¹: 对å¤æä¾æå¡çè¾å
¥/è¾åºæ°æ®ç»æå®ä¹, è·¯ç±pathå®ä¹, æ°æ®æ ¡éªç
â  âââ api.go # æ¥å£æ¨¡åéç¨ç»æ
â  âââ demo # demo模å
â  âââ demo.go # demo模åçapi interface, ç±make ctrlèªå¨çæ
â  âââ v1 # çæ¬æ§å¶
â  âââ demo.go # å¼åè
æç
§è§èç¼åçæ¥å£æ件, make ctrlä¼æ ¹æ®æ¬æ件èªå¨çæcontroller代ç
âââ bin # make build å make build.cli çæçäºè¿å¶å¯æ§è¡æ件æå¨ç®å½, ä¸è¦æ交å°ä»åº
â  âââ darwin_amd64
â  â  âââ gf2-demo-api
â  â  âââ gf2-demo-cli
â  âââ linux_amd64
â  âââ gf2-demo-api
â  âââ gf2-demo-cli
âââ cmd # 项ç®çå¯æ§è¡æ件å
¥å£
â  âââ gf2-demo-api # APIæå¡
â  â  âââ gf2-demo-api.go # 注æ: ç¼è¯æ¶ä¼ä½¿ç¨å
¥å£æ件çååä½ä¸ºäºè¿å¶æ件å称
â  âââ gf2-demo-cli # 项ç®çå
¶ä»å¯æ§è¡æå¡, æ¯å¦å¯ä»¥æ¯: å½ä»¤è¡å·¥å
·æDaemonåå°ç¨åºçå项ç®ç¸å
³çè¾
å©åºç¨
â  âââ gf2-demo-cli.go
âââ hack # åæ¾é¡¹ç®å¼åå·¥å
·ãèæ¬çå
容. ä¾å¦: gfå·¥å
·çé
ç½®, åç§shell/batèæ¬çæ件
â  âââ config.yaml # gf å·¥å
·çé
ç½®æ件, æ¯å¦ gf gen/gf build çä¼ä½¿ç¨è¿éçé
ç½®å
容
â  âââ change_project_name.sh # å°ç¤ºä¾é¡¹ç®å称æ¹æä½ èªå·±ç项ç®å称
âââ internal
â  âââ cmd # 对åºå¤å± cmd ç®å½
â  â  âââ apiserver # å¯¹åº gf2-demo-api, å½ä»¤é
ç½®, è·¯ç±æ³¨åç
â  â  â  âââ apiserver.go
â  â  âââ cli # å¯¹åº gf2-demo-cli, å½ä»¤é
ç½®ç
â  â  âââ cli.go
â  âââ codes # ä¸å¡é误ç å®ä¹ç»´æ¤
â  â  âââ biz_codes.go
â  â  âââ codes.go
â  âââ consts # 项ç®ææéç¨å¸¸éå®ä¹
â  â  âââ consts.go
â  âââ controller # æ§å¶å¨å±: æ¥æ¶/解æç¨æ·è¾å
¥åæ°çå
¥å£
â  â  âââ demo # æ¬ç®å½åç®å½ä¸æ件ç±make ctrlèªå¨çæ, é¤äºdemo_new.goä¸è½ä¿®æ¹, å
¶ä»æ件åéè¦æ·»å å
·ä½çæ§å¶å¨å®ç°(æ¯å¦åserviceèå¨)
â  â  âââ demo.go
â  â  âââ demo_new.go
â  â  âââ demo_v1_create.go
â  â  âââ demo_v1_delete.go
â  â  âââ demo_v1_get_list.go
â  â  âââ demo_v1_get_one.go
â  â  âââ demo_v1_update.go
â  âââ dao # æ°æ®è®¿é®å¯¹è±¡, ç±make daoèªå¨çæ. è¿æ¯ä¸å±æ½è±¡å¯¹è±¡, ç¨äºååºå±æ°æ®åºäº¤äº, ä»
å
å«æåºç¡ç CURD æ¹æ³. daoå±éè¿æ¡æ¶çORMæ½è±¡å±ç»ä»¶ä¸åºå±çå®çæ°æ®åºäº¤äº
â  â  âââ demo.go
â  â  âââ internal
â  â  âââ demo.go
â  âââ logic # ä¸å¡å°è£
: ä¸å¡é»è¾å°è£
管ç, ç¹å®çä¸å¡é»è¾å®ç°åå°è£
. å¾å¾æ¯é¡¹ç®ä¸æå¤æçé¨å. logicå±çä¸å¡é»è¾éè¦éè¿è°ç¨daoæ¥å®ç°æ°æ®çæä½, è°ç¨daoæ¶éè¦ä¼ édoæ°æ®ç»æ对象, ç¨äºä¼ éæ¥è¯¢æ¡ä»¶ãè¾å
¥æ°æ®. daoæ§è¡å®æ¯åéè¿Entityæ°æ®æ¨¡åå°æ°æ®ç»æè¿åç»service(logic)å±
â  â  âââ logic.go
â  â  âââ demo # demoæå¡çå
·ä½å®ç°
â  â  â  âââ demo.go
â  â  âââ middleware # ä¸é´ä»¶
â  â  âââ middleware.go
â  â  âââ accessuser.go
â  â  âââ response.go # ç»ä¸æ¦æªè§èååº
â  â  âââ traceid.go
â  âââ model # æ°æ®ç»æ管ç模å, 管çæ°æ®å®ä½å¯¹è±¡, 以åè¾å
¥ä¸è¾åºæ°æ®ç»æå®ä¹. è¿éçmodelä¸ä»
è´è´£ç»´æ¤æ°æ®å®ä½å¯¹è±¡(entity)ç»æå®ä¹, ä¹å
æ¬ææçè¾å
¥/è¾åºæ°æ®ç»æå®ä¹, 被api/dao/serviceå
±åå¼ç¨
â  â  âââ demo.go # è¾å
¥/è¾åºæ°æ®ç»æå®ä¹
â  â  âââ do # é¢å对象: ç¨äºdaoæ°æ®æä½ä¸ä¸å¡æ¨¡åä¸å®ä¾æ¨¡å转æ¢. NOTE: ç±å·¥å
·ç»´æ¤(make dao), ä¸è¦æå¨ä¿®æ¹
â  â  â  âââ demo.go
â  â  âââ entity # æ°æ®æ¨¡å: æ¯æ¨¡åä¸æ°æ®éåçä¸å¯¹ä¸å
³ç³», é常åæ°æ®è¡¨ä¸ä¸å¯¹åº. NOTE: ç±å·¥å
·ç»´æ¤(make dao), ä¸è¦æå¨ä¿®æ¹
â  â  â  âââ demo.go
â  âââ packed
â  â  âââ packed.go
â  âââ service # ä¸å¡æ¥å£å±: ç¨äºä¸å¡æ¨¡å解è¦çæ¥å£å®ä¹å±. å
·ä½çæ¥å£å®ç°å¨logicä¸è¿è¡æ³¨å
¥. NOTE: ç±å·¥å
·ç»´æ¤(make service), ä¸è¦æå¨ä¿®æ¹
â  âââ demo.go
â  âââ middleware.go
âââ manifest # 交ä»æ¸
å: å
å«åºç¨é
ç½®æ件, é¨ç½²æ件ç
â  âââ config # é
ç½®æ件åæ¾ç®å½, å¯éè¿gf build/make buildæå
å°äºè¿å¶æ件ä¸
â  â  âââ config.prod.yaml # ç产ç¯å¢
â  â  âââ config.test.yaml # æµè¯ç¯å¢
â  â  âââ config.yaml # å¼åç¯å¢
â  âââ deploy # åé¨ç½²ç¸å
³çæ件
â  â  âââ kustomize # Kubernetesé群åé¨ç½²çYaml模æ¿, éè¿kustomize管ç
â  â  â  âââ base
â  â  â  â  âââ deployment.yaml
â  â  â  â  âââ kustomization.yaml
â  â  â  â  âââ service.yaml
â  â  â  âââ overlays
â  â  â  âââ develop
â  â  â  âââ configmap.yaml
â  â  â  âââ deployment.yaml
â  â  â  âââ kustomization.yaml
â  â  âââ supervisor # éè¿ supervisor 管çæå¡
â  â  â  âââ deploy.sh # ä¸é®é¨ç½²èæ¬
â  â  â  âââ gf2-demo-api.ini # æ¬é¡¹ç®ç产ç¯å¢supervisoré
ç½®æ件
â  â  â  âââ gf2-demo-api_test.ini # æ¬é¡¹ç®æµè¯ç¯å¢supervisoré
ç½®æ件
â  â  âââ systemctl # éè¿systemctl管çæå¡
â  â  âââ deploy.sh # ä¸é®é¨ç½²èæ¬
â  â  âââ gf2-demo-api.service # ç产ç¯å¢æå¡æ件
â  â  âââ gf2-demo-api_test.service # æµè¯ç¯å¢æå¡æ件
â  âââ sql
â  âââ gf2_demo.sql # ç¨äºå建示ä¾è¡¨çsqlæ件
âââ resource # éæèµæºæ件: è¿äºæ件å¾å¾å¯ä»¥éè¿èµæºæå
/éåç¼è¯çå½¢å¼æ³¨å
¥å°åå¸æ件ä¸, 纯å端apiæå¡ä¸è¬ç¨ä¸å°æ¤ç®å½
â  âââ i18n
â  âââ public
â  â  âââ html
â  â  âââ plugin
â  â  âââ resource
â  â  âââ css
â  â  âââ image
â  â  âââ js
â  âââ template
âââ utility # éç¨å·¥å
·ç±»
âââ accessuser.go
âââ version.go
ç¯å¢ç®¡ç â
å¼åç¯å¢
é
ç½®æ件: manifest/config/config.yaml
è¿è¡:
make run
æ ./gf2-demo-api
ä¼é»è®¤å è½½é ç½®æ件 config.yaml
æµè¯ç¯å¢
é
ç½®æ件: manifest/config/config.test.yaml
è¿è¡:
- éè¿ç¯å¢åéæå®é
ç½®æ件:
GF_GCFG_FILE=config.test.yaml GF_GERROR_BRIEF=true ./gf2-demo-api
- éè¿å½ä»¤è¡åæ°æå®é
ç½®æ件:
./gf2-demo-api -c config.test.yaml
NOTE:
- éè¿å½ä»¤è¡åæ°æå®é ç½®æ件ä¼å äºç¯å¢åé.
- -c åæ°æå®çé ç½®æ件å¯ä»¥ä½¿ç¨ç»å¯¹è·¯å¾, å¦æä¸å å«è·¯å¾, é»è®¤ä¾æ¬¡å¨å¦ä¸è·¯å¾æç´¢é ç½®æ件:
./
(äºè¿å¶æå¨çå½åç®å½) >./config/
>./manifest/config/
.GF_GERROR_BRIEF=true
表示 HTTP æå¡æ¥å¿é误å æ ä¸ä¸å å« gf æ¡æ¶å æ .- é ç½®æ件å¨éè¿
make build
æmake build.cli
ç¼è¯æ¶å·²ç»æå å°äºè¿å¶æ件ä¸, æ以å¨é¨ç½²æ¶åªéé¨ç½²äºè¿å¶æ件å³å¯.
ç产ç¯å¢
é
ç½®æ件: manifest/config/config.prod.yaml
è¿è¡:
åæµè¯ç¯å¢, åªä¸è¿æå®çé ç½®æ件ä¸å, ç¥.
å¤å½ä»¤ç®¡ç â
ç®å½è®¾è®¡
举ä¾:
- å½ä»¤ 1:
cmd/gf2-demo-api/gf2-demo-api.go
->internal/cmd/apiserver/apiserver.go
- å½ä»¤ 2:
cmd/gf2-demo-cli/gf2-demo-cli.go
->internal/cmd/cli/cli.go
é ç½®æ件
é»è®¤ä¸åå½ä»¤å¨ç¸åç¯å¢ä¸ä½¿ç¨åä¸ä¸ªé
ç½®æ件, æ¯å¦ gf2-demo-api
å gf2-demo-cli
å¨å¼åç¯å¢ä¸é½ä½¿ç¨ manifest/config/config.yaml
ä½ä¸ºé
ç½®æ件.
ä¸è¿ä¹å¯ä»¥ä½¿ç¨åèªç¬ç«çé ç½®æ件, åªéè¦å¨è¿è¡æ¶éè¿ç¯å¢åéæå½ä»¤è¡åæ°æå®éè¦ä½¿ç¨çé ç½®æ件å³å¯, æ¯å¦:
./gf2-demo-cli -c cli_config.yaml
æ
GF_GCFG_FILE=cli_config.yaml ./gf2-demo-cli
é误ç 管ç â
è§èå¶å®
-
ç»ä¸ååºæ ¼å¼
ä¸è®ºæ¯æ£ç¡®è¿æ¯é误ååº, ååºä½é½ç»ä¸ä½¿ç¨å¦ä¸æ ¼å¼:
{ "code": "string", "message": "string", "traceid": "string", "data": null }
ð¡ ååº header ä¸å·²ç»æäº
Trace-Id
äº, 为ä»ä¹ååº json ä¸è¿è¦å ä¸ä¸ªtraceid
?ç®çæ¯å¨éå°é误é®é¢è¿è¡ææ¥æ¶, åå°ä¸å¿ è¦çæ²éææ¬, æ¯ç«å¾å¤ç¨æ·å®¹æ忽ç¥ååº header, å¨ååºä½ä¸ç´æ¥ä½ç°
traceid
æ´ç´æ¥. è¿æ ·å¨å¿«éæ¿å°ç¨æ·åé¦çtraceid
å, æ们就å¯ä»¥å¾å¿«æ¾å°å¯¹åºçæ¥å¿ä»èé«æ解å³é®é¢äº. -
ä¸å¡ç
ç»ä¸ä½¿ç¨å符串表示, å¦:"code": "ValidationFailed"
-
HTTP ç¶æç
- æ£ç¡®ååº
-
200
: æåçååº -
202
: é¨åæåçååº
-
- 客æ·ç«¯é误
-
401
: æªéè¿è®¿é®è®¤è¯ -
403
: 请æ±çèµæºæªè·å¾ææ -
404
: 请æ±çèµæºä¸åå¨ -
400
: å ¶ä»ææ客æ·ç«¯é误, æ¯å¦è¯·æ±åæ°éªè¯å¤±è´¥ç
-
- æå¡ç«¯é误
-
500
: æææå¡ç«¯é误
-
- æ£ç¡®ååº
ä¸å¡é误ç
è¯·å¨ internal/codes/biz_codes.go
æ件ä¸ç»´æ¤ä¸å¡é误ç .
package codes
// http status, bisiness code, message
var (
CodeOK = New(200, "OK", "")
CodePartSuccess = New(202, "PartSuccess", "part success")
CodePermissionDenied = New(401, "AuthFailed", "authentication failed")
CodeNotAuthorized = New(403, "NotAuthorized", "resource is not authorized")
CodeNotFound = New(404, "NotFound", "resource does not exist")
CodeValidationFailed = New(400, "ValidationFailed", "validation failed")
CodeNotAvailable = New(400, "NotAvailable", "not available")
CodeInternal = New(500, "InternalError", "an error occurred internally")
)
ååºç¤ºä¾
-
æ£ç¡®ååº
HTTP/1.1 200 OK Content-Type: application/json Server: GoFrame HTTP Server Trace-Id: 10c9769ce5cf4117c19a595c2d781e94 Date: Wed, 08 Feb 2023 09:38:41 GMT Content-Length: 34 { "code": "OK", "message": "", "traceid": "10c9769ce5cf4117c19a595c2d781e94", "data": null }
-
401 é误
HTTP/1.1 401 Unauthorized Content-Type: application/json Server: GoFrame HTTP Server Trace-Id: a89b7652b1cf41170d6e5233fbb76a21 Date: Wed, 08 Feb 2023 09:34:56 GMT Content-Length: 83 { "code": "AuthFailed", "message": "authentication failed", "traceid": "a89b7652b1cf41170d6e5233fbb76a21", "data": null }
-
500 é误
HTTP/1.1 500 Internal Server Error Content-Type: application/json Server: GoFrame HTTP Server Trace-Id: 70cd58a9d8cf4117376a265eb84137e5 Date: Wed, 08 Feb 2023 09:37:45 GMT Content-Length: 73 { "code": "InternalError", "message": "an error occurred internally", "traceid": "70cd58a9d8cf4117376a265eb84137e5", "data": null }
æ¥å¿ç®¡ç â
HTTP æå¡æ¥å¿
1. HTTP æå¡æ¥å¿é ç½®
# manifest/config/config.yaml
server:
# æå¡æ¥å¿(å
æ¬è®¿é®æ¥å¿åserveré误æ¥å¿)
logPath: "logs/" # æ¥å¿æ件åå¨ç®å½è·¯å¾, 建议使ç¨ç»å¯¹è·¯å¾. é»è®¤ä¸ºç©º, 表示å
³é
logStdout: true # æ¥å¿æ¯å¦è¾åºå°ç»ç«¯. é»è®¤ä¸ºtrue
errorStack: true # å½Serveræè·å°å¼å¸¸æ¶æ¯å¦è®°å½å æ ä¿¡æ¯å°æ¥å¿ä¸. é»è®¤ä¸ºtrue
errorLogEnabled: true # æ¯å¦è®°å½å¼å¸¸æ¥å¿ä¿¡æ¯å°æ¥å¿ä¸. é»è®¤ä¸ºtrue
errorLogPattern: "error-{Ymd}.log" # å¼å¸¸é误æ¥å¿æä»¶æ ¼å¼. é»è®¤ä¸º"error-{Ymd}.log"
accessLogEnabled: true # æ¯å¦è®°å½è®¿é®æ¥å¿(å
å«å¼å¸¸ç访é®æ¥å¿). é»è®¤ä¸ºfalse
accessLogPattern: "access-{Ymd}.log" # 访é®æ¥å¿æä»¶æ ¼å¼. é»è®¤ä¸º"access-{Ymd}.log"
# é对æå¡æ¥å¿çæ©å±é
ç½®
logger:
file: "{Ymd}.log" # è¿éåªè®°å½serverå¯å¨è¿ç¨ä¸gfé»è®¤æå°çæ¥å¿, é»è®¤ {Y-m-d}.log; æ¥å¿æå¨è·¯å¾ä¸ºserver.logPathæå®çç®å½
ctxKeys: ["user", "mail"] # èªå¨æå°Contextçæå®åéå°æ¥å¿ä¸. é»è®¤ä¸ºç©º
rotateExpire: "1d"
rotateBackupExpire: "30d"
rotateBackupLimit: 30
rotateCheckInterval: "1h"
2. çæçæ¥å¿ç¤ºä¾
$ curl -X GET 'localhost:9000/v1/demo' \
-H 'X-Consumer-Custom-ID: windvalley' \
-H 'X-Consumer-Username: [email protected]'
-
æå¡è®¿é®æ¥å¿ç¤ºä¾
# æ®éæ ¼å¼ 2023-02-08 16:50:51.992 {10fde08349cd4117115968787a401378} {windvalley, [email protected]} 401 "GET http localhost:9000 /v1/hello HTTP/1.1" 0.004, ::1, "", "PostmanRuntime/7.28.0" # jsonæ ¼å¼ {"Time":"2023-02-08 16:53:13.118","TraceId":"a8b1bf5f6acd41177931ba72f7411788","CtxStr":"windvalley, [email protected]","Level":"","Content":"401 \"GET http localhost:9000 /v1/hello HTTP/1.1\" 0.002, ::1, \"\", \"PostmanRuntime/7.28.0\""}
-
æå¡é误æ¥å¿ç¤ºä¾
# æ®éæ ¼å¼ 2023-02-08 16:55:25.984 {2068374f89cd41170d329c50fe5a5fc8} {windvalley, [email protected]} 401 "GET http localhost:9000 /v1/hello HTTP/1.1" 0.003, ::1, "", "PostmanRuntime/7.28.0", 0, "resource is not authorized", "{Code:NotAuthorized HttpCode:401}" Stack: 1. resource is not authorized: some error 1). gf2-demo/internal/controller.(*cHello).Hello /Users/xg/github/gf2-demo/internal/controller/hello.go:25 2). gf2-demo/internal/logic/middleware.(*sMiddleware).ResponseHandler /Users/xg/github/gf2-demo/internal/logic/middleware/response.go:16 3). gf2-demo/internal/logic/middleware.(*sMiddleware).AccessUser /Users/xg/github/gf2-demo/internal/logic/middleware/accessuser.go:25 4). gf2-demo/internal/logic/middleware.(*sMiddleware).TraceID /Users/xg/github/gf2-demo/internal/logic/middleware/traceid.go:27 2. some error # jsonæ ¼å¼ {"Time":"2023-02-08 16:54:28.757","TraceId":"18323afc7bcd411710d9f134cc2ec9d5","CtxStr":"windvalley, [email protected]","Level":"ERRO","Content":"401 \"GET http localhost:9000 /v1/hello HTTP/1.1\" 0.003, ::1, \"\", \"PostmanRuntime/7.28.0\", 0, \"resource is not authorized\", \"{Code:NotAuthorized HttpCode:401}\"\nStack:\n1. resource is not authorized: some error\n 1). gf2-demo/internal/controller.(*cHello).Hello\n /Users/xg/github/gf2-demo/internal/controller/hello.go:25\n 2). gf2-demo/internal/logic/middleware.(*sMiddleware).ResponseHandler\n /Users/xg/github/gf2-demo/internal/logic/middleware/response.go:16\n 3). gf2-demo/internal/logic/middleware.(*sMiddleware).AccessUser\n /Users/xg/github/gf2-demo/internal/logic/middleware/accessuser.go:25\n 4). gf2-demo/internal/logic/middleware.(*sMiddleware).TraceID\n /Users/xg/github/gf2-demo/internal/logic/middleware/traceid.go:27\n2. some error\n"}
SQL æ¥å¿
1. SQL æ¥å¿é ç½®
# manifest/config/config.yaml
# doc: https://goframe.org/pages/viewpage.action?pageId=1114245
database:
# sqlæ¥å¿
logger:
path: "logs/"
file: "sql-{Ymd}.log"
level: "all"
stdout: true
ctxKeys: ["user", "mail"]
rotateExpire: "1d"
rotateBackupExpire: "30d"
rotateBackupLimit: 30
rotateCheckInterval: "1h"
2. çæçæ¥å¿ç¤ºä¾
# æ®éæ ¼å¼
2023-02-12 16:52:32.330 [DEBU] {508ad625b3074317ec81cd791f1a5993} {windvalley, [email protected]} [ 2 ms] [default] [gf2_demo] [rows:5 ] SHOW FULL COLUMNS FROM `demo`
2023-02-12 16:52:32.331 [DEBU] {508ad625b3074317ec81cd791f1a5993} {windvalley, [email protected]} [ 1 ms] [default] [gf2_demo] [rows:1 ] SELECT * FROM `demo` WHERE `fielda`='windvalley' LIMIT 1
# jsonæ ¼å¼
{"Time":"2023-02-12 16:55:02.420","TraceId":"28a0d817d6074317a9e647156d712d81","CtxStr":"windvalley, [email protected]","Level":"DEBU","Content":"[ 3 ms] [default] [gf2_demo] [rows:5 ] SHOW FULL COLUMNS FROM `demo`"}
{"Time":"2023-02-12 16:55:02.421","TraceId":"28a0d817d6074317a9e647156d712d81","CtxStr":"windvalley, [email protected]","Level":"DEBU","Content":"[ 1 ms] [default] [gf2_demo] [rows:1 ] SELECT * FROM `demo` WHERE `fielda`='windvalley' LIMIT 1"}
å¼åè æå°çéç¨æ¥å¿
1. éç¨æ¥å¿é ç½®
# manifest/config/config.yaml
logger:
path: "logs/" # æ¥å¿æ件ç®å½, å¦æ为空, 表示ä¸è®°å½å°æ件; 建议ç®å½åserver.logPathä¿æä¸è´
file: "{Ymd}.log" # æ¥å¿æä»¶æ ¼å¼. é»è®¤ä¸º {Y-m-d}.log; 建议åserver.logger.fileä¿æä¸è´
level: "all" # DEBU < INFO < NOTI < WARN < ERRO < CRIT, ä¹æ¯æALL, DEV, PROD常è§é¨ç½²æ¨¡å¼é
ç½®å称. levelé
置项å符串ä¸åºå大å°å
stStatus: 0 # æ¯å¦æå°é误å æ (1: enabled - default; 0: disabled). å¦æå¼å¯, 使ç¨g.Log().Error å°ä¼æå°é误å æ
ctxKeys: ["user", "mail"] # èªå¨æå°Contextçåéå°æ¥å¿ä¸. é»è®¤ä¸ºç©º
stdout: true # æ¥å¿æ¯å¦åæ¶è¾åºå°ç»ç«¯. é»è®¤true
stdoutColorDisabled: false # å
³éç»ç«¯çé¢è²æå°. é»è®¤false
writerColorEnable: false # æ¥å¿æ件æ¯å¦å¸¦ä¸é¢è². é»è®¤false, 表示ä¸å¸¦é¢è²
rotateExpire: "1d" # å¤é¿æ¶é´ååä¸æ¬¡æ¥å¿
rotateBackupExpire: "30d" # å é¤è¶
è¿å¤é¿æ¶é´çååæ件, é»è®¤ä¸º0, 表示ä¸å¤ä»½, åååå é¤. å¦æå¯ç¨ææ¶é´å¤ä»½, rotateBackupLimit å¿
须设置为ä¸ä¸ªç¸å¯¹è¾å¤§çæ°
rotateBackupLimit: 30 # æå¤ä¿çå¤å°ä¸ªååæ件, ä½rotateBackupExpireçé
ç½®ä¼å
. é»è®¤ä¸º0, 表示ä¸å¤ä»½, åååå é¤. å¯ä»¥ä¸è®¾ç½®rotateBackupExpire
rotateCheckInterval: "1h" # æ»å¨ååçæ¶é´æ£æµé´é, ä¸è¬ä¸éè¦è®¾ç½®. é»è®¤ä¸º1å°æ¶
format: "" # "json" or other, ä¹å¯¹serveræå¡æ¥å¿çæ
# 为å项ç®gf2-demo-clié
ç½®ç¬ç«çlogger
cli:
path: "logs/"
file: "cli-{Ymd}.log"
level: "all"
stStatus: 1
stdout: true
stdoutColorDisabled: false
writerColorEnable: false
rotateExpire: "1d"
rotateBackupExpire: "30d"
rotateBackupLimit: 30
rotateCheckInterval: "1h"
format: ""
2. å¦ä½ææ¥å¿
// gf2-demo-apiçæ¥å¿
g.Log().Info(ctx, "hello world")
g.Log().Errorf(ctx, "hello %s", "world")
// gf2-demo-cliçæ¥å¿
g.Log("cli").Debug(ctx, "hello world")
g.Log("cli").Warningf(ctx, "hello %s", "world")
3. çæçæ¥å¿ç¤ºä¾
# æ®éæ ¼å¼
2023-02-08 17:02:31.906 [INFO] {389b4e7aeccd41175dd0bc18211c2519} {windvalley, [email protected]} /Users/xg/github/gf2-demo/internal/controller/hello.go:33: hello world
2023-02-08 17:02:31.906 [ERRO] {389b4e7aeccd41175dd0bc18211c2519} {windvalley, [email protected]} /Users/xg/github/gf2-demo/internal/controller/hello.go:34: hello world
# jsonæ ¼å¼
{"Time":"2023-02-08 17:04:08.957","TraceId":"d0e7f61203ce41171374033689322f91","CtxStr":"windvalley, [email protected]","Level":"INFO","CallerPath":"/Users/xg/github/gf2-demo/internal/controller/hello.go:33:","Content":"hello world"}
{"Time":"2023-02-08 17:04:08.957","TraceId":"d0e7f61203ce41171374033689322f91","CtxStr":"windvalley, [email protected]","Level":"ERRO","CallerPath":"/Users/xg/github/gf2-demo/internal/controller/hello.go:34:","Content":"hello world"}
é¾è·¯è·è¸ª â
-
ç¨äºé¾è·¯è·è¸ªçååº Header 为:
Trace-Id
, ä¼ä¼å 使ç¨å®¢æ·ç«¯ä¼ éçè¯·æ± HeaderTrace-Id
çå¼, å¦æä¸åå¨ä¼èªå¨çæ. 为äºä¾¿äºç¨æ·æ¥çTrace-Id
, ä¹å¨ååº json ä¸å å ¥äºtraceid
å段. -
æå¡å é¨å¦æéè¦è°ç¨å ¶ä»æå¡çæ¥å£, 请使ç¨
g.Client()
, å 为ä»ä¼ç»è¯·æ±èªå¨æ³¨å ¥Trace-Id
, è¿æ ·ä¸å API æå¡ä¹é´çæ¥å¿å°±å¯ä»¥éè¿Trace-Id
串起æ¥äº.
åè: https://goframe.org/pages/viewpage.action?pageId=49745257
çæ¬ç®¡ç â
1. åçæ¬åæ´ææ¡£
vi CHANGELOG.md
## v0.3.0
### Added
- xxx
- xxx
### Changed
- xxx
- xxx
### Fixed
- xxx
- xxx
2. ç»é¡¹ç®ä»åºæ tag
git tag v0.3.0
git push --tags
3. ä½¿ç¨ Makefile ç¼è¯
-
gf å·¥å ·é ç½®(
hack/config.yaml
)# doc: https://goframe.org/pages/viewpage.action?pageId=3673173 gfcli: # doc: https://goframe.org/pages/viewpage.action?pageId=1115788 build: path: "./bin" # ç¼è¯çæçäºè¿å¶æ件çåæ¾ç®å½. çæçäºè¿å¶å称é»è®¤ä¸ç¨åºå ¥å£goæ件åå arch: "amd64" system: "linux,darwin" # å°é¡¹ç®éè¦çé ç½®æ件æå è¿äºè¿å¶, è¿æ ·é¡¹ç®é¨ç½²çæ¶åå°±å¯ä»¥ä¸ç¨æ·è´é ç½®æ件äº. # NOTE: 1) å¦æå¼å¯äºæå åè½, ä½è¿æ¯æ³å使ç¨å¤é¨é ç½®æ件, # éè¦éè¿ç¯å¢åéGF_GCFG_FILEæ-cåæ°æå®é ç½®æ件路å¾. # ä¾å¦ï¼ # GF_GCFG_FILE=./config.prod.yaml ./gf2-demo-api # æ: ./gf2-demo-api -c ./config.prod.yaml # 2) 使ç¨å¤é¨é ç½®æ件ç好å¤: å¯ä»¥ä¸ç¨éæ°ç¼è¯åæ´é ç½®æ件å 容, æ¯æé ç½®æ件çæ´æ°. # 3) 使ç¨å¤é¨é ç½®æ件ç缺ç¹: é ç½®æ件çåå没æç»è¿çæ¬ç®¡ç, åºç°é®é¢ä¸æ¹ä¾¿å溯åå². packSrc: "manifest/config" extra: "" # ç¼è¯æ¶çå ç½®åéå¯ä»¥å¨è¿è¡æ¶éè¿gbuildå è·å, æ¯å¦: utility/version.go varMap: # NOTE: 1) `version` is used by `make build` to generate binary version. Do Not Edit. # 2) You can manage the project versions by command `git tag vX.X.X` version:
-
ç¼è¯
# For gf2-demo-api make build # For gf2-demo-cli make build.cli
4. æ¥çäºè¿å¶æ件çæ¬ä¿¡æ¯
$ bin/darwin_amd64/gf2-demo-api -v
è¾åº:
App Version: v0.3.0-7-g1898e82
Git Commit: 2023-02-08 14:55:39 1898e82dbcb4c2e8a091eb12fc96ead2f04f5993
Build Time: 2023-02-08 15:31:20
Go Version: go1.17.6
GF Version: v2.3.1
Redis â
Redis é ç½®
# manifest/config/config.yaml
# doc: https://goframe.org/pages/viewpage.action?pageId=1114217
redis:
# é»è®¤åç», è°ç¨æ¹å¼: g.Redis()
default:
address: 127.0.0.1:6379
# æ°æ®åºç´¢å¼, 0-15
db: 0
# 访é®ææå¯ç
pass:
# è¿æ¥æé¿åæ´»æ¶é´, é»è®¤å¼: 30s, 建议设置çé¿ä¸äº.
# 建ç«è¿æ¥å, å¯ç¨æ¤è¿æ¥è¿è¡å¤æ¬¡reids请æ±,
# ä»å»ºç«è¿æ¥å¼å§è®¡ç®, åªè¦è¶
è¿è¿ä¸ªæ¶é´å°±ä¼èªå¨æå¼è¿æ¥,
# å°±ç®æ¤æ¶æredis请æ±ä¹ä¼å
å
³éå½åè¿æ¥, ç¶åéæ°å»ºä¸ä¸ªæ°çè¿æ¥,
# é¤é请æ±æ¯ä¸ä¸ªé»å¡å¼è¯·æ±, æ¯å¦: BLPopç, æ¤æ¶è¿æ¥å°å§ç»ä¿æ,
# ç´å°ä»éå读åå°æ°æ®æä¼æå¼è¿æ¥.
maxConnLifetime: 30m
# è¿æ¥æ大空é²æ¶é´, é»è®¤å¼: 10s;
# åªè¦è¿æ¥ç©ºé²(没ææ°è¯·æ±)è¶
è¿idleTimeoutå, å°±ä¼æå¼è¿æ¥.
# æ¤å¼åºè¯¥å°äºmaxConnLifetime, ææå®é
æä¹.
idleTimeout: 10m
# 延è¿éååç», è°ç¨æ¹å¼: g.Redis("delayqueue")
delayqueue:
address: 127.0.0.1:6379
db: 1
pass:
maxConnLifetime: 30m
idleTimeout: 10m
Redis å·¥å ·åº
- Redis ç¼å:
internal/pkg/rediscache
- Redis åå¸å¼é:
internal/pkg/redislock
- Redis æ¶æ¯éå:
internal/pkg/redismq
- Redis 延è¿éå:
internal/pkg/redisdelaymq
- Redis åå¸è®¢é
:
internal/pkg/redispubsub
使ç¨æ¹æ³å¯åè代ç ææ¯ä¸ªå ä¸é¢ç test æ件.
å¼åæµç¨ â
1. 设计表ç»æ, å建ç©ç表
-
设计表ç»æ
-- manifest/sql/gf2_demo.sql -- Create demo database CREATE DATABASE IF NOT EXISTS `gf2_demo`; USE `gf2_demo`; -- Create demo table DROP TABLE IF EXISTS `demo`; CREATE TABLE `demo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', `fielda` varchar(45) NOT NULL COMMENT 'Field demo', `fieldb` varchar(45) NOT NULL COMMENT 'Private field demo', `created_at` datetime DEFAULT NULL COMMENT 'Created Time', `updated_at` datetime DEFAULT NULL COMMENT 'Updated Time', PRIMARY KEY (`id`), UNIQUE KEY `idx_fielda` (`fielda`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
å建ç©ç表
$ mysql -uroot -p'123456' < manifest/sql/demo.sql
2. èªå¨çææ°æ®å±ç¸å ³ä»£ç
-
gf å·¥å ·é ç½®
# hack/config.yaml gfcli: gen: # doc: https://goframe.org/pages/viewpage.action?pageId=3673173 dao: - link: "mysql:root:123456@tcp(127.0.0.1:3306)/gf2_demo" tables: "" # æå®å½åæ°æ®åºä¸éè¦æ§è¡ä»£ç çæçæ°æ®è¡¨, å¤ä¸ªä»¥éå·åé. å¦æ为空, 表示æ°æ®åºçææ表é½ä¼çæ. é»è®¤ä¸ºç©º descriptionTag: true # ç¨äºæå®æ¯å¦ä¸ºæ°æ®æ¨¡åç»æä½å±æ§å¢å desriptionçæ ç¾, å 容为对åºçæ°æ®è¡¨å段注é. é»è®¤ false noModelComment: true # ç¨äºæå®æ¯å¦å ³éæ°æ®æ¨¡åç»æä½å±æ§ç注éèªå¨çæ, å 容为æ°æ®è¡¨å¯¹åºå段ç注é. é»è®¤ false jsonCase: "snake" # æå®modelä¸çæçæ°æ®å®ä½å¯¹è±¡ä¸jsonæ ç¾å称è§å. é»è®¤ CamelLower clear: true # èªå¨å é¤æ°æ®åºä¸ä¸åå¨å¯¹åºæ°æ®è¡¨çæ¬å°dao/do/entity代ç æ件, é»è®¤ false. 线ä¸ç¯å¢åºè®¾ç½®ä¸ºfasle
-
èªå¨çæ
internal/dao
,internal/model/do
,internal/model/entity
$ make dao
3. ç¼å api å±ä»£ç
ä½ç½®: api/demo/v1/demo.go
å®ä¹ä¸å¡ä¾§æ°æ®ç»æ, æä¾å¯¹å¤æ¥å£çè¾å ¥/è¾åºæ°æ®ç»æ, å®ä¹è®¿é®è·¯ç± path, 请æ±æ¹æ³, æ°æ®æ ¡éª, api ææ¡£ç.
注æ: ç®å½ç»æå¿
é¡»éµå®è¿ä¸ªæ¨¡å¼è§è api/模åå称/çæ¬å·/模åå称.go
示ä¾:
// api/demo/v1/demo.go
type CreateReq struct {
g.Meta `path:"/demo" method:"post" tags:"DemoService" summary:"Create a demo record"`
Fielda string `p:"fileda" v:"required|passport|length:4,30"`
Fieldb string `p:"filedb" v:"required|length:10,30"`
}
type CreateRes struct {
ID uint `json:"id"`
}
ç¼åè§è请åèææ¡£: https://goframe.org/pages/viewpage.action?pageId=93880327
4. èªå¨çæ controller å±æ¡æ¶ä»£ç
ç¼åå® api å®ä¹ä»£ç (api/demo/v1/demo.go
)å, å¨é¡¹ç®æ ¹ç®å½æ§è¡å¦ä¸å½ä»¤è¡:
$ make ctrl
该å½ä»¤è¡ä¼æ ¹æ®å¼åè
ç¼åç api/demo/v1/demo.go
api å®ä¹æ件èªå¨çæ:
-
api interface æ件
api/demo/demo.go
-
controller å±ä»£ç
âââ internal â  âââ controller â  â  âââ demo â  â  âââ demo.go â  â  âââ demo_new.go # ä¸å¯åæ´ â  â  âââ demo_v1_create.go # æ们åªéè¦å¨è¿éå¡«å controllerçå ·ä½å®ç°
5. ç¼å model å±ä»£ç
ä½ç½®: internal/model/
å®ä¹æ°æ®ä¾§æ°æ®ç»æï¼æä¾å¯¹å
çæ°æ®å¤ççè¾å
¥/è¾åºæ°æ®ç»æ.
å¨ GoFrame æ¡æ¶è§èä¸, è¿é¨åè¾å
¥è¾åºæ¨¡åå称以 XxxInput
å XxxOutput
æ ¼å¼å½å, éè¦å¨ internal/model
ç®å½ä¸å建æ件.
示ä¾:
// internal/model/demo.go
type DemoCreateInput struct {
Fielda string
Fieldb string
}
type DemoCreateOutput struct {
ID uint
}
åè: https://goframe.org/pages/viewpage.action?pageId=7295964
6. ç¼å service å±ä»£ç
a. ç¼åå
·ä½çä¸å¡å®ç°(internal/logic/
)
è°ç¨æ°æ®è®¿é®å±(internal/dao/
), ç¼åå
·ä½çä¸å¡é»è¾. è¿éæ¯ä¸å¡é»è¾çéå¿, ç»å¤§é¨åçä¸å¡é»è¾é½åºè¯¥å¨è¿éç¼å.
示ä¾:
// internal/logic/demo/demo.go
import (
"gf2-demo/internal/dao"
"gf2-demo/internal/model"
"gf2-demo/internal/model/do"
"gf2-demo/internal/model/entity"
)
type sDemo struct{}
func New() *sDemo {
return &sDemo{}
}
func (s *sDemo) Create(ctx context.Context, in model.DemoCreateInput) (*model.DemoCreateOutput, error) {
notFound, err := s.FieldaNotFound(ctx, in.Fielda)
if err != nil {
return nil, err
}
if !notFound {
err1 := gerror.WrapCode(codes.CodeNotAvailable, fmt.Errorf("fielda '%s' already exists", in.Fielda))
return nil, err1
}
id, err := dao.Demo.Ctx(ctx).Data(in).InsertAndGetId()
if err != nil {
return nil, err
}
return &model.DemoCreateOutput{
ID: uint(id),
}, nil
}
b. èªå¨çæ service æ¥å£ä»£ç (internal/service/
)
$ make service
c. å°ä¸å¡å®ç°æ³¨å ¥å°æå¡æ¥å£(ä¾èµæ³¨å ¥)
示ä¾:
// internal/logic/demo/demo.go
import "gf2-demo/internal/service"
type sDemo struct{}
func init() {
service.RegisterDemo(New())
}
d. ç¨åºå¯å¨æ¶èªå¨æ³¨åæå¡
å¨ç¨åºå
¥å£æ件 cmd/gf2-demo-api/gf2-demo-api.go
ä¸å¯¼å
¥ logic å
.
示ä¾:
// cmd/gf2-demo-api/gf2-demo-api.go
package main
import _ "gf2-demo/internal/logic"
åè: https://goframe.org/pages/viewpage.action?pageId=49770772
7. ç¼å controller å±ä»£ç
ä½ç½®: internal/controller/
controller 代ç æ件åé¢å·²ç»éè¿make ctrl
èªå¨çæäº, æ们åªéè¦å¨éå½çä½ç½®å¡«å
å
·ä½å®ç°å³å¯.
å ·ä½å®ç°å¦ä½ç¼å:
解æ api å±(api/demo/v1/demo.go
)å®ä¹çä¸å¡ä¾§ç¨æ·è¾å
¥æ°æ®ç»æ, ç»è£
为 model å±(internal/model/
)å®ä¹çæ°æ®ä¾§è¾å
¥æ°æ®ç»æå®ä¾, è°ç¨ internal/service/
å±çæå¡, æåç´æ¥å°ç»ææé误 return å³å¯(ååºä¸é´ä»¶ä¼ç»ä¸æ¦æªå¤ç, æè§èååºç¨æ·).
示ä¾:
// internal/controller/demo/demo_v1_create.go
import (
"context"
v1 "gf2-demo/api/demo/v1"
"gf2-demo/internal/model"
"gf2-demo/internal/service"
)
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
data := model.DemoCreateInput{
Fielda: req.Fielda,
Fieldb: req.Fieldb,
}
result, err := service.Demo().Create(ctx, data)
if err != nil {
return nil, err
}
return &v1.CreateRes{ID: result.ID}, err
}
8. è·¯ç±æ³¨å
ä½ç½®: internal/cmd/apiserver/apiserver.go
è·¯ç±æ³¨å, è°ç¨ controller å±(internal/controller/
), 对å¤æ´é²æ¥å£.
示ä¾:
// internal/cmd/apiserver/apiserver.go
import "gf2-demo/internal/controller/demo"
var (
Main = gcmd.Command{
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
s.Group("/v1", func(group *ghttp.RouterGroup) {
group.Bind(
// 对å¤æ´é²çæ¥å£è·¯ç±éå: æ¯ "/v1" å api/demo/v1/demo.go æ件ä¸çææXxxReqç»æä½å®ä¹ç path çç»å
demo.NewV1(),
)
})
s.Run()
return nil
},
}
)
9. æ¥å£è®¿é®æµè¯
# è¿è¡
$ make run
# 访é®
$ curl -X POST -i 'localhost:9000/v1/demo' -d \
'{
"fielda": "foobar",
"fieldb": "LJIYdjsvt83l"
}'
# è¾åº:
HTTP/1.1 200 OK
Content-Type: application/json
Server: GoFrame HTTP Server
Trace-Id: 0862d01e5aa64317c7fae45b326dabd1
Date: Tue, 14 Feb 2023 09:19:52 GMT
Content-Length: 88
{"code":"OK","message":"","traceid":"0862d01e5aa64317c7fae45b326dabd1","data":{"id":17}}
代ç è´¨é â
ç»ä¸å¢é代ç é£æ ¼, ä¿éå¢é代ç è´¨é.
Github: https://github.com/golangci/golangci-lint
Documentation: https://golangci-lint.run
å®è£ golangci-lint
# è¿è¡ä»£ç æ£æ¥, å¦æ golangci-lint 没æå®è£
ä¼èªå¨è¿è¡å®è£
$ make lint
æ
$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
使ç¨æ¹æ³
-
å½ä»¤è¡æ§è¡
# å¨ä»åºæ ¹è·¯å¾æ§è¡, æ£æµä»åºå ææGo代ç $ golangci-lint run # æ $ make lint # æ¥çæælintersçåè½ä»ç» $ golangci-lint help linters # æ¥ç .golangci.yml å·²å¯ç¨ç linters $ golangci-lint linters # åªä½¿ç¨æä¸ä¸ªlinteræ¥æ£æ¥ä»£ç $ golangci-lint run --no-config --disable-all -E errcheck
-
éæå°ç¼è¾å¨æ IDE
请åèå®æ¹ææ¡£:
https://golangci-lint.run/usage/integrations/
强ç建议使ç¨æ¤ç§æ¹å¼, å¯å®æ¶æ示代ç åå¨çé®é¢, èä¸æ¯çå°ç¼è¯çæ¶åæç¥éåªéåºéäº, ä¸ä½æé«ä»£ç è´¨é, è¿è½æé«ç¼ç æç.
项ç®é¨ç½² â
Systemctl
-
ç¸å ³çé ç½®æ件åèæ¬
- ç产ç¯å¢ systemctl æå¡é
ç½®æ件:
manifest/deploy/systemctl/gf2-demo-api.service
- æµè¯ç¯å¢ systemctl æå¡é
ç½®æ件:
manifest/deploy/systemctl/gf2-demo-api_test.service
- é¨ç½²èæ¬:
manifest/deploy/systemctl/deploy.sh
- ç产ç¯å¢ systemctl æå¡é
ç½®æ件:
-
设置ç®æ æå¡å¨(ä¿®æ¹
deploy.sh
èæ¬)# ç¨äºè¿æ¥ç®æ æå¡å¨çç¨æ·å. # NOTE: ä¸è¦ä½¿ç¨ä¸ªäººè´¦å·é¨ç½²åè¿è¡æå¡, 建议建ç«ç¬ç«çå ¬å ±è´¦å·é¨ç½²åè¿è¡åºç¨. deploy_user="vagrant" # ç®æ é¨ç½²æå¡å¨. # NOTE: # 1. 请æåé ç½®åå¸æºå°ç®æ æå¡å¨ä¹é´çssh keyä¿¡ä»»: # 1) ssh-keygen -t rsa -b 4096 -C "vagrant@$(hostname)" # 2) ssh-copy-id -i ~/.ssh/id_rsa.pub vagrant@$(deploy_server) # 2. ç®æ æºå¨ä¸è§£å³sudoæ§è¡å½ä»¤æ示è¾å ¥å¯ç çé®é¢: # æ§è¡visudo, æ«å°¾æ·»å ä¸è¡: vagrant ALL=(ALL:ALL) NOPASSWD:ALL deploy_server="gf2-demo.sre.im" # é¨ç½²ç®å½çæéåºè¯¥ä¸º$deploy_user: chown -R $deploy_user. $deploy_dir deploy_dir=/app/$project_name
-
æ§è¡é¨ç½²
# é¨ç½²æµè¯ç¯å¢ $ ./manifest/deploy/systemctl/deploy.sh test # é¨ç½²ç产ç¯å¢ $ ./manifest/deploy/systemctl/deploy.sh prod
-
éªè¯
é¦å ç»å½å°ç®æ æå¡å¨.
# é»è®¤é¡¹ç®çæææ åè¾åºåæ åé误è¾åºé½ä¼å¨æ¤æ件ä¸. $ tail -f /app/gf2-demo/gf2-demo-api.log # 项ç®å¸¸è§æ¥å¿, å æ¬éè¿g.Log()æå°çæ¥å¿. $ tail -f /app/gf2-demo/logs/2023-02-15.log # 项ç®HTTPæå¡è®¿é®æ¥å¿ $ tail -f /app/gf2-demo/logs/access-20230215.log # 项ç®HTTPæå¡é误æ¥å¿ $ tail -f /app/gf2-demo/logs/error-20230215.log # sql debug æ¥å¿ $ tail -f /app/gf2-demo/logs/sql-20230215.log
-
systemctl 常ç¨å½ä»¤
# gf2-demo-api.service é ç½®æåå¨çæ¶å, éè¦éæ°å 载使çæ $ sudo systemctl daemon-reload # å¯å¨ $ sudo systemctl start gf2-demo-api # å ³é: åé SIGTERM ä¿¡å·ç»ä¸»(sh)ååè¿ç¨(gf2-demo-api), # gf2-demo-apiç¨åºå¯éè¿æè·SIGTERMä¿¡å·æ¥å®ç°ä¼é å ³é. $ sudo systemctl stop gf2-demo-api # éå¯: å å ³é(SIGTERM), åå¯å¨ $ sudo systemctl restart gf2-demo-api
NOTE:
- æ¤ç¤ºä¾ä¸ºåå°é¨ç½², è¥é¨ç½²é群å¯ä½¿ç¨
gossh
ãansible
çå·¥å ·.- æå¡å¨æä½ç³»ç»:
CentOS7.x
, å ¶ä»ç³»ç»ç±»åæªéªè¯.
Supervisor
-
ç¸å ³çé ç½®æ件åèæ¬
- ç产ç¯å¢ supervisor é
ç½®æ件:
manifest/deploy/supervisor/gf2-demo-api.ini
- æµè¯ç¯å¢ supervisor æå¡é
ç½®æ件:
manifest/deploy/supervisor/gf2-demo-api_test.ini
- é¨ç½²èæ¬:
manifest/deploy/supervisor/deploy.sh
- ç产ç¯å¢ supervisor é
ç½®æ件:
-
设置ç®æ æå¡å¨(ä¿®æ¹
deploy.sh
èæ¬)deploy_server="gf2-demo.sre.im" deploy_user="vagrant" deploy_dir=/app/$project_name
ç¸å ³è¯´æåè systemctl é¨ç½²ç¤ºä¾.
-
å¨ç®æ æå¡å¨ä¸æåå®è£ supervisor
åºäº CentOS7 ç³»ç»æ¼ç¤º.
yum update -y yum install epel-release -y yum install supervisor -y systemctl enable supervisord systemctl start supervisord systemctl status supervisord echo_supervisord_conf > /etc/supervisord.conf cat >> /etc/supervisord.conf <<EOF [include] files = supervisord.d/*.ini EOF mkdir -p /etc/supervisord.d
-
æ§è¡é¨ç½²
# é¨ç½²æµè¯ç¯å¢ $ ./manifest/deploy/supervisor/deploy.sh test # é¨ç½²ç产ç¯å¢ $ ./manifest/deploy/supervisor/deploy.sh prod
-
supervisorctl 常ç¨å½ä»¤
# å¯å¨ $ sudo supervisorctl start gf2-demo-api # å ³é(SIGTERMä¿¡å·), å¯æè·SIGTERMä¿¡å·, å®ç°ä¼é å ³é $ sudo supervisorctl stop gf2-demo-api # éå¯: å å ³é(SIGTERMä¿¡å·), åå¯å¨. # NOTE: /etc/supervisord.*ç¸å ³é ç½®æåå¨, éå¯å ·ä½ææå¡å¹¶ä¸ä¼çæ $ sudo supervisorctl restart gf2-demo-api # éå¯ supervisor æ§å¶çæææå¡. # NOTE: å½ /etc/supervisord.*ç¸å ³é ç½®æåå¨, å¿ é¡»æ§è¡æ¤å½ä»¤æè½å è½½çæ $ sudo supervisorctl reload
Docker
-
Dockerfile
éç¨ä¸¤é¶æ®µæ建, éåä½ç§¯å°; å°ä¾èµåºä¸è½½å¥ç¦»åºæ¥å¹¶ä¸åç½®, å©ç¨ç¼åç¹æ§æé«ç¼è¯é度.
# syntax=docker/dockerfile:1 # Step 1: build binary FROM golang:1.17 as builder ENV GOPROXY https://goproxy.cn,direct WORKDIR /src # pre-copy/cache go.mod for pre-downloading dependencies and # only redownloading them in subsequent builds if they change COPY Makefile ./ RUN make cli COPY go.mod go.sum ./ RUN go mod download && go mod verify COPY . . RUN make build OS="linux" # Step 2: copy binary from step 1 FROM loads/alpine:3.8 ENV GF_GERROR_BRIEF=true WORKDIR /app COPY --from=builder /src/bin/linux_amd64/gf2-demo-api . EXPOSE 9000 ENTRYPOINT [ "./gf2-demo-api" ]
-
å¶ä½å®¹å¨éå
$ cd gf2-demo $ make image $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE gf2-demo-api 20230221113306.0d26121.dirty 58e6953c2e1b 15 seconds ago 30.1MB
-
è¿è¡å®¹å¨
# å¼åç¯å¢ $ docker run --name gf2-demo -p80:9000 -d gf2-demo-api:20230221113306.0d26121.dirty # æµè¯ç¯å¢ $ docker run --name gf2-demo -p80:9000 -e GF_GCFG_FILE=config.test.yaml -d gf2-demo-api:20230221113306.0d26121.dirty # ç产ç¯å¢ $ docker run --name gf2-demo -p80:9000 -e GF_GCFG_FILE=config.prod.yaml -d gf2-demo-api:20230221113306.0d26121.dirty
-
éªè¯
-
æ¥çæ¯å¦æåè¿è¡:
æµè§å¨è®¿é®http://localhost/swagger
, åç api ææ¡£æ¯å¦æ£å¸¸å±ç¤º. -
æ¥çäºè¿å¶åºç¨çæ¬ä¿¡æ¯
$ docker exec -it gf2-demo ./gf2-demo-api -v # è¾åºå¦ä¸: App Version: v0.7.0 Git Commit: 2023-02-17 19:32:05 95390e39485aa29050c2327c263a732267ec3eb3 Build Time: 2023-02-20 06:18:57 Go Version: go1.17.13 GF Version: v2.3.2
-
æ¥çä¸åç¯å¢ä¸, ç¨åºä½¿ç¨çé ç½®æ件æ¯å¦æ£ç¡®
# æ¥ç容å¨è¾åºçæ¥å¿ $ docker logs gf2-demo # å¦æé ç½®äºæ¥å¿ä¿åå°æ件, ä¹å¯ç»å½å°å®¹å¨å é¨è¿è¡æ¥ç. $ docker exec -it gf2-demo sh # è¾åºçé¨åæ¥å¿æªå: 2023-02-17 18:52:36.568 [DEBU] {7f0f8d5a279744179740f477f49fbd06} /Users/xg/github/gf2-demo/internal/cmd/apiserver/apiserver.go:79: use config file: &{defaultName:config searchPaths:0xc0000bf6e0 jsonMap:0xc000303720 violenceCheck:false}
ä¸é¢æ¥å¿ä¸ç
defaultName
å¦æ为config
, 代表å¼åç¯å¢; 为config.test.yaml
, 代表æµè¯ç¯å¢; 为config.prod.yaml
, 代表ç产ç¯å¢.
-
å¦ä½ä¼é å ³é
# å ³é: ä¼åéSIGTERMä¿¡å·, gf2-demoæè·è¯¥ä¿¡å·ç»è¿å¤ç, å¯å®ç°ä¼é å ³é $ docker stop gf2-demo # éå¯: å å ³é(SIGTERMä¿¡å·), åå¯å¨, å¯å®ç°ä¼é å ³é $ docker restart gf2-demo # 强å¶å ³é(SIGKILLä¿¡å·), gf2-demoæ æ³æè·å°SIGKILLä¿¡å·, ç´æ¥éåº $ docker kill gf2-demo # 强å¶å ³é并å é¤å®¹å¨(SIGKILLä¿¡å·) $ docker rm -f gf2-demo
ä¼é å ³éæµè¯
GoFrame
ä» V2.4.0
çæ¬å¼å§å·²æ¯ææè·SIGTERM
ä¿¡å·æ¥å®ç°ä¼é
å
³éæå¡. 以ä¸ä¸ç§é¨ç½²æ¹å¼(Systemctl
/Supervisor
/Docker
)å¨å
³éæéå¯æå¡çæ¶ååæ¯åéSIGTERM
ä¿¡å·ç»æå¡è¿ç¨, æ以é½è½ä¼é
å
³éæå¡.
å¼å§æµè¯, åå¤è³å° 3 个 terminal çªå£.
-
模ææ¥å£ååºå»¶æ¶ 8 ç§
æµè¯æ¥å£:
GET localhost:9000/v1/demo/:fielda
// internal/controller/demo.go func (c *cDemo) Get(ctx context.Context, req *v1.DemoGetReq) (*v1.DemoGetRes, error) { // å å ¥æ¤è¡ä»£ç , 模æå»¶è¿ time.Sleep(8 * time.Second) demoInfo, err := service.Demo().Get(ctx, req.Fielda) if err != nil { return nil, err }
-
é ç½®æ件ä¸ç
server.gracefulShutdownTimeout
è°æ´ä¸º 10 ç§server: gracefulShutdownTimeout: 10 # é»è®¤ 5ç§, å»ºè®®æ ¹æ®å®é ä¸å¡æ åµè°æ´
-
å¨çªå£ 1 å¯å¨æå¡
$ make run
å¨è¾åºæ¥å¿ä¸æ¾å°æå¡ PID 为 80273, å¨æ¥éª¤ 5 ä¼ç¨å°:
2023-04-25 11:31:16.716 [INFO] pid[80273]: http server started listening on [:9000]
-
å¨çªå£ 2 模æ请æ±
$ curl localhost:9000/v1/demo/windvalley
请æ±å¡ä½, å¨çå¾ ååºä¸.
-
å¨çªå£ 3 模æä¼é å ³éæå¡
æ§è¡å®ç¬¬ 4 æ¥å, ç«å³æ§è¡å¦ä¸å½ä»¤:
$ kill -SYSTERM 80273
çªå£ 1 è¾åº:
2023-04-25 11:41:52.004 80273: server gracefully shutting down by signal: terminated
æ¤æ¶, 并没æ马ä¸å ³éæå¡, å¨çå¾ è¯·æ±å¤çå®æ. å ç§éå, 请æ±å¤çå®æ, æå¡å ³é, çªå£ 1 继ç»è¾åº:
2023-04-25 11:41:57.863 [DEBU] {708a5f7a8710591764de0d572ec0fc19} [ 1 ms] [default] [gf2_demo] [rows:1 ] SELECT * FROM `demo` WHERE `fielda`='windvalley' LIMIT 1 2023-04-25 11:41:57.863 {708a5f7a8710591764de0d572ec0fc19} 200 "GET http localhost:9000 /v1/demo/windvalley HTTP/1.1" 8.005, 127.0.0.1, "", "curl/7.79.1" 2023-04-25 11:41:58.275 [INFO] pid[80273]: all servers shutdown
æ¤æ¶çªå£ 2 è¾åº:
{"code":"OK","message":"","traceid":"708a5f7a8710591764de0d572ec0fc19","data":{"id":14,"fielda":"windvalley","created_at":"2023-02-14 17:12:59","updated_at":"2023-02-14 17:12:59"}}
请æ±æå.
NOTE:
è½ç¶
GoFrame
æ¯æä¼é éå¯ç¹æ§, ä½å¨ç产ç¯å¢ä¸ä¸å»ºè®®å¼å¯:
Systemctl
æSupervisor
æ æ³å¾å¥½çæ¥ç®¡åæ§å¶ç¶åè¿ç¨. ä¼é å ³éç¹æ§å·²è¶³å¤, å¯éè¿é¨ç½²è´è½½åè¡¡å¨(LVS ç)æ¥å®ç°ä¸ä¸ææå¡.对äº
Docker
/K8S
ç容å¨ååºæ¯, æä½³å®è·µè¦æ±è¿ç¨å容å¨æ¬èº«åçå½å¨æ, æ´ä¸è½å¼å¯ä¼é éå¯ç¹æ§. 容å¨ç®¡çå¹³å°æ¬èº«æ¯æ容å¨çå¹³æ»å¯å, å®ç°ä¸ä¸ææå¡.
ä½¿ç¨ Makefile 管çé¡¹ç® â
$ make help
Usage:
make [TARGETS] [OPTIONS]
Targets:
cli Install/Update to the latest Gf Cli tool
lint Run golangci-lint
ctrl Generate Go files for Controller
dao Generate Go files for Dao/Do/Entity
service Generate Go files for Service
run Run gf2-demo-api for development environment
run.cli Run gf2-demo-cli for development environment
build Build gf2-demo-api binary
build.cli Build gf2-demo-cli binary
image Build docker image
image.push Build docker image and automatically push to docker repo
help Show this help
Options:
ARCH The multiple ARCH to build. Default is "amd64";
This option is available for: make build*;
Example: make build ARCH="amd64,arm64"
OS The multiple OS to build. Default is "darwin,linux";
This option is available for: make build*;
Example: make build OS="linux,darwin,windows"
TAG Docker image tag. Default is generated from current git commit;
This option is available for: make image*;
Example: make image TAG="v0.6.0"
使ç¨ç¤ºä¾:
# å®è£
ææ°çgf
$ make cli
# è¿è¡ golangci-lint æ£æ¥ä»£ç
$ make lint
# ç©ç表æå¢å æ表ç»æææ´æ°æ¶, èªå¨çæææ´æ°æ°æ®å±ç¸å
³ä»£ç
$ make dao
# èªå¨çæ internal/controller/ æ§å¶å¨å±ä»£ç
$ make ctrl
# internal/logic/ æ代ç åå¨å, 使ç¨æ¤å½ä»¤èªå¨çæ internal/service/ æ¥å£ä»£ç
$ make service
# å¼åç¯å¢çå¯å¨ gf2-demo-api
$ make run
# å¼åç¯å¢çå¯å¨ gf2-demo-cli
$ make run.cli
# ç¼è¯ gf2-demo-cli
$ make build.cli
# æç
§ hack/config.yaml é
ç½®, ç¼è¯ gf2-demo-api
$ make build
# ç¼è¯ linux_arm64 æ¶æçäºè¿å¶æ件
$ make build OS="linux" ARCH="arm64"
# å¶ä½dockeréå: gf2-demo-api:20230220144044.2d4bb19.dirty
$ make image
# èªå®ä¹tagå¶ä½dockeréå: gf2-demo-api:v0.3.0
$ make image TAG="v0.3.0"
# å¶ä½éå并æ¨éå°dockerä»åº
$ make image.push
NOTE: å¦ææ¯ macOS ç³»ç», éè¦æåå®è£
gsed
å½ä»¤.
åæ´é¡¹ç®å称 â
请æå¦ä¸æ¥éª¤ä¾¿æ·å°å°æ¬é¡¹ç®å称 gf2-demo
æ¹æä½ èªå·±ç项ç®å称 new-project
.
-
åæ´é¡¹ç®ç®å½å称
$ mv gf2-demo new-project
-
è¿è¡åæ´èæ¬
$ cd new-project $ hack/change_project_name.sh new-project
NOTE:
- å¦ææ¯ macOS ç³»ç», éè¦æåå®è£
gsed
å½ä»¤.
- mysql æ°æ®åºé
ç½®éè¦æ ¹æ®ä½ çå®é
æ
åµè¿è¡æå¨ä¿®æ¹, æ以ä¸å å¤ï¼
-
manifest/config/*.yaml
-
hack/config.yaml
-
manifest/sql/gf2_demo.sql
-
- å¦ææ¯ macOS ç³»ç», éè¦æåå®è£
-
éªè¯
$ make build è¾åºå¦ä¸: bin âââ darwin_amd64 â  âââ new-project-api âââ linux_amd64 âââ new-project-api
ð References
- https://github.com/gogf/gf
- https://goframe.org/display/gf
- https://pkg.go.dev/github.com/gogf/gf/v2
âï¸ License
This project is under the MIT License. See the LICENSE file for the full license text.