go-zero微服务入门教程

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

本文源码:https://gitee.com/songfayuan/go-zero-demo (教程源码分支:1.zero入门)

准备工作

安装基础环境

安装etcd, mysql,建议采用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。

Name: admin-api

Host: 0.0.0.0

Port: 8888

Timeout: 60000

Mysql:

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

SysRpc:

Timeout: 30000

Etcd:

Hosts:

- 192.168.2.204:2379

Key: sysa.rpc

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

config.go

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

package config

import (

"github.com/zeromicro/go-zero/rest"

"github.com/zeromicro/go-zero/zrpc"

)

type Config struct {

rest.RestConf

SysRpc zrpc.RpcClientConf

Mysql struct {

Datasource string

}

}

servicecontext.go

修改api/internal/svc/servicecontext.go,如下:

package svc

import (

"context"

"github.com/zeromicro/go-zero/zrpc"

"go-zero-demo/api/internal/config"

"go-zero-demo/rpc/sys/sys"

"google.golang.org/grpc"

"google.golang.org/grpc/metadata"

)

type ServiceContext struct {

Config config.Config

Sys sys.Sys

}

func NewServiceContext(c config.Config) *ServiceContext {

return &ServiceContext{

Config: c,

Sys: sys.NewSys(zrpc.MustNewClient(c.SysRpc, zrpc.WithUnaryClientInterceptor(interceptor))),

}

}

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: "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:123456789@tcp(192.168.2.204:3306)/dsms_admin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

config.go

修改rpc/sys/internal/config/config.go,如下:

package config

import (

"github.com/zeromicro/go-zero/zrpc"

)

type Config struct {

zrpc.RpcServerConf

Mysql struct {

Datasource string

}

}

servicecontext.go

修改rpc/sys/internal/svc/servicecontext.go,如下:

package svc

import (

"github.com/zeromicro/go-zero/core/stores/sqlx"

"go-zero-demo/rpc/model/sysmodel"

"go-zero-demo/rpc/sys/internal/config"

)

type ServiceContext struct {

Config config.Config

UserModel sysmodel.SysUserModel

}

func NewServiceContext(c config.Config) *ServiceContext {

sqlConn := sqlx.NewMysql(c.Mysql.Datasource)

return &ServiceContext{

Config: c,

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

源码

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

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: