go-zero微服务入门教程

go-zero微服务入门教程

本教程主要模拟实现用户注册用户信息查询两个接口。

准备工作

安装基础环境

  • 安装etcd, mysql,redis,建议采用docker安装。

MySQL安装好之后,新建数据库dsms_admin,并新建表sys_user,建表语句见后文。

安装插件

这里采用GoLand开发工具,请自行搜索安装插件Goctl。

在这里插入图片描述

创建工程

这里采用开发工具GoLand,File > New > Project

在这里插入图片描述

创建api目录、rpc目录、common目录。
在这里插入图片描述
在这里插入图片描述

编写API Gateway代码

编写api文件

在api目录下创建新目录doc/sys。

在这里插入图片描述

在api/doc/sys下创建user.api。

在这里插入图片描述

user.api文件内容如下:

syntax = "v1"

info(
	title: "用户相关"
	desc: "用户相关"
	author: "宋发元"
)

type (
	UserInfoResp {
		Code    int64        `json:"code"`
		Message string       `json:"message"`
		Data    UserInfoData `json:"data"`
	}

	UserInfoData {
		Avatar      string             `json:"avatar"`
		Name        string             `json:"name"`
		MenuTree    []*ListMenuTree    `json:"menuTree"`
		MenuTreeVue []*ListMenuTreeVue `json:"menuTreeVue"`
		ResetPwd    bool               `json:"resetPwd,default=false"`
	}

	ListMenuTree {
		Id       int64  `json:"id"`
		Path     string `json:"path"`
		Name     string `json:"name"`
		ParentId int64  `json:"parentId"`
		Icon     string `json:"icon"`
	}

	ListMenuTreeVue {
		Id           int64        `json:"id"`
		ParentId     int64        `json:"parentId"`
		Title        string       `json:"title"`
		Path         string       `json:"path"`
		Name         string       `json:"name"`
		Icon         string       `json:"icon"`
		VueRedirent  string       `json:"vueRedirent"`
		VueComponent string       `json:"vueComponent"`
		Meta         MenuTreeMeta `json:"meta"`
	}

	MenuTreeMeta {
		Title string `json:"title"`
		Icon  string `json:"icon"`
	}

	AddUserReq {
		Name     string `json:"name"`
		NickName string `json:"nickName"`
		Password string `json:"password,optional"`
		Email    string `json:"email"`
		RoleId   int64  `json:"roleId"`
		Status   int64  `json:"status,default=1"`
	}

	AddUserResp {
		Code    int64           `json:"code"`
		Message string          `json:"message"`
		Data    ReceiptUserData `json:"data"`
	}

	ReceiptUserData {
		Id int64 `json:"id"`
	}
)

@server (
	group : sys/user
	prefix : /sys/user
)

service admin-api{
	@doc(
		summary : "用户管理-获取当前用户信息"
	)
	@handler UserInfo
	get /currentUser returns (UserInfoResp)

	@doc(
		summary : "用户管理-新增用户"
	)
	@handler UserAdd
	post /add(AddUserReq)returns(AddUserResp)
}

用goctl生成API Gateway代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生成的文件结构如下:

api
├── admin.go    //main入口定义
├── doc
│   └── sys
│       └── user.api    //api定义文件
├── etc
│   └── admin-api.yaml    //配置文件
└── internal
    ├── config
    │   └── config.go    //定义配置
    ├── handler
    │   ├── routes.go   //定义路由处理
    │   └── sys
    │       └── user
    │           ├── useraddhandler.go    //实现addhandler
    │           └── userinfohandler.go    //实现infohandler
    ├── logic
    │   └── sys
    │       └── user
    │           ├── useraddlogic.go    //实现addlogic
    │           └── userinfologic.go    //实现infologic
    ├── svc
    │   └── servicecontext.go    //定义ServiceContext
    └── types
        └── types.go    //定义请求、返回结构体

编写rpc服务

编写sys.proto文件

在rpc下创建新目录sys。
在这里插入图片描述

在rpc/sys目录下创建sys.proto文件。

在这里插入图片描述

sys.proto文件内容如下:

syntax = "proto3";

package sysclient;

option go_package = "./sysclient";

message InfoReq{
  int64 UserId = 1;
}
message InfoResp{
  string avatar =1;
  string name = 2;
  repeated MenuListTree menuListTree = 3;
  repeated string backgroundUrls=4;
  bool resetPwd=5;
}

message MenuListTree{
  int64 id=1;
  string name=2;
  string icon=3;
  int64 parentId=4;
  string path=5;
  string vuePath=6;
  string vueComponent=7;
  string vueIcon=8;
  string vueRedirect=9;
  string backgroundUrl=10;
}

message UserAddReq{
  string name=1;
  string nickName=2;
  string password=3;
  string email=4;
  int64 roleId=5;
  int64 status=6;
  string createBy=7;
}

message  UserAddResp{
  int64 id=1;
}

service Sys{
  rpc UserInfo(InfoReq)returns(InfoResp);
  rpc UserAdd(UserAddReq)returns(UserAddResp);
}

用goctl生成rpc代码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

生成的文件结构如下:

