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/userprefix : /sys/user
)service admin-api{@doc(summary : "用户管理-获取当前用户信息")@handler UserInfoget /currentUser returns (UserInfoResp)@doc(summary : "用户管理-新增用户")@handler UserAddpost /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: 60000Mysql:Datasource: root:123456@tcp(192.168.2.204:3306)/dsms_admin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis:- Host: 192.168.2.204:6379Pass: qkgxChxNkCwKType: nodeRedis:Address: 192.168.2.204:6379Pass: qkgxChxNkCwKSysRpc:Timeout: 30000Etcd:Hosts:- 192.168.2.204:2379Key: sysa.rpc
通过etcd自动去发现可用的rpc服务。
config.go
- 修改
api/internal/config/config.go
如下,增加rpc服务依赖。
SysRpc zrpc.RpcClientConfCacheRedis cache.ClusterConfRedis struct {Address stringPass string}Mysql struct {Datasource string}
servicecontext.go
- 修改
api/internal/svc/servicecontext.go
,如下:
type ServiceContext struct {Config config.ConfigSys sys.SysRedis *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.NodeTyper.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 = 1resp, 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.ListMenuTreeVuefor _, 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 sysmodelimport ("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 {sysUserModelwithSession(session sqlx.Session) SysUserModelTrans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) errorUpdateBuilder() sq.UpdateBuilderUpdateByQuery(ctx context.Context, updateBuilder sq.UpdateBuilder) errorRowBuilder() sq.SelectBuilderFindOneByQuery(ctx context.Context, rowBuilder sq.SelectBuilder) (*SysUser, error)FindRowsByQuery(ctx context.Context, rowBuilder sq.SelectBuilder, orderBy string) ([]*SysUser, error)CountBuilder(field string) sq.SelectBuilderFindCount(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 []*SysUserListerr = m.conn.QueryRowsCtx(ctx, &resp, query, values...)switch err {case nil:return resp, nilcase 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 int64err = m.conn.QueryRowCtx(ctx, &resp, query, values...)switch err {case nil:return resp, nildefault: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 []*SysUsererr = m.conn.QueryRowCtx(ctx, &resp, query, values...)switch err {case nil:return resp, nilcase 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 SysUsererr = m.conn.QueryRowCtx(ctx, &resp, query, values...)switch err {case nil:return &resp, nildefault: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:2379Key: sysa.rpcMysql:Datasource: root:123456@tcp(192.168.2.204:3306)/dsms_admin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghaiCacheRedis:- Host: 192.168.2.204:6379Pass: qkgxChxNkCwKType: node
config.go
- 修改
rpc/sys/internal/config/config.go
,如下:
type Config struct {zrpc.RpcServerConfMysql struct {Datasource string}CacheRedis cache.ClusterConf
}
servicecontext.go
- 修改
rpc/sys/internal/svc/servicecontext.go
,如下:
type ServiceContext struct {Config config.ConfigCache cache.CacheRedis *redis.RedisUserModel 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].Typer.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 []stringreturn &sysclient.InfoResp{Avatar: "11111",Name: userInfo.Name,MenuListTree: nil,BackgroundUrls: nil,ResetPwd: false,}, nil
}
common目录
common目录下为通用工具,直接拷贝进去即可。
bcrypt.go
- common/utils/bcrypt.go
package utilsimport ("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 10hashedPasswordBytes, 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.PublicKeycipherTxt, 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.PublicKeycipherTxt, 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 errorsconst BaseCode = 50000const RpcCode = 51000const MustUpdatePwdCode = 50005const LoginExpired = 50001
base.go
- common/errors/base.go
package errorstype CommonError interface {Error() stringErrorType() stringData() *CommonErrorResp
}type CommonErrorResp struct {Code int `json:"code"`Message string `json:"message"`Type string `json:"error"`
}
errorx.go
- common/errors/errorx/errorx.go
package errorximport "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 rpcerrorimport "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服务模块。
源码
上面的源码在这里…
源码包
相关文章:
go-zero微服务入门教程
go-zero微服务入门教程 本教程主要模拟实现用户注册和用户信息查询两个接口。 准备工作 安装基础环境 安装etcd, mysql,redis,建议采用docker安装。 MySQL安装好之后,新建数据库dsms_admin,并新建表sys_user&#…...
蓝桥杯刷题--python-12
3768. 字符串删减 - AcWing题库 nint(input()) sinput() res0 i0 while(i<n): if s[i]x: ji1 while(j<n and s[j]x): j1 resmax(j-i-2,0) ij else: i1 print(res) 3777. 砖块 - AcWing题库 # https://www.a…...
LeetCode LCR 085.括号生成
正整数 n 代表生成括号的对数,请设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 示例 1: 输入:n 3 输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例 2: 输入&#x…...
抖音视频评论数据提取软件|抖音数据抓取工具
一、开发背景: 在业务需求中,我们经常需要下载抖音视频。然而,在网上找到的视频通常只能通过逐个复制链接的方式进行抓取和下载,这种操作非常耗时。我们希望能够通过关键词自动批量抓取并选择性地下载抖音视频。因此,为…...
【web】云导航项目部署及环境搭建(复杂)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、项目介绍1.1项目环境架构LNMP1.2项目代码说明 二、项目环境搭建2.1 Nginx安装2.2 php安装2.3 nginx配置和php配置2.3.1 修改nginx文件2.3.2 修改vim /etc/p…...
软件测试人员必会的linux命令
文件和目录操作: ● ls:列出目录中的文件和子目录。 ● cd:改变当前工作目录。 ● mkdir:创建新目录。 ● rm:删除文件或目录。 ● cp:复制文件或目录。 ● mv:移动或重命名文件或目录。 文本查看和编辑: ● cat:查看文件内容。 ● more或less:分页查看文件内…...
Mac使用K6工具压测WebSocket
commend空格 打开终端,安装k6 brew install k6验证是否安装成功 k6 version设置日志级别为debug export K6_LOG_LEVELdebug执行脚本(进入脚本所在文件夹下) k6 run --vus 100 --duration 10m --out csvresult.csv script.js 脚本解释&…...
小程序--vscode配置
要在vscode里开发微信小程序,需要安装以下两个插件: 安装后,即可使用vscode开发微信小程序。 注:若要实现鼠标悬浮提示,则需新建jsconfig.json文件,并进行配置,即可实现。 jsconfig.json内容如…...
linux僵尸进程
僵尸进程(Zombie Process)是指在一个进程终止时,其父进程尚未调用wait()或waitpid()函数来获取该进程的终止状态信息,导致进程的资源(如进程表中的记录)仍然保留在系统中的一种状态。 当一个进程结束时&am…...
【web | CTF】攻防世界 Web_php_unserialize
天命:这条反序列化题目也是比较特别,里面的漏洞知识点,在现在的php都被修复了 天命:而且这次反序列化的字符串数量跟其他题目不一样 <?php class Demo { // 初始化给变量内容,也就是当前文件,高亮显示…...
Vue3中的select 的option是多余的?
背景: 通过Vue3中填充一个下拉框,在打开页面时要指定默认选中,并在选项改变时把下拉框的选中值显示出来 问题: 填充通常的作法是设置 <option v-for"option in cities" :value"option.value" >&a…...
考研408深度分析+全年规划
408确实很难,他的难分两方面 一方面是408本身的复习难度,我们都知道,408的考察科目有四科,分别是数据结构,计算机组成原理,操作系统和计算机网络。大家回想一下自己在大学本科时候学习这些专业课的难度&am…...
【算法笔记】ch01_01_0771 宝石与石头
笔记介绍: 本项目是datawhale发布的LeetCode 算法笔记(Leetcode-Notes)课程完成笔记,根据推荐题目循序渐进练习算法题目。主要用python进行书写相关代码,会介绍解题思路及跑通解法。 0771. 宝石与石头 题目大意 描…...
jQuery瀑布流画廊,瀑布流动态加载
jQuery瀑布流画廊,瀑布流动态加载 效果展示 手机布局 jQuery瀑布流动态加载 HTML代码片段 <!-- mediabanner --><div class"mediabanner"><img src"img/mediabanner.jpg" class"bg"/><div class"text&qu…...
玩转ChatGPT:参考文献速查
一、写在前面 各位大佬,我又回来了,最近2月太忙啦(过年、奶娃、本子、材料、结题),断更了。现水一篇证明我还活着!!! 最近在写国自然本子,遇到一个估计大家都会遇到的问…...
[设计模式Java实现附plantuml源码~行为型]算法的封装与切换——策略模式
前言: 为什么之前写过Golang 版的设计模式,还在重新写Java 版? 答:因为对于我而言,当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言,更适合用于学习设计模式。 为什么类图要附上uml 因为很…...
【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解】
欢迎来CILMY23的博客喔,本期系列为【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解】,图文讲解各种字符串函数,带大家更深刻理解C语言中各种字符串函数的应用&am…...
如何实现一个K8S DevicePlugin?
什么是device plugin k8s允许限制容器对资源的使用,比如CPU和内存,并以此作为调度的依据。 当其他非官方支持的设备类型需要参与到k8s的工作流程中时,就需要实现一个device plugin。 Kubernetes提供了一个设备插件框架,你可以用…...
Android LruCache源码分析
文章目录 Android LruCache源码分析概述LruCache和LinkedHashMap关系源码分析属性写入数据读取数据删除缓存 Android LruCache源码分析 概述 LruCache(Least Recently Used Cache,最近最少使用缓存)是 Android 中的一种缓存机制。 根据数据…...
如何使用Inno Setup制作Unity构建程序的Windows安装程序
1. 准备 (1)准备好Unity构建的程序集合 必须包括: Data文件夹(xxx_Data) Mono文件夹(MonoBleedingEdge) 打包的应用程序文件(xxx.exe) Unity播放器dll文件ÿ…...
linux 面试题
1.linux操作系统的常用指令可以详细说下吗,平常哪些用的比较多 文件目录操作命令: ls cd more cat tail mkdir touch rm rmdir 拷贝复制: cp mv 打包解包压缩解压: tar -z 解亚压缩 -c 打包 -x 解包 -v 显示过程 -f 指定文件名 文本编辑: vi vim 查找: find 查找文件 gre…...
嵌入式中逻辑分析仪基本操作方法
前期准备 1.一块能触摸的屏对应的主板机 2.逻辑分析仪对应的软件工具 3.对应的拓展板 4.确定拓展板的引脚分布情况 第一步:逻辑分析仪j基本操作 1.数据捕捉需要先进行对应软件安装,并按照需求进行配置 2.这里以A20为例:此手机使用显示驱动芯片CST148,触摸屏分辨…...
ONLYOFFICE 桌面编辑器 v8.0 更新内容详细攻略
文章目录 引言PDF 表单RTL 支持电子表格中的新增功能Moodle 集成用密码保护 PDF 文件从“开始”菜单快速创建文档本地界面主题下载安装桌面编辑工具总结 引言 官网链接: ONLYOFFICE 官方网址 ONLYOFFICE 桌面编辑器是一款免费的文档处理软件,适用于 Li…...
2024-2-22 作业
作业要求: 复习前面知识点(指针、结构体、函数)整理思维导图顺序表(按位置插入、按位置删除和去重、重新写)理解链表的代码,尝试写一下链表的尾插和输出 1.复习前面知识点(指针、结构体、函数) 2.整理思维导图 3.顺序表(按位置插入、按位置删除和去重、…...
2.1 RK3399项目开发实录-升级固件介绍(物联技术666)
1. 介绍 1.1. 前言 AIO-3399J 出厂默认安装Android操作系统,如果用户要运行其他操作系统,需要使用对应的固件烧写到主板。 AIO-3399J 有灵活的启动方式。一般情况下,除非硬件损坏,AIO-3399J 开发板是不会变砖的。 如果在升级过…...
Uniapp + VUE3.0 实现双向滑块视频裁剪效果
效果图 <template><view v-if"info" class"all"><video:src"info.videoUrl"class"video" id"video" :controls"true" object-fit"fill" :show-fullscreen-btn"false"play-btn…...
【算法小讲堂】#1 贪心算法
引入——关于贪心算法 我们先来做一个小游戏——现在假设自己是一个小偷,桌上有一些物品,包括一台iPhone15、一个充电宝、一个眼罩和一个溜溜梅。此时,你听说警察即将到来,那么你会先带走哪个东西呢? 一般来讲…...
判断当前shell版本
查看$SHELL环境变量: echo $SHELL输出的结果将是当前使用的shell的路径。例如,如果输出为 /bin/bash,则表示当前使用的是Bash shell。 查看ps命令输出: ps -p $$上述命令将显示当前终端进程的信息,其中 $$ 代表当前进…...
如何实现两个电脑之间通过以太网(网线)实现文件互传
如何实现两个电脑之间通过以太网(网线)实现文件互传 本帖目的:介绍如何通过以太网(网线)连接两台电脑,通过文件夹共享的方式,实现两台电脑之间的文件互传。 本帖以笔者实际工作上遇到的场景为例…...
Jenkins 中部署Nodejs插件并使用,并构建前端项目(3)
遇到多个版本nodeJS需要构建的时候 1、第一种就是一个配置安装,然后进行选中配置 2、第二种就是插件:nvm-wrapper,我们还是选用NodeJS插件: (1)可以加载任意npmrc文件; (2&#x…...
跨境电商平台b2b代表有哪些/淘宝优化
讨论:linux 下socket客户端崩溃后连接不上服务器问题(2012-06-07 02:33:21)标签:服务器客户端杂谈讨论:linux 下socket客户端崩溃后连接不上服务器问题 本帖最后由 fantansy 于 2010-10-28 17:16编辑最近写一个客户端程序,负责向服…...
徐州金网网站建设/中国营销传播网官网
自建数据集实现车标识别 使用YOLOv5 YOLOv3算法训练的模型 支持奔驰 宝马 奥迪 别克 丰田等常见车标 效果还是不错的,同时使用pyqt开发的简易可视化界面...
http wordpress.org/上海网站seo诊断
跳转页面跳到不同的Tab点击整行进入详情页面tab点击兑换记录进入tab兑换记录阻止冒泡click.stop"detailGift()"点击整行进入详情页面tab <el-table:data"tableData"ref"tableDatas"class"view_table"style"width: 100%"…...
上海松江做网站/设计网站模板
服务器文件删除日志 内容精选换一换主机和云服务的日志数据上报至云日志服务后,默认存储时间为7天,您也在创建日志组时,可以对日志存储进行设置(1-30天)。超出存储时间的日志数据将会被自动删除,对于需要长期存储的日志数据(日志持…...
wordpress 地址/凡科网免费建站
1。注册表中的HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Enum\USBSTOR中, 罗列了USB移动存储设备的型号 2。注册表中的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\ 3.注册表中的HKEY_LOCAL_MACHINE\SYSTEM\C…...
龙岩天宫山简介概况/免费的关键词优化工具
1)drawcall是什么,降低drawcall对性能调优的意义 面试题: drawcall是什么? (1)我们的游戏,提交给GPU来绘制; (2)drawcall就是: 我们的整个场景里面,分几个批次提交给显卡绘制,整个就是drawcall (3)100个物体需要绘制,分多少次批次提交给我们的GPU,整个…...