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服务模块。
源码
上面的源码在这里…
源码包