Author: Jack
Recently, I found a new star micro-service framework in Golang community, from the good future. Just looking at the name, it is very ambitious. Before, I only played go-Micro, but I have not really used it in the project, but I think micro-service and GRPC are very tall and still haven’t really played in the project. I have a look at the official tool is really good, just need to define, comfortable file JIA structure has been generated, only need to care about the business, plus there is a recent voting activity, plus the recent years in Taiwan is also more popular, so I decided to play,
Open source: github.com/jackluo2012…
Let’s talk about the china-Taiwan architecture first:
The concept of the middle platform is to unify one app by one, as I understand it.
Now a company has a lot of public accounts, small programs, wechat, Alipay, and XXX, XXX, many platforms, every time we develop, we always need to do user login services, keep copying code, and then we are thinking about whether we can have a set of independent user services. Just tell me that you need to transfer a platform you want to login (such as wechat), wechat login, the need is the client to return a code to the server, and then the server with this code to wechat to get user information, anyway, we all understand.
We decided to put all the information into the configuration public service, where we store wechat, Alipay, and other platforms appID,appkey, and payment appID,appkey, and so on.
Finally, implementation, the entire REPO:
-
Gateway, we use: GO – Zero Api service
-
The other is the service, which we use with go-Zero’s RPC service
Take a look at the directory structure
The whole project completed, I a person to write a week, I realized the above platform system.
Datacenter – API service
First look at the official document www.yuque.com/tal-tech/go…
Let’s set up the gateway first
➜ blogs mkdir Datacenter && CD Datacenter ➜ Datacenter Go mod init Datacenter Go: Creating New Go.mod: The module datacenter ➜ datacenterCopy the code
View the book directory:
➜ datacenter tree. └─ go. Mod 0 directories, 1 fileCopy the code
Creating an API file
➜ datacenter goctl API -o Datacenter. API done. ➜ Datacenter tree. ├── datacenterCopy the code
Defining API services
It includes the above public service, user service and voting activity service respectively
info(
title: "中台系统"
desc: "中台系统"
author: "jackluo"
email: "[email protected]"
)
// 获取 应用信息
type Beid struct {
Beid int64 `json:"beid"`
}
type Token struct{
Token string `json:"token"`
}
type WxTicket struct{
Ticket string `json:"ticket"`
}
type Application struct {
Sname string `json:"Sname"` //名称
Logo string `json:"logo"` // login
Isclose int64 `json:"isclose"` //是否关闭
Fullwebsite string `json:"fullwebsite"` // 全站名称
}
type SnsReq struct{
Beid
Ptyid int64 `json:"ptyid"` //对应平台
BackUrl string `json:"back_url"` //登陆返回的地址
}
type SnsResp struct{
Beid
Ptyid int64 `json:"ptyid"` //对应平台
Appid string `json:"appid"` //sns 平台的id
Title string `json:"title"` //名称
LoginUrl string `json:"login_url"` //微信登陆的地址
}
type WxShareResp struct {
Appid string `json:"appid"`
Timestamp int64 `json:"timestamp"`
Noncestr string `json:"noncestr"`
Signature string `json:"signature"`
}
@server(
group: common
)
service datacenter-api {
@doc(
summary: "获取站点的信息"
)
@handler votesVerification
get /MP_verify_NT04cqknJe0em3mT.txt (SnsReq) returns (SnsResp)
@handler appInfo
get /common/appinfo (Beid) returns (Application)
@doc(
summary: "获取站点的社交属性信息"
)
@handler snsInfo
post /common/snsinfo (SnsReq) returns (SnsResp)
// 获取分享的
@handler wxTicket
post /common/wx/ticket (SnsReq) returns (WxShareResp)
}
// 上传需要登陆
@server(
jwt: Auth
group: common
)
service datacenter-api {
@doc(
summary: "七牛上传凭证"
)
@handler qiuniuToken
post /common/qiuniu/token (Beid) returns (Token)
}
// 注册请求
type RegisterReq struct {
// TODO: add members here and delete this comment
Mobile string `json:"mobile"` // 基本一个手机号码就完事
Password string `json:"password"`
Smscode string `json:"smscode"` // 短信码
}
// 登陆请求
type LoginReq struct{
Mobile string `json:"mobile"`
Type int64 `json:"type"` // 1.密码登陆,2.短信登陆
Password string `json:"password"`
}
// 微信登陆
type WxLoginReq struct {
Beid int64 `json:"beid"` // 应用id
Code string `json:"code"` // 微信登陆密钥
Ptyid int64 `json:"ptyid"` // 对应平台
}
//返回用户信息
type UserReply struct {
Auid int64 `json:"auid"`
Uid int64 `json:"uid"`
Beid int64 `json:"beid"` // 应用id
Ptyid int64 `json:"ptyid"` // 对应平台
Username string `json:"username"`
Mobile string `json:"mobile"`
Nickname string `json:"nickname"`
Openid string `json:"openid"`
Avator string `json:"avator"`
JwtToken
}
// 返回APPUser
type AppUser struct{
Uid int64 `json:"uid"`
Auid int64 `json:"auid"`
Beid int64 `json:"beid"` // 应用id
Ptyid int64 `json:"ptyid"` // 对应平台
Nickname string `json:"nickname"`
Openid string `json:"openid"`
Avator string `json:"avator"`
}
type LoginAppUser struct{
Uid int64 `json:"uid"`
Auid int64 `json:"auid"`
Beid int64 `json:"beid"` // 应用id
Ptyid int64 `json:"ptyid"` // 对应平台
Nickname string `json:"nickname"`
Openid string `json:"openid"`
Avator string `json:"avator"`
JwtToken
}
type JwtToken struct {
AccessToken string `json:"access_token,omitempty"`
AccessExpire int64 `json:"access_expire,omitempty"`
RefreshAfter int64 `json:"refresh_after,omitempty"`
}
type UserReq struct{
Auid int64 `json:"auid"`
Uid int64 `json:"uid"`
Beid int64 `json:"beid"` // 应用id
Ptyid int64 `json:"ptyid"` // 对应平台
}
type Request {
Name string `path:"name,options=you|me"`
}
type Response {
Message string `json:"message"`
}
@server(
group: user
)
service user-api {
@handler ping
post /user/ping ()
@handler register
post /user/register (RegisterReq) returns (UserReply)
@handler login
post /user/login (LoginReq) returns (UserReply)
@handler wxlogin
post /user/wx/login (WxLoginReq) returns (LoginAppUser)
@handler code2Session
get /user/wx/login () returns (LoginAppUser)
}
@server(
jwt: Auth
group: user
middleware: Usercheck
)
service user-api {
@handler userInfo
get /user/dc/info (UserReq) returns (UserReply)
}
// 投票活动api
type Actid struct {
Actid int64 `json:"actid"` //活动id
}
type VoteReq struct {
Aeid int64 `json:"aeid"` // 作品id
Actid
}
type VoteResp struct {
VoteReq
Votecount int64 `json:"votecount"` //投票票数
Viewcount int64 `json:"viewcount"` //浏览数
}
// 活动返回的参数
type ActivityResp struct {
Actid int64 `json:"actid"`
Title string `json:"title"` //活动名称
Descr string `json:"descr"` //活动描述
StartDate int64 `json:"start_date"` //活动时间
EnrollDate int64 `json:"enroll_date"` //投票时间
EndDate int64 `json:"end_date"` //活动结束时间
Votecount int64 `json:"votecount"` //当前活动的总票数
Viewcount int64 `json:"viewcount"` //当前活动的总浏览数
Type int64 `json:"type"` //投票方式
Num int64 `json:"num"` //投票几票
}
//报名
type EnrollReq struct {
Actid
Name string `json:"name"` // 名称
Address string `json:"address"` //地址
Images []string `json:"images"` //作品图片
Descr string `json:"descr"` // 作品描述
}
// 作品返回
type EnrollResp struct {
Actid
Aeid int64 `json:"aeid"` // 作品id
Name string `json:"name"` // 名称
Address string `json:"address"` //地址
Images []string `json:"images"` //作品图片
Descr string `json:"descr"` // 作品描述
Votecount int64 `json:"votecount"` //当前活动的总票数
Viewcount int64 `json:"viewcount"` //当前活动的总浏览数
}
@server(
group: votes
)
service votes-api {
@doc(
summary: "获取活动的信息"
)
@handler activityInfo
get /votes/activity/info (Actid) returns (ActivityResp)
@doc(
summary: "活动访问+1"
)
@handler activityIcrView
get /votes/activity/view (Actid) returns (ActivityResp)
@doc(
summary: "获取报名的投票作品信息"
)
@handler enrollInfo
get /votes/enroll/info (VoteReq) returns (EnrollResp)
@doc(
summary: "获取报名的投票作品列表"
)
@handler enrollLists
get /votes/enroll/lists (Actid) returns(EnrollResp)
}
@server(
jwt: Auth
group: votes
middleware: Usercheck
)
service votes-api {
@doc(
summary: "投票"
)
@handler vote
post /votes/vote (VoteReq) returns (VoteResp)
@handler enroll
post /votes/enroll (EnrollReq) returns (EnrollResp)
}
Copy the code
The above basically write API and document ideas
Generate the Datacenter API service
➜ datacenter goctl API Go-api Datacenter.api-dir.done. ➜ Datacenter tree. ├── datacenter. API ├─ etc │ ├─ Datacenter - API. Yaml ├ ─ ─. Mod ├ ─ ─ internal │ ├ ─ ─ the config │ │ └ ─ ─ config. Go │ ├ ─ ─ handler │ │ ├ ─ ─ common │ │ │ ├ ─ ─ Appinfohandler. Go │ │ │ ├ ─ ─ qiuniutokenhandler. Go │ │ │ ├ ─ ─ snsinfohandler. Go │ │ │ ├ ─ ─ votesverificationhandler. Go │ │ │ ├─ ├─ Bass Exercises. Go │ ├─ Bass Exercises. Go │ ├─ code │ ├─ bass Exercises. Go │ ├─ bass Exercises ├ ─ ─ pinghandler. Go │ │ │ ├ ─ ─ registerhandler. Go │ │ │ ├ ─ ─ userinfohandler. Go │ │ │ └ ─ ─ wxloginhandler. Go │ │ └ ─ ─ votes │ │ ├ ─ ─ activityicrviewhandler. Go │ │ ├ ─ ─ activityinfohandler. Go │ │ ├ ─ ─ enrollhandler. Go │ │ ├ ─ ─ enrollinfohandler. Go │ │ ├ ─ ─ enrolllistshandler. Go │ │ └ ─ ─ votehandler. Go │ ├ ─ ─ logic │ │ ├ ─ ─ common │ │ │ ├ ─ ─ appinfologic. Go │ │ │ ├ ─ ─ Qiuniutokenlogic. Go │ │ │ ├ ─ ─ snsinfologic. Go │ │ │ ├ ─ ─ votesverificationlogic. Go │ │ │ └ ─ ─ wxticketlogic. Go │ │ ├ ─ ─ User │ │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Go │ ├─ Userinfologic. Go │ │ │ └ ─ ─ wxloginlogic. Go │ │ └ ─ ─ votes │ │ ├ ─ ─ activityicrviewlogic. Go │ │ ├ ─ ─ activityinfologic. Go │ Go │ ├─ ├─ ├─ ├─ exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercises └ ─ ─ usercheckmiddleware. Go │ ├ ─ ─ SVC │ │ └ ─ ─ servicecontext. Go │ └ ─ ─ types │ └ ─ ─ types. Go └ ─ ─ datacenter. Go 14 directories, 43 filesCopy the code
Let’s open /etc/datacenter-api.yaml to add the necessary configuration information
Name: datacenter-api
Log:
Mode: console
Host: 0.0. 0. 0
Port: 8857
Auth:
AccessSecret: Your jwtwon Secret
AccessExpire: 86400
CacheRedis:
- Host: 127.0. 01.: 6379
Pass: password
Type: node
UserRpc:
Etcd:
Hosts:
- 127.0. 01.: 2379
Key: user.rpc
CommonRpc:
Etcd:
Hosts:
- 127.0. 01.: 2379
Key: common.rpc
VotesRpc:
Etcd:
Hosts:
- 127.0. 01.: 2379
Key: votes.rpc
Copy the code
UserRpc, CommonRpc, and VotesRpc, I’m going to write them first, and then I’m going to add them as I go.
Let’s start by writing the CommonRpc service.
CommonRpc service
New Project Directory
➜ datacenter mkdir -p common/ RPC && CD common/ RPCCopy the code
It’s just going to be in the datacenter directory, and because common is probably going to provide not just RPC services, but maybe API services, we added the RPC directory
Goctl Creates a template
➜ rpc goctl rpc template -o=common.proto
➜ rpc ls
common.proto
Copy the code
Fill in the contents:
➜ rpc cat common.proto
syntax = "proto3";
package common;
message BaseAppReq{
int64 beid=1;
}
message BaseAppResp{
int64 beid=1;
string logo=2;
string sname=3;
int64 isclose=4;
string fullwebsite=5;
}
// The requested API
message AppConfigReq {
int64 beid=1;
int64 ptyid=2;
}
// The value returned
message AppConfigResp {
int64 id=1;
int64 beid=2;
int64 ptyid=3;
string appid=4;
string appsecret=5;
string title=6;
}
service Common {
rpc GetAppConfig(AppConfigReq) returns(AppConfigResp);
rpc GetBaseApp(BaseAppReq) returns(BaseAppResp);
}
Copy the code
Gotcl generates RPC services
➜ RPC goctl RPC proto - SRC common. Proto - dir. Protoc - I = / Users/jackluo/works/blogs/datacenter/common/RPC common proto --go_out=plugins=grpc:/Users/jackluo/works/blogs/datacenter/common/rpc/common Done.Copy the code
➜ RPC tree. ├─ common │ ├─ common. Pb. Go ├─ common Common. Yaml └ ─ ─ internal ├ ─ ─ the config │ └ ─ ─ config. Go ├ ─ ─ logic │ ├ ─ ─ getappconfiglogic. Go │ └ ─ ─ getbaseapplogic. Go ├ ─ ─ ├ ─ exfoliate-1st imp. Go exfoliate-1st imp. Go exfoliate-1st imp. Go exfoliate-1st impCopy the code
Basically, all the directory specification and structure stuff is generated, so you don’t have to worry about the project directory, how to put it, how to organize it.
Mysql and other Redis information can be written to the configuration information:
Name: common.rpc
ListenOn: 127.0. 01.: 8081
Mysql:
DataSource: Root: admin @ TCP (127.0.0.1:3306)/datacenter? charset=utf8&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
- Host: 127.0. 01.: 6379
Pass:
Type: node
Etcd:
Hosts:
- 127.0. 01.: 2379
Key: common.rpc
Copy the code
Let’s add database services:
➜ RPC CD.. ➜ common ls RPC ➜ common PWD/Users/jackluo/works/blogs/datacenter/common ➜ common goctl model mysql datasource -url="root:admin@tcp(127.0.0.1:3306)/datacenter" -table="base_app" -dir./model -c Done. ➜ Common tree Baseappmodel. Go │ └ ─ ─ vars. Go └ ─ ─ the RPC ├ ─ ─ common │ └ ─ ─ common. Pb. Go ├ ─ ─ common. Go ├ ─ ─ common. The proto ├ ─ ─ commonclient │ └ ─ ─ common. Go ├ ─ ─ etc │ └ ─ ─ common. The yaml └ ─ ─ internal ├ ─ ─ the config │ └ ─ ─ config. Go ├ ─ ─ logic │ ├ ─ ─ getappconfiglogic. Go │ ├─ ├─ org.txt TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXTCopy the code
This completes the basic RPC, and then we link RPC to model and API. The official documentation is quite detailed, so here is just the code:
➜ common cat rpc/internal/config/config.go
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
zrpc.RpcServerConf
Mysql struct {
DataSource string
}
CacheRedis cache.ClusterConf
}
Copy the code
Then modify in SVC:
➜ common cat rpc/internal/svc/servicecontext.go
package svc
import (
"datacenter/common/model"
"datacenter/common/rpc/internal/config"
"github.com/tal-tech/go-zero/core/stores/sqlx"
)
type ServiceContext struct {
c config.Config
AppConfigModel model.AppConfigModel
BaseAppModel model.BaseAppModel
}
func NewServiceContext(c config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.Mysql.DataSource)
apm := model.NewAppConfigModel(conn, c.CacheRedis)
bam := model.NewBaseAppModel(conn, c.CacheRedis)
return &ServiceContext{
c: c,
AppConfigModel: apm,
BaseAppModel: bam,
}
}
Copy the code
Now that the code above has associated RPC with the Model database, let’s associate RPC with the API:
➜ datacenter cat internal/config/config.go
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rest.RestConf
Auth struct {
AccessSecret string
AccessExpire int64
}
UserRpc zrpc.RpcClientConf
CommonRpc zrpc.RpcClientConf
VotesRpc zrpc.RpcClientConf
CacheRedis cache.ClusterConf
}
Copy the code
Join the SVC service:
➜ datacenter cat internal/svc/servicecontext.go
package svc
import (
"context"
"datacenter/common/rpc/commonclient"
"datacenter/internal/config"
"datacenter/internal/middleware"
"datacenter/shared"
"datacenter/user/rpc/userclient"
"datacenter/votes/rpc/votesclient"
"fmt"
"net/http"
"time"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/syncx"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
type ServiceContext struct {
Config config.Config
GreetMiddleware1 rest.Middleware
GreetMiddleware2 rest.Middleware
Usercheck rest.Middleware
UserRpc userclient.User / / user
CommonRpc commonclient.Common
VotesRpc votesclient.Votes
Cache cache.Cache
RedisConn *redis.Redis
}
func timeInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ... grpc.CallOption) error {
stime := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
iferr ! =nil {
return err
}
fmt.Printf("Call %s method time: %v\n", method, time.Now().Sub(stime))
return nil
}
func NewServiceContext(c config.Config) *ServiceContext {
ur := userclient.NewUser(zrpc.MustNewClient(c.UserRpc, zrpc.WithUnaryClientInterceptor(timeInterceptor)))
cr := commonclient.NewCommon(zrpc.MustNewClient(c.CommonRpc, zrpc.WithUnaryClientInterceptor(timeInterceptor)))
vr := votesclient.NewVotes(zrpc.MustNewClient(c.VotesRpc, zrpc.WithUnaryClientInterceptor(timeInterceptor)))
/ / cache
ca := cache.NewCache(c.CacheRedis, syncx.NewSharedCalls(), cache.NewCacheStat("dc"), shared.ErrNotFound)
rcon := redis.NewRedis(c.CacheRedis[0].Host, c.CacheRedis[0].Type, c.CacheRedis[0].Pass)
return &ServiceContext{
Config: c,
GreetMiddleware1: greetMiddleware1,
GreetMiddleware2: greetMiddleware2,
Usercheck: middleware.NewUserCheckMiddleware().Handle,
UserRpc: ur,
CommonRpc: cr,
VotesRpc: vr,
Cache: ca,
RedisConn: rcon,
}
}
Copy the code
Basically, we can call from logic’s file directory:
cat internal/logic/common/appinfologic.go
package logic
import (
"context"
"datacenter/internal/svc"
"datacenter/internal/types"
"datacenter/shared"
"datacenter/common/model"
"datacenter/common/rpc/common"
"github.com/tal-tech/go-zero/core/logx"
)
type AppInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAppInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) AppInfoLogic {
return AppInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AppInfoLogic) AppInfo(req types.Beid) (appconfig *common.BaseAppResp, err error) {
// Check if there are values in the cache
err = l.svcCtx.Cache.GetCache(model.GetcacheBaseAppIdPrefix(req.Beid), appconfig)
iferr ! =nil && err == shared.ErrNotFound {
appconfig, err = l.svcCtx.CommonRpc.GetBaseApp(l.ctx, &common.BaseAppReq{
Beid: req.Beid,
})
iferr ! =nil {
return
}
err = l.svcCtx.Cache.SetCache(model.GetcacheBaseAppIdPrefix(req.Beid), appconfig)
}
return
}
Copy the code
So, basically, it’s connected, but I don’t have to change anything else, UserRPC, VotesRPC, so I’m not going to write it here.
Use notes
Go – zero is sweet, because it has a goctl tools, he can be generated automatically all the code structure of the good, we won’t go to struggle, the directory structure, how to organize, not a few years of architecture ability is bad, what is the specification that, concurrent, fuse, no, take an examination of other filter, it is good to concentrate on the implementation of the business, There’s microservices, there’s service discovery, a whole bunch of things that you don’t care about, because go-Zero already does it internally.
I’ve been writing code for over 10 years, and the PHP that I’ve been using before, well known as Laravel, ThinkPHP, is basically modular, and it really costs money to implement things like Microserver, but when you use Go Zero, you’re just as easy to develop as an API, what other services are found, Don’t worry about that at all, just focus on the business.
A good language, framework, and their underlying thinking is always the idea of being efficient and not working overtime. I believe go-Zero will improve the efficiency of you and your team or company. The author of Go-Zero said that they have a team dedicated to organizing the Go-Zero framework, and the purpose should be obvious, that is, to improve their own development efficiency, streamline and standardization, which is the principle to improve work efficiency. When we encounter problems or bugs, my first thought is not how to solve my bugs. I was wondering if there was something wrong with my process, which of my processes was causing bugs, and I ended up believing that Go-Zero could be the preferred framework for microservices development.
Finally, a few words about the pits encountered:
grpc
[GRPC] [GRPC] [GRPC] [GRPC] [GRPC] [GRPC] [GRPC] [GRPC]
Through the GRPC official library jSONPB to achieve, the official in its setting has a structure to achieve the conversion of protoc buffer to JSON structure, and can be configured according to the field conversion requirements.
- Cross-domain problem
Go – Zero set, feel no effect, big guy said through nginx Settings, later found still not work, recently forced to get a domain name, later have time to solve.
sqlx
Go-zero SQLX issue, this really took a long time:
Timestamp is used in the database. For example, my field is delete_at and the default value is null in the database. When the result is inserted, an Incorrect Datetime value is reported: ‘0000-00-00’ for column ‘deleted_at’ at row 1″} unsupported Scan, storing driver.Value type \u003cnil\u003e into type *time.Time”
Db :”.omitEmpty “db:”.omitEmpty”
Conversion from collation UTf8_general_ci into UTf8MB4_unicode_ci, Conversion from collation UTf8_general_ci into UTf8MB4_unicode_ci.
- Data connection
Mysql > create database with utF8MB4 and utF8MB4_unicoDE_CI.
In this case, all tables and string fields are encoded in this format. If you don’t want all of them to be, you can set them separately. That’s not the point. Because navicat is easy to set up, just click on it manually.
Here’s the kicker: Golang uses the github.com/go-sql-driver/mysql driver, which will connect to mysql’s DSN (since I’m using GORM here, DSN may not be quite the same as the native format, but that’s ok, Just focus on charset and Collation.)
root:password@/name? ParseTime =True&loc=Local&charset=utf8 change to: root:password@/name? parseTime=True&loc=Local&charset=utf8mb4&collation=utf8mb4_unicode_ci
Go-zero project address
Github.com/tal-tech/go…
Please like 👍