go-zero微服务入门教程

news2024/9/18 16:40:11

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服务模块。

源码

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

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

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

相关文章

openEuler安装MySQL客户端、openEuler安装MySQL-client、openEuler部署MySQL-client

MySQL客户端下载链接:https://downloads.mysql.com/archives/community/ mysql-community-client-5.7.30-1.el7.x86_64.rpm mysql-community-common-5.7.30-1.el7.x86_64.rpm mysql-community-libs-5.7.30-1.el7.x86_64.rpm 3个必选 8.0.22以上的版本是4个&…

个人博客系列-项目部署-nginx(3)

使用Nginx uwsgi进行部署django项目 一. 检查项目是否可以运行 启动项目 python manage.py runserver 0.0.0.0:8099输入ip:8099 查看启动页面 出现上述页面表示运行成功 二. 安装uwsgi并配置 2.1 下载uwsgi pip install uwsgi新建文件test.py写入内容,测试一…

Python算法题集_图论(课程表)

Python算法题集_课程表 题207:课程表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【循环递归全算】2) 改进版一【循环递归缓存】3) 改进版二【循环递归缓存反向计算】4) 改进版三【迭代剥离计数器检测】 4. 最优算法5. 相关资源 本…

GaussDB SQL调优:建立合适的索引

背景 GaussDB是华为公司倾力打造的自研企业级分布式关系型数据库,该产品具备企业级复杂事务混合负载能力,同时支持优异的分布式事务,同城跨AZ部署,数据0丢失,支持1000扩展能力,PB级海量存储等企业级数据库…

Rust之构建命令行程序(四):用TDD(测试-驱动-开发)模式来开发库的功能

开发环境 Windows 11Rust 1.75.0 VS Code 1.86.2 项目工程 这次创建了新的工程minigrep. 用测试-驱动模式来开发库的功能 既然我们已经将逻辑提取到src/lib.rs中,并将参数收集和错误处理留在src/main.rs中,那么为代码的核心功能编写测试就容易多了。我…

CSS轻松学:简单易懂的CSS基础指南

css基础 更多web开发知识欢迎访问我的专栏>>> 01-CSS初体验 层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种 样式表 语言,用来描述 HTML 文档的呈现(美化内容)。 书写位置:…

揭秘抖音自动评论软件的使用方法和步骤

**一、引言** 随着移动互联网的普及,抖音已经成为了人们日常生活中不可或缺的一部分。为了更好地利用抖音,我们今天就来探讨一下抖音自动评论软件的使用方法和步骤。本文将通过通俗易懂的语言,结合实际操作,帮助大家轻松掌握这一…

springboot网站开发0201-使用MybatisPlus查询数据库信息返回前端渲染

springboot网站开发0201-使用MybatisPlus查询数据库信息返回前端渲染!这一次我们将会详细的展示一个完整的数据库查询案例,从查询数据库到返回前端渲染页面完成一个流程。 首先,我们需要清楚,本次业务需求是,查询新闻分…

Android 仿信号格子强度动画效果实现

效果图 在 Android 中,如果你想要绘制一个圆角矩形并使其居中显示,你可以使用 Canvas 类 drawRoundRect 方法。要使圆角矩形居中,你需要计算矩形的位置,这通常涉及到确定矩形左上角的位置(x, y)&#xff0…

【kubernetes】二进制部署k8s集群之cni网络插件flannel和calico工作原理(中)

↑↑↑↑接上一篇继续部署↑↑↑↑ 目录 一、k8s集群的三种接口 二、k8s的三种网络模式 1、pod内容器之间的通信 2、同一个node节点中pod之间通信 3、不同的node节点的pod之间通信 Overlay Network VXLAN 三、flannel网络插件 1、flannel插件模式之UDP模式&#xff0…

计算机视觉学习指南(划分为20个大类)

计算机视觉的知识领域广泛而庞杂,涵盖了众多重要的方向和技术。为了更好地组织这些知识,我们需要遵循无交叉无重复(Mutually Exclusive Collectively Exhaustive,MECE)的原则,并采用循序渐进的方式进行分类…

数据库增删改查

DDL: 数据定义语言,用来定义数据库对象(数据库、表、字段)DML: 数据操作语言,用来对数据库表中的数据进行增删改DQL: 数据查询语言,用来查询数据库中表的记录DCL: 数据控制语言,用来创建数据库用户、控制数…

智能运维服务指的是哪些?智能运维阶段有哪些

智能运维服务通常包含哪些关键组成部分?它们在IT管理中的作用和重要性?智能运维的发展可以分为哪些主要阶段?每个阶段的核心技术或实践有哪些,它们是如何推动运维工作向更高水平的自动化和智能化发展的? 智能运维服务…

8.CSS层叠继承规则总结

CSS 层叠继承规则总结 经典真题 请简述一下 CSS 中的层叠规则 CSS 中的层叠继承规则 在前面《CSS属性的计算过程》中,我们介绍了每一个元素都有都有所有的属性,每一个属性都会通过一系列的计算过程得到最终的值。 这里来回顾一下计算过程&#xff0…

K8S—Pod详解

目录 一 Pod基础概念 1.1 Pod是什么 1.2 为什么要使用Pod?Pod在K8S集群中的使用方式? 1.3 基础容器pause 二 Pod的分类 2.1 自主式Pod和控制器管理的Pod 2.2 容器的分类 2.2.1 基础容器(infrastructure container) 2.2.2…

【Linux】Vagrant搭建Linux环境

Vagrant Vagrant是一个基于Ruby的工具,用于创建和部署虚拟化开发环境。它使用Oracle的开源VirtualBox虚拟化系统,使用 Chef创建自动化虚拟环境。 安装Vagrant 从Vagrant官网下载安装包,执行安装。 安装VirtualBox 从官网下载VirtualBo…

企业品牌软文发布在媒体上,有啥用呢

传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 通常企业都会建立自己的媒体矩阵,在公众号,视频号,抖音,网易号,搜狐号等企业品牌矩阵中发布软文,公司动态&#xf…

智慧城市|SHARE 孪影F2 PRO 在数字化城市平台中的应用。

在数字化时代背景下,乌审旗政府积极响应实景三维中国建设工作,以数字乌审作为全旗智慧城市总框架、总平台,致力提升城市治理现代化水平,结合互联网、云计算、人工智能等信息技术建设新型智慧城市,推进城市发展新理念。…

现货黄金怎么交易

现货黄金是投资者广泛关注的一种黄金交易方式。与期货黄金相比,现货黄金交易更加简单、灵活,同时也更容易掌握。本文将介绍现货黄金交易的基本知识,以及投资者应该如何进行现货黄金交易。 一、现货黄金交易基础知识 什么是现货黄金&#xf…

ES6内置对象 - Set

Set(es6提供的一种数据结构,类似数组,是一个集合,可以存储任何类型的元素且唯一、不重复,so,多用于元素去重) 如上图,Set数据结构自带一些方法 1.Set对象创建 let a new Set([1,2,3,3,1,2,4,…