sys
├── etc
│   └── sys.yaml                     //yaml配置文件
├── internal
│   ├── config
│   │   └── config.go                //yaml配置文件对应的结构体定义
│   ├── logic                        //业务逻辑
│   │   ├── useraddlogic.go
│   │   └── userinfologic.go
│   ├── server                       //rpc server
│   │   └── sysserver.go
│   └── svc                          //资源依赖
│       └── servicecontext.go
├── sys                              //rpc client call entry
│   └── sys.go
├── sys.go                           //main函数入口
├── sys.proto                        //proto源文件
└── sysclient                        //pb.go
    ├── sys.pb.go
    └── sys_grpc.pb.go

修改API Gateway代码调用rpc服务

admin-api.yaml

  • 修改配置文件admin-api.yaml,增加如下内容,这里的192.168.2.204为基础环境服务器IP。
Timeout: 60000

Mysql:
  Datasource: root:123456@tcp(192.168.2.204:3306)/dsms_admin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
  - Host: 192.168.2.204:6379
    Pass: qkgxChxNkCwK
    Type: node

Redis:
  Address: 192.168.2.204:6379
  Pass: qkgxChxNkCwK

SysRpc:
  Timeout: 30000
  Etcd:
    Hosts:
      - 192.168.2.204:2379
    Key: sysa.rpc

通过etcd自动去发现可用的rpc服务。

config.go

  • 修改api/internal/config/config.go如下,增加rpc服务依赖。
	SysRpc zrpc.RpcClientConf

	CacheRedis cache.ClusterConf

	Redis struct {
		Address string
		Pass    string
	}

	Mysql struct {
		Datasource string
	}

servicecontext.go

  • 修改api/internal/svc/servicecontext.go,如下:
type ServiceContext struct {
	Config config.Config

	Sys   sys.Sys
	Redis *redis.Redis
}

func NewServiceContext(c config.Config) *ServiceContext {
	newRedis := redis.New(c.Redis.Address, redisConfig(c))
	return &ServiceContext{
		Config: c,
		Sys:    sys.NewSys(zrpc.MustNewClient(c.SysRpc, zrpc.WithUnaryClientInterceptor(interceptor))),
		Redis:  newRedis,
	}
}

func redisConfig(c config.Config) redis.Option {
	return func(r *redis.Redis) {
		r.Type = redis.NodeType
		r.Pass = c.Redis.Pass
	}
}

func interceptor(ctx context.Context, method string, req any, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	md := metadata.New(map[string]string{"x": "xx"})
	ctx = metadata.NewOutgoingContext(ctx, md)
	// logx.Debug("调用rpc服务前")
	err := invoker(ctx, method, req, reply, cc)
	if err != nil {
		return err
	}
	// logx.Debug("调用rpc服务后")
	return nil
}

通过ServiceContext在不同业务逻辑之间传递依赖。

useraddlogic.go

  • 修改api/internal/logic/useraddlogic.go里的UserAdd方法,如下:
func (l *UserAddLogic) UserAdd(req *types.AddUserReq) (resp *types.AddUserResp, err error) {
	res, err := l.svcCtx.Sys.UserAdd(l.ctx, &sysclient.UserAddReq{
		Name:     req.Name,
		NickName: req.NickName,
		Password: req.Password,
		Email:    req.Email,
		RoleId:   req.RoleId,
		Status:   req.Status,
		//CreateBy: l.ctx.Value(cache.JwtFieldUserName).(string),
		CreateBy: "songfayuan",
	})

	if err != nil {
		reqJson, _ := json.Marshal(req)
		logx.WithContext(l.ctx).Errorf("添加用户信息失败,请求参数:%s,异常信息:%s", reqJson, err.Error())
		return nil, rpcerror.New(err)
	}

	return &types.AddUserResp{
		Code:    200,
		Message: "添加用户成功",
		Data:    types.ReceiptUserData{Id: res.Id},
	}, nil
}

userinfologic.go

  • 修改api/internal/logic/userinfologic.go里的UserInfo方法,如下:

func (l *UserInfoLogic) UserInfo() (*types.UserInfoResp, error) {
	//这里的key和生成jwt token时传入的key一致
	//userId, _ := l.ctx.Value(cache.JwtFieldUserId).(json.Number).Int64()

	var userId int64 = 1
	resp, err := l.svcCtx.Sys.UserInfo(l.ctx, &sysclient.InfoReq{
		UserId: userId,
	})

	if err != nil {
		logx.WithContext(l.ctx).Errorf("根据userId:%s, 查询用户异常:%s", strconv.FormatInt(userId, 10), err.Error())
		return nil, rpcerror.New(err)
	}

	var MenuTree []*types.ListMenuTree

	//组装ant ui中的菜单
	for _, item := range resp.MenuListTree {
		MenuTree = append(MenuTree, &types.ListMenuTree{
			Id:       item.Id,
			Path:     item.Path,
			Name:     item.Name,
			ParentId: item.ParentId,
			Icon:     item.Icon,
		})
	}

	if MenuTree == nil {
		MenuTree = make([]*types.ListMenuTree, 0)
	}

	//组装element ui中的菜单
	var MenuTreeVue []*types.ListMenuTreeVue

	for _, item := range resp.MenuListTree {
		if len(strings.TrimSpace(item.VuePath)) != 0 {
			MenuTreeVue = append(MenuTreeVue, &types.ListMenuTreeVue{
				Id:           item.Id,
				ParentId:     item.ParentId,
				Title:        item.Name,
				Path:         item.VuePath,
				Name:         item.Name,
				Icon:         item.VueIcon,
				VueRedirent:  item.VueRedirect,
				VueComponent: item.VueComponent,
				Meta: types.MenuTreeMeta{
					Title: item.Name,
					Icon:  item.VueIcon,
				},
			})
		}
	}
	if MenuTreeVue == nil {
		MenuTreeVue = make([]*types.ListMenuTreeVue, 0)
	}

	err = l.svcCtx.Redis.Set(strconv.FormatInt(userId, 10), strings.Join(resp.BackgroundUrls, ","))
	if err != nil {
		logx.Errorf("设置用户:%s, 权限到Redis异常:%+v", resp.Name, err)
	}

	return &types.UserInfoResp{
		Code:    200,
		Message: "成功",
		Data: types.UserInfoData{
			Avatar:      resp.Avatar,
			Name:        resp.Name,
			MenuTree:    MenuTree,
			MenuTreeVue: MenuTreeVue,
			ResetPwd:    resp.ResetPwd,
		},
	}, nil
}

