go-zero 实战(2)

news2024/11/26 6:59:51

go-zero 实战(1) 中,使用了go-zero 创建了order 和 user 两个微服务。而order作为grpc的客户端,user 作为grpc的服务端,打通了 order 到 user的调用。接下来,我们在user中,加入mysql组件。确保数据能够写到数据库。

引入MySQL

1. 启动mysql,创建数据库 zero-mall

可以使用 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
);

2. 在 user/internal 目录下创建 model目录,并创建user.sql

创建 user.sql 文件,并将上面脚本放入 user.sql 文件中。然后,在当前目录下,执行:

goctl model mysql ddl -src user.sql -dir . -c

这步操作,会生成操作数据库相关的代码。 由于生成的代码比较乱。我们在做数据库连接的时候,会摘取部分代码,按照自己的思路做数据库相关操作。

在当前目录下新建 user.go (user/internal/model/user.go)文件,把当前生成的 usermodel_gen.go 文件中的 User 结构体拿出来,放到user.go 文件中,并添加一个 TableName函数。然后,把生成的 usermodel.go、usermodel_gen.go、vars.go文件删除。最终的 user.go 代码如下:

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表。接下来的我们需要创建数据库的连接。

3. 在user下创建database目录

创建database目录,在该目录下创建sqlx.go文件,主要为了使用 go-zero的orm框架。 当然,这个目录下也可以创建如 mongo、redis的连接。也可以使用不同的orm框架,如gorm等。

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),
	}
}

4. 创建操作数据的接口,并提供实现

在 user/internal/ 下创建 repo 目录,并创建 user.go 文件

package repo

import (
	"context"
	"user/internal/model"
)

type UserRepo interface {
	Save(ctx context.Context, user *model.User) error
}

该代码提供了一个Save接口,用来保存 User。

在 user/internal/ 下创建 dao 目录,并创建 user.go 文件,提供 接口的实现。

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

}

5. 修改 user/internal/config目录下的 config.go文件

由于,我们需要连接 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连接配置。

6. 修改 user/rpc/user.proto 文件,并重新生成代码

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=.

7. 修改userserver.go 和 getuserlogic.go 代码

将生成的 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 逻辑已经完成。

8. 调用 rpc 接口,测试 user 保存到数据库

1. 在mall 目录下执行如下命令,创建 userapi微服务(为了测试user rpc 保存到数据的功能):

goctl api new userapi

2. 在 userapi 目录下,创建一个 go.mod 文件,文件内容如下:

module userapi

go 1.22.2

3. 在 mall 目录下执行如下命令,将 userapi 加入workspace中

go work use userapi/
cd userapi/
go mod tidy

到此,生成的代码结构如下:
在这里插入图片描述
在这里插入图片描述
生成的包名稍微有点问题,建议直接手动修改一下。将user/api 改为 userapi

4. 修改 userapi/etc/user-api.yaml 文件如下:

Name: userapi-api
Host: 0.0.0.0
Port: 8888
UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: user.rpc

该文件中增加了 UserRpc 配置,主要是为了调用rpc接口。

5. 修改 userapi/internal/config/config.go 文件

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 中的配置。

6. 修改userapi/internal/handler/routers.go文件

在 userapi/internal/handler 目录下创建 register.go 文件 和 userhandler.go文件

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,
			},
		},
	)
}

7. 修改 userapi/internal/types.go 文件

// 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数据。

8. 为了使用 Rpc 服务,修改 userapi/internal/svc/servicecontext.go 文件

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方法。

9.修改业务代码 userapi/internal/logic/userapilogic.go

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
}

9 测试

  1. 启动 user 服务
  2. 启动 userapi 服务
  3. 用 postman测试,并查看数据库
    在这里插入图片描述
    测试成功。

10. 重构代码。

由于在 userapi中,用到user中的代码。并且之前的 order中也直接引用了user中的代码。这样增加了耦合性。我们可以把这部分公共的代码拿出来,这样以后。即使user服务发生变动,只要公共部分不变。那么userapi和order服务就不会受到影响。

1. 创建公共目录

mkdir rpc-common
cd rpc-common

2. 创建 go.mod 文件

在 mall/rpc-common下创建 go.mod文件

module rpc-common

go 1.22.2

3. 将 rpc-common 加入 workspacke

在 mall 目录下,执行命令

go work use rpc-common

4. 重新调整一下 order、user、userapi中包的引用

5. 测试

在这里插入图片描述
截图显示,测试成功。代码调整成功。

11. 整理代码之后的 git 地址

github 当前整理后代码,放在了 mysql 分支下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1708999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

module ‘plotting‘ has no attribute ‘EpisodeStats‘

plotting.py 的版本不同,可以使用下列版本 reinforcement-learning/lib/plotting.py at master dennybritz/reinforcement-learning GitHubImplementation of Reinforcement Learning Algorithms. Python, OpenAI Gym, Tensorflow. Exercises and Solutions to a…

【Real】[Flask]SSTI

文章目录 前言一、题目解读二、解题过程三、知识点Flask是什么SSTI是什么SSTI是如何形成的易于利用的类payload是什么 探索类型和类层次结构和方法 前言 温馨提示:看到哪里不懂直接跳到知识点部分,理解完再回到解题过程。 一、题目解读 题目是[Flask]S…

winform安装时覆盖原版本并保留配置文件

如何打包参考大佬的博客添加链接描述 覆盖原版本 修改 Properties 下的 AssemblyInfo.cs 中的版本号,如下。原来是1.0.0.0,我修改成1.0.2。 选中 Setup 项目,修改 Version 属性修改 Version 属性后 ProductCode 也会改变,卸载程…

