安装goctl

参照官方文档,安装goctl

1
2
3
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 验证
goctl --version

创建Api服务

1
2
3
4
5
6
7
8
mkdir go-zero-demo && cd go-zero-demo
# 生成go.mod
go mod init go-zero-demo
mkdir api common rpc
ls
# api common rpc
mkdir -p api/doc/sys && cd api/doc/sys
# touch user.api

创建user.api, 内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
syntax = "v1"

info(
title: "用户相关"
desc: "用户相关"
author: "pangxie"
)

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代码

1
2
3
4
5
6
7
8
# 在api路径下
cd go-zero-demo/api
# 验证api文件
goctl api format doc/sys/user.api -dir .
# 生成go代码
goctl api go --api doc/sys/user.api -dir .
# run
go run admin.go

创建rpc服务

1
2
3
cd go-zero-demo
mkdir -p rpc/sys && cd rpc/sys
vi sys.proto

sys.proto内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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);
}
1
2
3
4
5
6
7
8
goctl api format sys/sys.proto --dir .
cd go-zero-demo/rpc/sys
# 生成rpc代码
goctl rpc protoc sys.proto --go_out=. --go-grpc_out=. --zrpc_out=.

# run
go run sys.go
# Starting rpc server at 0.0.0.0:8080...

修改API Gateway代码调用rpc服务

common目录

common目录下为通用工具

1
2
3
4
mkdir -p common/utils
mkdir -p common/errors
mkdir -p common/errors/errorx
mkdir -p common/errors/rpcerror

common/utils/bcrypt.gon内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# 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))
}

common/errors/code.go内容

1
2
3
4
5
6
7
8
9
package errors

const BaseCode = 50000

const RpcCode = 51000

const MustUpdatePwdCode = 50005

const LoginExpired = 50001

common/errors/base.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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"`
}

common/errors/errorx/errorx.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package errorx

import "go-zero-demo/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"}
}

common/errors/rpcerror/rpcerror.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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}
}

修改API Gateway代码调用rpc服务

修改admin-api.yaml

1
vi api/etc/admin-api.yaml

修改内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Name: admin-api
Host: 0.0.0.0
Port: 8888
Timeout: 60000

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}
}

修改api/internal/svc/servicecontext.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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/sys/user/useraddlogic.go里的UserAdd方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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, err
}

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

注意添加引用

1
2
3
4
import (
"encoding/json"
"go-zero-demo/rpc/sys/sysclient"
)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

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)
}

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目录

1
mkdir -p  rpc/doc/sql/sys/

在rpc/doc/sql/sys目录下编写sql文件sys_user.sql,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- 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);

在数据库中,创建database,并执行上述语句,创建表格及插入数据

1
create database dsms_admin

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

1
2
# cd rpc
goctl model mysql ddl -src doc/sql/sys/sys_user.sql -dir ./model/sysmodel

完善CRUD代码

sysusermodel.go

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

修改rpc代码调用crud代码

修改rpc/sys/etc/sys.yaml,如下内容:

1
2
3
4
5
6
7
8
9
10
11
Name: sys.rpc
# 注意不能为0.0.0.0:8080, 注意host
ListenOn: 127.0.0.1:8080
Timeout: 10000
Etcd:
Hosts:
- 127.0.0.1:2379
Key: sysa.rpc

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

修改rpc/sys/internal/config/config.go

1
2
3
4
5
6
7
8
9
10
11
12
13
package config

import (
"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
zrpc.RpcServerConf

Mysql struct {
Datasource string
}
}

修改rpc/sys/internal/svc/servicecontext.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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),
}
}

修改rpc/sys/internal/logic/useraddlogic.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
}

注意添加import

1
2
3
4
import(
"errors"
"go-zero-demo/rpc/model/sysmodel"
)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
}

注意添加import

1
2
3
4
5
6
import(
"strconv"
"fmt"
sq "github.com/Masterminds/squirrel"
"github.com/zeromicro/go-zero/core/logx"
)

部署Etcd服务

vi docker_run.sh

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
echo "etcd"
NAME=etcd
HOME=$(pwd)

docker stop $NAME
docker rm $NAME
docker run --name $NAME --restart=always \
-p 2379:2379 \
-p 2380:2380 \
-e ALLOW_NONE_AUTHENTICATION=yes \
-d bitnami/etcd:3.5.13
1
./docker_run.sh

运行

1
2
3
4
5
6
# 在根目录go-zero-demo执行下命令
go mod tidy
# 启动rpc
cd rpc/sys && go run sys.go
# 启动api
cd api && go run admin

可能错误信息

1
rpc dial: etcd://127.0.0.1:2379/sysa.rpc, error: context deadline exceeded, make sure rpc service \"sysa.rpc\" is already started

查看etcd中服务是否注册

1
docker exec -it etcd etcdctl get sysa.rpc --prefix

修改rpc/sys/etc/sys.yaml

1
2
3
Name: sys.rpc
# 0.0.0.0 修改为127.0.0.1
ListenOn: 127.0.0.1:8080

重新启动rpc服务,再次查看docker exec -it etcd etcdctl get sysa.rpc --prefix

测试

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

原文

go-zero微服务入门教程

示例源码