定义数据库表结构,并生成CRUD代码

  • 在rpc目录下创建model/sysmodel目录,在rpc目录下创建doc/sql/sys目录。

在这里插入图片描述

创建sys_user.sql

  • 在rpc/doc/sql/sys目录下编写sql文件sys_user.sql,如下:
-- goctl model mysql ddl -src doc/sql/sys/sys_user.sql -dir ./model/sysmodel

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`
(
    `id`          bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
    `name`        varchar(128) NOT NULL DEFAULT '' COMMENT '账号',
    `nick_name`   varchar(128) NOT NULL DEFAULT '' COMMENT '名称',
    `avatar`      varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
    `password`    varchar(128) NOT NULL DEFAULT '' COMMENT '密码',
    `salt`        varchar(40)  NOT NULL DEFAULT '' COMMENT '加密盐',
    `email`       varchar(128) NOT NULL DEFAULT '' COMMENT '邮箱',
    `mobile`      varchar(32)  NOT NULL DEFAULT '' COMMENT '手机号',
    `status`      tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态  -1:禁用   1:正常',
    `create_by`   varchar(128) NOT NULL DEFAULT '' COMMENT '创建人',
    `create_time` timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_by`   varchar(128) NOT NULL DEFAULT '' COMMENT '更新人',
    `update_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `del_flag`    tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除  1:已删除  0:正常',
    PRIMARY KEY (`id`),
    KEY           `name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户管理';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', 'admin', '', '$2a$10$hDlSis2/3IPGNYQhFlFfK.Wmi7iH9/jr6wcN.5c.rh7fc/uUnCo4S', '', 'admin@dsms.com', '13612345678', 1, 'admin', '2018-08-14 11:11:11', '', '2023-01-04 10:17:30', 0);

生成CRUD代码

方法一

通过工具生成,这种方式生成带缓存的代码。(本文采用方法二生成)

在这里插入图片描述

选择代码位置。
在这里插入图片描述

生成的代码。

在这里插入图片描述

方法二(采纳)

在rpc路径下执行如下命令,生成不带缓存的代码。

goctl model mysql ddl -src doc/sql/sys/sys_user.sql -dir ./model/sysmodel

完善CRUD代码

sysusermodel.go

在model/sysmodel/sysusermodel.go文件中添加常用crud的代码,完整代码如下。

package sysmodel

import (
	"context"
	"errors"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"time"
)
import sq "github.com/Masterminds/squirrel"

var _ SysUserModel = (*customSysUserModel)(nil)

type (
	// SysUserModel is an interface to be customized, add more methods here,
	// and implement the added methods in customSysUserModel.
	SysUserModel interface {
		sysUserModel
		withSession(session sqlx.Session) SysUserModel

		Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error

		UpdateBuilder() sq.UpdateBuilder
		UpdateByQuery(ctx context.Context, updateBuilder sq.UpdateBuilder) error

		RowBuilder() sq.SelectBuilder
		FindOneByQuery(ctx context.Context, rowBuilder sq.SelectBuilder) (*SysUser, error)
		FindRowsByQuery(ctx context.Context, rowBuilder sq.SelectBuilder, orderBy string) ([]*SysUser, error)

		CountBuilder(field string) sq.SelectBuilder
		FindCount(ctx context.Context, countBuilder sq.SelectBuilder) (int64, error)

		FindAll(ctx context.Context, rowBuilder sq.SelectBuilder, orderBy string) ([]*SysUserList, error)

		TableName() string
	}

	customSysUserModel struct {
		*defaultSysUserModel
	}

	SysUserList struct {
		Id         int64     `db:"id"`          // 编号
		Name       string    `db:"name"`        // 账号
		NickName   string    `db:"nick_name"`   // 名称
		Avatar     string    `db:"avatar"`      // 头像
		Password   string    `db:"password"`    // 密码
		Salt       string    `db:"salt"`        // 加密盐
		Email      string    `db:"email"`       // 邮箱
		Mobile     string    `db:"mobile"`      // 手机号
		Status     int64     `db:"status"`      // 状态  -1:禁用   1:正常
		CreateBy   string    `db:"create_by"`   // 创建人
		CreateTime time.Time `db:"create_time"` // 创建时间
		UpdateBy   string    `db:"update_by"`   // 更新人
		UpdateTime time.Time `db:"update_time"` // 更新时间
		DelFlag    int64     `db:"del_flag"`    // 是否删除  1:已删除  0:正常
		RoleId     int64     `db:"role_id"`
		RoleName   string    `db:"role_name"`
	}
)