关于k8s集群的污点和容忍,以及k8s集群的故障排查思路

一 污点(Taint) 和 容忍(Tolerations) (一)污点 在Kubernetes(K8s)中,污点(Taints)是一个重要的概念,用于实现Pod的调度控制。以下是关于污点的详细解释:1.污点定义 污点…

SSL协议:网络安全通信的守护者

在网络通信迅猛发展的今天,数据安全和隐私保护变得尤为重要。安全套接层协议(Secure Sockets Layer, SSL)作为早期网络加密及身份验证的基石,为在线数据传输提供了安全保障。下面我们就来了解一下SSL协议。 SSL协议概述 SSL协议最…

package.json中peerDependencies的使用场景

文章目录 peerDependencies 的使用场景peerDependencies 的使用案例为什么使用 peerDependencies需要注意的事项主要作用 ✍创作者:全栈弄潮儿 🏡 个人主页: 全栈弄潮儿的个人主页 🏙️ 个人社区,欢迎你的加入&#xf…

(2020|ICML PMLR,线性 Transformer,核函数,RNN)Transformer 是 RNN

Transformers are RNNs: Fast Autoregressive Transformers with Linear Attention 公众号:EDPJ(进 Q 交流群:922230617 或加 VX:CV_EDPJ 进 V 交流群) 目录 0. 摘要 3. 线性 Transformers 3.1. Transformer 3.2.…

力扣62 不同路径 Java版本

文章目录 题目描述代码 题目描述 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少…

NLP技术发展和相关书籍分享

自然语言处理(Natural Language Processing,NLP)是计算机科学领域和人工智能领域的重要研究方向之一,旨在探索实现人与计算机之间用自然语言进行有效交流的理论与方法。它融合了语言学、计算机科学、机器学习、数学、认知心理学等…

场景文本检测识别学习 day10(MMdetection)

配置文件(config) 由于在大型项目中,一种模型需要分:tiny、small、big等很多种,而它们的区别主要在网络结构,数据的加载,训练策略等,且差别很多都很小,所以如果每个模型都手动从头写一份&#…

ssm150旅游网站的设计与实现+jsp

旅游网站设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本旅游网站就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞…

鸿蒙OS开发:【一次开发,多端部署】(音乐专辑主页)

一多音乐专辑主页 介绍 本示例展示了音乐专辑主页。 头部返回栏: 因元素单一、位置固定在顶部,因此适合采用自适应拉伸,充分利用顶部区域。专辑封面: 使用栅格组件控制占比,在小尺寸屏幕下封面图与歌单描述在同一行。歌曲列表: 使用栅格组…

汽车电子零部件(14):TMS热管理系统

前言: TMS(thermal management system)热管理系统,这是新能源汽车诞生后随之而产生的一种新汽车零部件,一旦热管理失控会触发自燃,这种现象也是对EV来说是件头疼的事。汽车的热管理系统(TMS)是一个关键部件,有助于调节汽车电池组、车厢和其他车辆系统的温度。TMS的主要…

假象和谎言

原创 | 刘教链 隔夜BTC(比特币)徘徊在69k一线。5.25教链内参报告,《BTC ETF持仓即将超越中本聪》。ETH ETF的尘嚣逐渐散去,复归于平静。戏刚唱了个开头,结尾还留着悬念。4000刀之于ETH看来是个关键阻力位,最…

JavaEE-Spring Controller(服务器控制以及Controller的实现和配置)

Spring Controller 服务器控制 响应架构 Spring Boot 内集成了 Tomcat 服务器,也可以外接 Tomcat 服务器。通过控制层接收浏览器的 URL 请求进行操作并返回数据。 底层和浏览器的信息交互仍旧由 servlet 完成,服务器整体架构如下: Server&…

[9] CUDA性能测量与错误处理

CUDA性能测量与错误处理 讨论如何通过CUDA事件来测量它的性能如何通过CUDA代码进行调试 1.测量CUDA程序的性能 1.1 CUDA事件 CPU端的计时器可能无法给出正确的内核执行时间CUDA事件等于是在你的CUDA应用运行的特定时刻被记录的时间戳,通过使用CUDA事件API&#…

第十四届蓝桥杯c++研究生组

A 关键思路是求每个十进制数的数字以及怎么在一个数组中让判断所有的数字次数相等。 求每个十进制的数字 while(n!0){int x n%10;//x获取了n的每一个位数字n/10;}扩展:求二进制的每位数字 (注意:进制转换、1的个数、位运算) x…

rk3568_semaphore

文章目录 前言1 什么是信号量1.1 信号量API函数2、信号量实验2.1 实验目的2.2函数源码2.3 运行结果图前言 本文记录rk3568开发板的信号量实验 1 什么是信号量 信号量是同步的一种方式,常常用于控制对共享资源的访问。 举个例子:停车场的停车位有100个,这100个停车位就是共…

js的学习

什么是JavaScript? JavaScript(简称:JS)是一门跨平台、面向对象的脚本语言。是用来控制网页行为的,”它能使网页可交互。 JavaScript 和Java 是完全不同的语言,不论是概念还是设计。但是基础语法类似。 JavaScript在1995 年由 Brendan Eich 发明&#x…

【OpenCV】图像通道合并与分离,ROI

介绍可以实现图像通道合并与分离的API,这只是一种方式,后续还会介绍其他的合并与分离方法,以及ROI区域截取的方法。相关API: split() merge() Mat对象() 代码: #include "iostream" #include "ope…