中,使用了go-zero 创建了order 和 user 两个微服务。而order作为grpc的客户端,user 作为grpc的服务端,打通了 order 到 user的调用。接下来,我们在user中,加入mysql组件。确保数据能够写到数据库。
可以使用 DBeaver 工具,连接mysql,并创建zero-mall数据库。
use zero_mall;
create table `user`(
id bigint(0) not null auto_increment,
name varchar(255) character set utf8mb4 COLLATE utf8mb4_general_ci not null,
gender varchar(255) character set utf8mb4 COLLATE utf8mb4_general_ci not null,
PRIMARY key (id) using btree
);
goctl model mysql ddl -src user.sql -dir . -c
这步操作,会生成操作数据库相关的代码。 由于生成的代码比较乱。我们在做数据库连接的时候,会摘取部分代码,按照自己的思路做数据库相关操作。
package model
type User struct {
Id int64 `db:"id"`
Name string `db:"name"`
Gender string `db:"gender"`
}
// 返回表名
func (User) TableName() string {
return "user"
}
当我们创建了model之后,就有User 实体,该实体映射数据库的User表。接下来的我们需要创建数据库的连接。
sqlx.go 文件
package database
import "github.com/zeromicro/go-zero/core/stores/sqlx"
// we use go-zero sqlx
type DBConn struct {
Conn sqlx.SqlConn
}
func Connect(datasource string) *DBConn {
return &DBConn{
Conn: sqlx.NewMysql(datasource),
}
}
package repo
import (
"context"
"user/internal/model"
)
type UserRepo interface {
Save(ctx context.Context, user *model.User) error
}
该代码提供了一个Save接口,用来保存 User。
package dao
import (
"context"
"fmt"
"user/database"
"user/internal/model"
)
type UserDao struct {
*database.DBConn
}
func NewUserDao(conn *database.DBConn) *UserDao {
return &UserDao{
conn,
}
}
func (d *UserDao) Save(ctx context.Context, user *model.User) error {
sql := fmt.Sprintf("insert into %s (name, gender) values(?, ?)", user.TableName())
result, err := d.Conn.ExecCtx(ctx, sql, user.Name, user.Gender)
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
user.Id = id
return nil
}
由于,我们需要连接 mysql 数据库。因此,我们需要从配置文件中读取 mysql 连接的配置。go-zero 提供了一种简便方式,可以自动读取配置。
首先,修改 user/etc/user.yaml中的配置, 如下:
Name: user.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: user.rpc
Mysql:
Datasource: root:thinker@tcp(127.0.0.1:33306)/zero_mall?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai
Mysql 的配置是我自己手动添加的。
user/internal/config/config.go 文件如下:
package config
import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
zrpc.RpcServerConf
Mysql MysqlConfig
}
type MysqlConfig struct {
DataSource string
}
该文件中添加了 MySqlConfig 结构体,并且在Config 结构体中添加了 Mysql 变量。这样 go-zero 可以自动读取到 user.yaml 中 Mysql连接配置。
user.proto 文件
option go_package = "./user";
message IdRequest {
string id = 1;
}
message UserRequest {
string id = 1;
string name = 2;
string gender = 3;
}
message UserResponse {
string id = 1;
string name = 2;
string gender = 3;
}
service User {
rpc getUser(IdRequest) returns(UserResponse);
rpc save(UserRequest) returns(UserResponse);
}
该代码中,添加了 rpc save(UserRequest) returns(UserResponse); 接口。并使用如下命令重新生成代码:
goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
将生成的 types/user 和 userclient/下的代码,覆盖之前生成的代码。并且把 internal/server/userserver.go 文件中的 如下代码(新生成的代码):
func (s *UserServer) Save(ctx context.Context, in *user.UserRequest) (*user.UserResponse, error) {
l := logic.NewUserLogic(ctx, s.svcCtx)
return l.SaveUser(in)
}
放到 user/internal/server/userserver.go(旧文件中) 文件中。
修改 user/internal/logic/getuserlogic.go代码,为了命名规范,我将getuserlogic.go 该成了 userlogic.go。
package logic
import (
"context"
"strconv"
"user/internal/model"
"user/internal/svc"
"user/types/user"
"github.com/zeromicro/go-zero/core/logx"
)
type UserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLogic {
return &UserLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *UserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
// todo: add your logic here and delete this line
return &user.UserResponse{
Id: in.GetId(),
Name: "hello user name",
Gender: "man",
}, nil
}
func (l *UserLogic) SaveUser(in *user.UserRequest) (*user.UserResponse, error) {
data := &model.User{
Name: in.Name,
Gender: in.Gender,
}
err := l.svcCtx.UserRepo.Save(context.Background(), data)
if err != nil {
return nil, err
}
return &user.UserResponse{
Id: strconv.FormatInt(data.Id, 10),
Name: data.Name,
Gender: data.Gender,
}, nil
}
userlogic 相当于业务组件,这里实现了用户保存到数据库的逻辑。
到此,在user服务中连接mysql数据库,并实现通过rpc接口调用将用户数据保存到 mysql 逻辑已经完成。
goctl api new userapi
module userapi
go 1.22.2
go work use userapi/
cd userapi/
go mod tidy
到此,生成的代码结构如下:
Name: userapi-api
Host: 0.0.0.0
Port: 8888
UserRpc:
Etcd:
Hosts:
- 127.0.0.1:2379
Key: user.rpc
该文件中增加了 UserRpc 配置,主要是为了调用rpc接口。
package config
import (
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
rest.RestConf
UserRpc zrpc.RpcClientConf
}
增加了 UserRpc 变量,为了读取 user-api.yaml 中的配置。
register.go
package handler
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"userapi/internal/logic"
"userapi/internal/types"
)
func (u *UserHandler) register(w http.ResponseWriter, r *http.Request) {
var req types.Request
if err := httpx.ParseJsonBody(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := logic.NewUserLogic(r.Context(), u.svcCtx)
resp, err := l.Register(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
userhandler.go
package handler
import (
"userapi/internal/svc"
)
type UserHandler struct {
svcCtx *svc.ServiceContext
}
func NewUserHandler(svcCtx *svc.ServiceContext) *UserHandler {
return &UserHandler{
svcCtx: svcCtx,
}
}
删除 自动生成的代码 userapihandler.go 文件。
将生成的 userapi/internal/handler/routers.go 文件修改如下:
// Code generated by goctl. DO NOT EDIT.
package handler
import (
"net/http"
"userapi/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
handler := NewUserHandler(serverCtx)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodPost,
Path: "/register",
Handler: handler.register,
},
},
)
}
// Code generated by goctl. DO NOT EDIT.
package types
type Request struct {
Name string `json:"name"`
Gender string `json:"gender"`
}
type Response struct {
Message string `json:"message"`
Data any `json:"data"`
}
这里主要是 为了处理 http请求过来的 json数据。
servicecontext.go 文件
package svc
import (
"github.com/zeromicro/go-zero/zrpc"
"user/userclient"
"userapi/internal/config"
)
type ServiceContext struct {
Config config.Config
UserRpc userclient.User
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
}
}
这里加入了 UserRpc 变量,为了远程调用User服务提供Save方法。
package logic
import (
"context"
"time"
"user/types/user"
"userapi/internal/svc"
"userapi/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type UserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLogic {
return &UserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UserLogic) Register(req *types.Request) (resp *types.Response, err error) {
// todo: add your logic here and delete this line
ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFunc()
userResponse, err := l.svcCtx.UserRpc.Save(ctx, &user.UserRequest{
Name: req.Name,
Gender: req.Gender,
})
if err != nil {
return nil, err
}
return &types.Response{
Message: "success",
Data: userResponse,
}, nil
}
由于在 userapi中,用到user中的代码。并且之前的 order中也直接引用了user中的代码。这样增加了耦合性。我们可以把这部分公共的代码拿出来,这样以后。即使user服务发生变动,只要公共部分不变。那么userapi和order服务就不会受到影响。
mkdir rpc-common
cd rpc-common
在 mall/rpc-common下创建 go.mod文件
module rpc-common
go 1.22.2
go work use rpc-common
当前整理后代码,放在了 mysql 分支下。