func (m *customSysUserModel) UpdateByQuery(ctx context.Context, updateBuilder sq.UpdateBuilder) error {
	query, values, err := updateBuilder.Where("del_flag = ?", 0).ToSql()
	if err != nil {
		return err
	}
	_, err = m.conn.ExecCtx(ctx, query, values...)
	return err
}

func (m *customSysUserModel) UpdateBuilder() sq.UpdateBuilder {
	return sq.Update(m.table)
}

func (m *customSysUserModel) Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error {
	return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
		return fn(ctx, session)
	})
}

func (m *customSysUserModel) TableName() string {
	return m.table
}

func (m *customSysUserModel) FindAll(ctx context.Context, rowBuilder sq.SelectBuilder, orderBy string) ([]*SysUserList, error) {
	if orderBy == "" {
		rowBuilder = rowBuilder.OrderBy("id AEC")
	} else {
		rowBuilder = rowBuilder.OrderBy(orderBy)
	}

	query, values, err := rowBuilder.Where("del_flag = ?", 0).ToSql()
	if err != nil {
		return nil, err
	}

	var resp []*SysUserList
	err = m.conn.QueryRowsCtx(ctx, &resp, query, values...)
	switch err {
	case nil:
		return resp, nil
	case sqlx.ErrNotFound:
		return nil, errors.New("查询记录为空")
	default:
		return nil, err
	}
}

func (m *customSysUserModel) FindCount(ctx context.Context, countBuilder sq.SelectBuilder) (int64, error) {
	query, values, err := countBuilder.Where("del_flag = ?", 0).ToSql()
	if err != nil {
		return 0, err
	}

	var resp int64
	err = m.conn.QueryRowCtx(ctx, &resp, query, values...)
	switch err {
	case nil:
		return resp, nil
	default:
		return 0, err
	}
}

func (m *customSysUserModel) CountBuilder(field string) sq.SelectBuilder {
	return sq.Select("COUNT(" + field + ")").From(m.table)
}

func (m *customSysUserModel) FindRowsByQuery(ctx context.Context, rowBuilder sq.SelectBuilder, orderBy string) ([]*SysUser, error) {
	if orderBy == "" {
		rowBuilder = rowBuilder.OrderBy("id DESC")
	} else {
		rowBuilder = rowBuilder.OrderBy(orderBy)
	}

	query, values, err := rowBuilder.Where("del_flag = ?", 0).ToSql()
	if err != nil {
		return nil, err
	}

	var resp []*SysUser
	err = m.conn.QueryRowCtx(ctx, &resp, query, values...)
	switch err {
	case nil:
		return resp, nil
	case sqlx.ErrNotFound:
		return nil, errors.New("查询记录为空")
	default:
		return nil, err
	}
}

func (m *customSysUserModel) FindOneByQuery(ctx context.Context, rowBuilder sq.SelectBuilder) (*SysUser, error) {
	query, values, err := rowBuilder.Where("del_flag = ?", 0).Limit(1).ToSql()
	if err != nil {
		return nil, err
	}

	var resp SysUser
	err = m.conn.QueryRowCtx(ctx, &resp, query, values...)
	switch err {
	case nil:
		return &resp, nil
	default:
		return nil, err
	}
}

func (m *customSysUserModel) RowBuilder() sq.SelectBuilder {
	return sq.Select(sysUserRows).From(m.table)
}

// NewSysUserModel returns a model for the database table.
func NewSysUserModel(conn sqlx.SqlConn) SysUserModel {
	return &customSysUserModel{
		defaultSysUserModel: newSysUserModel(conn),
	}
}

func (m *customSysUserModel) withSession(session sqlx.Session) SysUserModel {
	return NewSysUserModel(sqlx.NewSqlConnFromSession(session))
}

修改rpc代码调用crud代码

sys.yaml

  • 修改rpc/sys/etc/sys.yaml,如下内容:
Name: sys.rpc
ListenOn: 0.0.0.0:8080
Timeout: 10000
Etcd:
  Hosts:
  - 192.168.2.204:2379
  Key: sysa.rpc

Mysql:
  Datasource: root:123456@tcp(192.168.2.204:3306)/dsms_admin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
  - Host: 192.168.2.204:6379
    Pass: qkgxChxNkCwK
    Type: node

config.go

  • 修改rpc/sys/internal/config/config.go,如下:
type Config struct {
	zrpc.RpcServerConf

	Mysql struct {
		Datasource string
	}

	CacheRedis cache.ClusterConf
}

servicecontext.go

  • 修改rpc/sys/internal/svc/servicecontext.go,如下:
type ServiceContext struct {
	Config config.Config
	Cache  cache.Cache
	Redis  *redis.Redis

	UserModel sysmodel.SysUserModel
}

func NewServiceContext(c config.Config) *ServiceContext {
	sqlConn := sqlx.NewMysql(c.Mysql.Datasource)

	ca := cache.New(c.CacheRedis, syncx.NewSingleFlight(), cache.NewStat("dc"), errors.New("data not find"))
	rConn := redis.New(c.CacheRedis[0].Host, func(r *redis.Redis) {
		r.Type = c.CacheRedis[0].Type
		r.Pass = c.CacheRedis[0].Pass
	})

	return &ServiceContext{
		Config: c,
		Cache:  ca,
		Redis:  rConn,

		UserModel: sysmodel.NewSysUserModel(sqlConn),
	}
}

useraddlogic.go

  • 修改rpc/sys/internal/logic/useraddlogic.go,如下:
func (l *UserAddLogic) UserAdd(in *sysclient.UserAddReq) (*sysclient.UserAddResp, error) {
	if in.Name == "" {
		return nil, errors.New("账号不能为空")
	}
	if in.NickName == "" {
		return nil, errors.New("姓名不能为空")
	}
	if in.Email == "" {
		return nil, errors.New("邮箱不能为空")
	}

	//校验账号是否已存在
	selectBuilder := l.svcCtx.UserModel.CountBuilder("id").Where(sq.Eq{"name": in.Name})
	count, _ := l.svcCtx.UserModel.FindCount(l.ctx, selectBuilder)
	if count > 0 {
		logx.WithContext(l.ctx).Errorf("账号已存在,添加失败,userName = %s", in.Name)
		return nil, errors.New("账号已存在")
	}

	if in.Password == "" {
		in.Password = "123456"
	}
	hashedPassword, err := utils.GenerateFromPassword(in.Password)
	if err != nil {
		return nil, errors.New("密码加密出错")
	}

	//插入数据
	result, err := l.svcCtx.UserModel.Insert(l.ctx, &sysmodel.SysUser{
		Name:       in.Name,
		NickName:   in.NickName,
		Avatar:     "",
		Password:   hashedPassword,
		Salt:       "",
		Email:      in.Email,
		Mobile:     "",
		Status:     0,
		CreateBy:   in.CreateBy,
		UpdateTime: time.Time{},
		DelFlag:    0,
	})
	if err != nil {
		return nil, err
	}
	insertId, err := result.LastInsertId()
	if err != nil {
		return nil, err
	}

	return &sysclient.UserAddResp{Id: insertId}, nil
}

userinfologic.go

  • 修改rpc/sys/internal/logic/userinfologic.go,如下:
func (l *UserInfoLogic) UserInfo(in *sysclient.InfoReq) (*sysclient.InfoResp, error) {
	rowBuilder := l.svcCtx.UserModel.RowBuilder().Where(sq.Eq{"id": in.UserId})
	userInfo, err := l.svcCtx.UserModel.FindOneByQuery(l.ctx, rowBuilder)

	switch err {
	case nil:
	case sqlx.ErrNotFound:
		logx.WithContext(l.ctx).Infof("用户不存在userId:%s", in.UserId)
		return nil, fmt.Errorf("用户不存在userId:%s", strconv.FormatInt(in.UserId, 10))
	default:
		return nil, err
	}

	//var list []*sys.MenuListTree
	//var listUrls []string

	return &sysclient.InfoResp{
		Avatar:         "11111",
		Name:           userInfo.Name,
		MenuListTree:   nil,
		BackgroundUrls: nil,
		ResetPwd:       false,
	}, nil
}

common目录

common目录下为通用工具,直接拷贝进去即可。

bcrypt.go

  • common/utils/bcrypt.go
package utils

import (
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"log"

	"github.com/tjfoc/gmsm/sm2"
	x509g "github.com/tjfoc/gmsm/x509"
	"golang.org/x/crypto/bcrypt"
)

func GenerateFromPassword(pwd string) (hashedPassword string, err error) {
	password := []byte(pwd)
	// Hashing the password with the default cost of 10
	hashedPasswordBytes, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
	hashedPassword = string(hashedPasswordBytes)
	return
}

func CompareHashAndPassword(hashedPwd, plainPwd string) bool {
	byteHash := []byte(hashedPwd)
	err := bcrypt.CompareHashAndPassword(byteHash, []byte(plainPwd))
	if err != nil {
		return false
	}
	return true
}

// EncryptSm2 加密
func EncryptSm2(privateKey, content string) string {
	// 从十六进制导入公私钥
	priv, err := x509g.ReadPrivateKeyFromHex(privateKey)
	if err != nil {
		log.Fatal(err)
	}

	// 公钥加密部分
	msg := []byte(content)
	pub := &priv.PublicKey
	cipherTxt, err := sm2.Encrypt(pub, msg, rand.Reader, sm2.C1C2C3) // sm2加密
	if err != nil {
		log.Fatal(err)
	}
	// fmt.Printf("加密文字:%s\n加密结果:%x\n", msg, cipherTxt)
	encodeRes := fmt.Sprintf("%x", cipherTxt)
	return encodeRes
}

// DecryptSm2 解密
func DecryptSm2(privateKey, encryptData string) (string, error) {
	// 从十六进制导入公私钥
	priv, err := x509g.ReadPrivateKeyFromHex(privateKey)
	if err != nil {
		return "", err
	}
	// 私钥解密部分
	hexData, err := hex.DecodeString(encryptData)
	if err != nil {
		return "", err
	}
	plainTxt, err := sm2.Decrypt(priv, hexData, sm2.C1C2C3) // sm2解密
	if err != nil {
		return "", err
	}
	// fmt.Printf("解密后的明文:%s\n私钥:%s \n 匹配一致", plainTxt, x509.WritePrivateKeyToHex(priv))
	return string(plainTxt), nil
}

// EncryptAndDecrypt 加密/解密
func EncryptAndDecrypt(privateKey, content string) {
	// 从十六进制导入公私钥
	priv, err := x509g.ReadPrivateKeyFromHex(privateKey)
	if err != nil {
		log.Fatal(err)
	}

	// 公钥加密部分
	msg := []byte(content)
	pub := &priv.PublicKey
	cipherTxt, err := sm2.Encrypt(pub, msg, rand.Reader, sm2.C1C2C3) // sm2加密
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("加密文字:%s\n加密结果:%x\n", msg, cipherTxt)

	// 私钥解密部分
	plainTxt, err := sm2.Decrypt(priv, cipherTxt, sm2.C1C2C3) // sm2解密
	if err != nil {
		log.Fatal(err)
	}
	if !bytes.Equal(msg, plainTxt) {
		log.Fatal("原文不匹配:", msg)
	}
	fmt.Printf("解密后的明文:%s\n私钥:%s \n 匹配一致", plainTxt, x509g.WritePrivateKeyToHex(priv))
}

// EncryptRSA 加密
func EncryptRSA(content, publicKey string) (encryptStr string, err error) {
	// 	var publicKey = `-----BEGIN PUBLIC KEY-----
	// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaIWAL13RU+bJN2hfmTSyOBotf
	// 71pq8jc2ploPBHtN3smTUkYPbX2MIbO9TrRj3u67s/kGQZrz6tyQ68oexpukPN4/
	// ypzp64UA5CQENSA41ZxTpYADbFQsiX9Spv6aDHhHzUlZtWRru9ptcFO3tDKq0ACT
	// OAR1ZEHFwQGhzwaAowIDAQAB
	// -----END PUBLIC KEY-----`
	block, _ := pem.Decode([]byte(publicKey))
	if block == nil {
		return "", fmt.Errorf("failed to parse public key PEM")
	}
	publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return "", err
	}
	//  类型断言
	rsaPublicKey := publicKeyInterface.(*rsa.PublicKey)
	// 加密数据
	encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, []byte(content))
	if err != nil {
		return "", fmt.Errorf("error encrypting data:%v", err)
	}

	return base64.StdEncoding.EncodeToString(encryptedData), err

}

// DecryptRSA 解密
func DecryptRSA(encryptStr, privateKey string) (content string, err error) {
	// 	var privateKey = `-----BEGIN PRIVATE KEY-----
	// MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANohYAvXdFT5sk3a
	// F+ZNLI4Gi1/vWmryNzamWg8Ee03eyZNSRg9tfYwhs71OtGPe7ruz+QZBmvPq3JDr
	// yh7Gm6Q83j/KnOnrhQDkJAQ1IDjVnFOlgANsVCyJf1Km/poMeEfNSVm1ZGu72m1w
	// U7e0MqrQAJM4BHVkQcXBAaHPBoCjAgMBAAECgYA/aJJN/uyvQwKlBPALn4WDJ73e
	// PmrvScfpGAR39xqM8WVxcOoy0+Y6FRX1wupHWefWIqQSQIH1w+EoM5LGzX8yflSo
	// lG3E0mgJzrMAOTs5FVkdN4tV6rKYq/vA9R67AD0a9nq7yOFeTqjGzWj4l7Vptvu4
	// prK5GWV+i0+mpB2kKQJBAP0n1EMAHQSW38zOngfaqC6cvnjEbX4NnhSPRZVzlu3y
	// ZkitiA/Y96yCCybCWD0TkF43Z1p0wIGuXSJ1Igku6bcCQQDclMziUz1RnQDl7RIN
	// 449vbmG2mGLoXp5HTD9QP0NB46w64WwXIX7IZL2GubndTRFUFTTPLZZ80XbhFtp6
	// 19B1AkEAnIgjJGaOisbrjQz5BCw8r821rKDwfu/WninUwcteOLUYb7n1Fq92vZEP
	// aiDjRKizLL6fRnxIiCcTaXn52KnMUwJBAJaKOxYPRx8G7tD8rcCq2H5tL+TFNWNv
	// B8iTAfbLZiR2tFlu9S0IIBW1ox9qa63b5gKjgmoOq9C9x8swpKUH2u0CQAKDHqwh
	// aH6lVtV8cw55Ob8Dsh3PgFUazuM1+e5PjmZku3/2jeQQJrecu/S6LooPdeUf+EtV
	// OB/5HvFhGpEu2/E=
	// -----END PRIVATE KEY-----`
	block, _ := pem.Decode([]byte(privateKey))
	if block == nil {
		return "", fmt.Errorf("failed to parse private key PEM")
	}
	privateKeyData, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return "", err
	}
	privateKeyInterface := privateKeyData.(*rsa.PrivateKey)

	// 解密数据

	byt, err := base64.StdEncoding.DecodeString(encryptStr)
	if err != nil {
		return "", fmt.Errorf("base64 DecodeString err:%v", err)
	}

	decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKeyInterface, byt)
	if err != nil {
		return "", fmt.Errorf("error decrypting data:%v", err)
	}

	return string(decryptedData), nil

}

func Md5(s []byte) string {
	m := md5.New()
	m.Write(s)

	return hex.EncodeToString(m.Sum(nil))
}

code.go

  • common/errors/code.go
package errors

const BaseCode = 50000

const RpcCode = 51000

const MustUpdatePwdCode = 50005

const LoginExpired = 50001

base.go

  • common/errors/base.go
package errors

type CommonError interface {
	Error() string
	ErrorType() string

	Data() *CommonErrorResp
}

type CommonErrorResp struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Type    string `json:"error"`
}

errorx.go

  • common/errors/errorx/errorx.go
package errorx

import "go-zero-test/common/errors"

var _ errors.CommonError = (*ErrorX)(nil)

type ErrorX struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Type    string `json:"error"`
}

func (e *ErrorX) Error() string {
	return e.Message
}

func (e *ErrorX) ErrorType() string {
	return e.Type
}

func (e *ErrorX) Data() *errors.CommonErrorResp {
	return &errors.CommonErrorResp{
		Code:    e.Code,
		Message: e.Message,
		Type:    e.Type,
	}
}

func New(s string) error {
	return &ErrorX{Code: errors.BaseCode, Message: s, Type: "base error"}
}

func NewCodeErr(code int, s string) error {
	return &ErrorX{Code: code, Message: s, Type: "base error"}
}

rpcerror.go

  • common/errors/rpcerror/rpcerror.go
package rpcerror

import "go-zero-test/common/errors"

var _ errors.CommonError = (*RpcError)(nil)

type RpcError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Type    string `json:"error"`
}

func (e *RpcError) Error() string {
	return e.Message
}

func (e *RpcError) ErrorType() string {
	return e.Type
}

func (e *RpcError) Data() *errors.CommonErrorResp {
	return &errors.CommonErrorResp{
		Code:    e.Code,
		Message: e.Message,
		Type:    e.Type,
	}
}

// New rpc返回错误
func New(e error) error {
	msg := e.Error()[len("rpc error: code = Unknown desc = "):]
	return &RpcError{Code: errors.RpcCode, Message: msg, Type: "rpc error"}
}

// NewError 返回自定义错误,rpc返回错误
func NewError(s string, err error) error {
	msgType := err.Error()[len("rpc error: code = Unknown desc = "):]
	return &RpcError{Code: errors.RpcCode, Message: s, Type: msgType}
}

完整调用演示

最后,在根目录go-zero-test执行下命令。

go mod tidy

运行rpc服务

在这里插入图片描述

修改路径。

在这里插入图片描述

之后直接启动即可。

在这里插入图片描述

运行api

在这里插入图片描述

修改路径。

在这里插入图片描述

之后直接启动即可。

在这里插入图片描述

api调用

命令请求:

curl -i "localhost:8888/sys/user/currentUser"

返回结果:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-7cf8f53fe7009655963024f44767cd53-67d21fe606d82a15-00
Date: Thu, 22 Feb 2024 06:27:28 GMT
Content-Length: 120

{"code":200,"message":"成功","data":{"avatar":"11111","name":"admin","menuTree":[],"menuTreeVue":[],"resetPwd":false}}%

或者postman调用也行。

在这里插入图片描述

后续研发

后续新增服务、新增接口流程同编写rpc服务模块。

源码

上面的源码在这里…
源码包

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Springboot--整合定时任务quartz--集群篇

文章目录 前言一、quartz 的集群:1.1 服务集群带来的定时任务问题:1.2 服务集群定时任务解决思路: 二、quartz 集群实现:2.1 引入jar2.2 配置文件:2.3 定义quartz 数据源:2.4 集群测试:2.4.1 定…

介绍 CI / CD

目录 一、介绍 CI / CD 1、为什么要 CI / CD 方法简介 1、持续集成 2、持续交付 3、持续部署 2、GitLab CI / CD简介 3、GitLab CI / CD 的工作原理 4、基本CI / CD工作流程 5、首次设置 GitLab CI / CD 6、GitLab CI / CD功能集 一、介绍 CI / CD 在本文档中&#x…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture07多维输入

lecture07多维输入 课程网址 Pytorch深度学习实践 部分课件内容: import torch import numpy as npxy np.loadtxt(diabetes.csv.gz, delimiter,, dtypenp.float32) x_data torch.from_numpy(xy[:,:-1]) #第一列开始最后一列不要 y_data torch.from_numpy(…

【Python_Zebra斑马打印机编程学习笔记(一)】实现标贴预览的两种方式

实现标贴预览的两种方式 实现标贴预览的两种方式前言一、调用 Labelary Online ZPL Viewer API 方法实现标贴预览功能1、Labelary Online ZPL Viewer API 案例介绍2、生成 PNG 格式3、Parameters 二、通过 zpl 的 label.preview() 方法实现标贴预览功能1、实现步骤2、代码示例 …

gitlab,从A仓库迁移某个工程到B仓库,保留提交记录

从A仓库,拷贝 git clone --bare ssh://git192.168.30.88:22/framework/platform.git 在B仓库新建工程,注意:一定要去掉默认的生成README文件进入platform.git 文件夹下,推送到B仓库 git push --mirror ssh://git192.168.30.100…

怎么用sora赚第一桶金?

🌟解锁文字变视频的强大功能!🌟 ✨欢迎来到 Sora Cand,一个革命性的网站,利用 OpenAI 的 Sora 模型帮你把文字变成酷炫的视频!✨ 想象一下,你的文字从纸上跳出来,变成引人入胜的视觉…

全志T527国产核心板及米尔配套开发板批量上市!

2023年12月,米尔电子联合战略合作伙伴全志科技,率先业内发布了国产第一款T527核心板及开发板。这款高性能、高性价比、八核A55的国产核心板吸引了广大客户关注,为积极响应客户需求,米尔基于全志T527核心板现已批量上市&#xff0c…

RabbitMQ 部署方式选择

部署模式 RabbitMQ支持多种部署模式,可以根据应用的需求和规模选择适合的模式。以下是一些常见的RabbitMQ部署模式: 单节点模式: 最简单的部署方式,所有的RabbitMQ组件(消息存储、交换机、队列等)都运行在…

Java项目:21 基于SSM实现的图书借阅管理系统

作者主页:舒克日记 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 基于SSM实现的图书借阅管理系统设计了两个角色,分别是管理员、用户,在数据表user中以ident字段区分,为1表示管理员…

Math方法,以及三角函数计算

abs(x) 返回参数的绝对值 var xMath.abs(-5) //5floor(x) 向下舍入为最接近的整数。 var xMath.floor(2.1) //2ceil(x) 向上舍入为最接近的整数。 var xMath.ceil(2.1) //3fround(x) 最接近的(32 位单精度)浮点表示。 var xMath.fround(2.60) //2.59…

企业动态|上海航空工业集团殷舜晖部长一行到访同创永益

1月24日上午,中国商飞上海航空工业集团采购中心殷舜晖部长一行4人到访同创永益北京总部。同创永益COO马青山、营销副总经理刘翔、总经办主任田东陪同参观,并介绍了公司的发展历程与近年来的突出成绩。 在随后的会议中,马青山向殷舜晖部长一行…

AppBox快速开发框架(开源)开发流程介绍

目前很多低代码平台都是基于Web用拖拽方式生成界面,确实可以极大的提高开发效率,但也存在一些问题: 大部分平台灵活性不够,特殊需求需要较大的自定义开发; 解析json配置的执行效率不是太高; 大部分平台缺…

统计图雷达图绘制方法

统计图雷达图绘制方法 常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图。 前几类图比较容易绘制,饼图环形图绘制较难。 还有一种雷达图的绘制也较难,今提供雷达图的绘制方法供参考。 本方法采用C语言的最基本功能: &am…

k8s(2)

目录 一.二进制部署k8s 常见的K8S安装部署方式: k8s部署 二进制与高可用的区别 二.部署k8s 初始化操作: 每台node安装docker: 在 master01 节点上操作; 准备cfssl证书生成工具:: 执行脚本文件: 拉入etcd压缩包…

【目标检测新SOTA!v7 v4作者新作!】YOLO v9 思路复现 + 全流程优化

YOLO v9 思路复现 全流程优化 提出背景:深层网络的 信息丢失、梯度流偏差YOLO v9 设计逻辑可编程梯度信息(PGI):使用PGI改善训练过程广义高效层聚合网络(GELAN):使用GELAN改进架构 对比其他解法…

Airtest-Selenium实操小课③:下载可爱猫猫图片

1. 前言 那么这周我们看看如何实现使用Airtest-Selenium实现自动搜索下载可爱的猫猫图片吧~ 2. 需求分析和准备 整体的需求大致可以分为以下步骤: 打开chrome浏览器 打开百度网页 搜索“可爱猫猫图片” 定位图片元素 创建存储图片的文件夹 下载可爱猫猫图片…

SpringBoot中Redis缓存的使用

目录 1 前言 2 实现方法 2.1 查询数据时 2.2 修改数据 1 前言 对于一些不常改变,但又经常查询的数据,我们可以使用Redis缓存,来缓解数据库的压力,其中的逻辑如下: 2 实现方法 2.1 查询数据时 一般在控制类查询方…

普中51单片机(DS18B20温度传感器)

DS18B20温度传感器原理 内部结构 64位(激)光刻只读存储器 光刻ROM中的64位序列号是出厂前被光刻好的,它可以看作是该DS18B20的地址序列号。64位光刻ROM的排列是:开始8位(28H)是产品类型标号,接着的48位是该DS18B20自身…

推荐系统经典模型YouTubeDNN

文章目录 YouTubeDNN概念YouTubeDNN模型架构图YouTubeDNN召回阶段YouTubeDNN层级介绍 YouTubeDNN排序阶段YoutubeDNN模型中的一些Trick负采样问题特征构造上下文选择 总结 YouTubeDNN概念 YouTubeDNN是YouTube用于做视频推荐的落地模型,其大体思路就是召回阶段使用…

Kubernetes 二进制部署 《easzlab / kubeasz项目部署》(一)

Kubernetes 二进制部署 - easzlab / kubeasz项目部署 1. 准备工作1.1 设置防火墙1.2 设置SeLinux1.3 设置时区及时间同步1.4 配置域名解析1.5 确认SSH开启1.6 IP转发1.7 安装docker1.8 关闭swap 2. 服务器规划2.1 基本架构图2.2 官方建议2.3 实践服务器规划 3. 服务器配置3.1 配…