kratos 框架商城微服务实战之用户服务 (四)

本文最后更新于:2 个月前

Go-kratos 框架商城微服务实战之用户服务 (四)

这篇主要编写 HTTP API 端的服务,跟前几篇写的用户服务对接上,主要还是项目的初始化准备工作。写的不清晰的地方可看GitHub 源码 , 也感谢您指出不足之处。

注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。这里所有 import 引入的包都没有特殊说明,自己写的时候要注意包的引入。

shop Api 准备工作

new 一个新的 kratos 项目

在 kratos-shop 目录下新建一个 kratos new shop shop 项目

1
2
3
4
5
// 整体的项目 目录结构如下
|-- kratos-shop
|-- service
|-- user // 原先的用户服务 grpc
|-- shop // 刚刚通过 kratos new shop 新增的项目代码
  • 进入新建的 shop 目录下
  • 删除目录下的所有文件 rm -rf api/helloworld
  • 复制  service/user/api/user/v1/user.proto  文件到新建的  shop/api/service/user/v1  目录下
  • 执行命令 kratos proto add api/shop/v1/user.proto 创建 api user.proto
  • 执行 kratos proto server api/user/v1/user.proto -t internal/service 命令生成对应的 service 文件。
  • 删除不需要的 service 文件 rm internal/service/greeter.go
    完整执行命令如下:
1
2
3
4
5
6
7
8
9
cd kratos-shop
kratos new shop
cd shop
rm -rf api/helloworld
rm internal/service/greeter.go
kratos proto add api/shop/v1/user.proto
kratos proto server api/shop/v1/user.proto -t internal/service
mkdir -p api/service/user/v1
cp ../service/user/api/user/v1/user.proto api/service/user/v1

proto 的目录结构如下:

1
2
3
4
5
6
7
8
├── api
│   ├── service
│   │   └── user
│   │   └── v1
│   │   └── user.proto
│   └── shop
│   └── v1
│   └── user.proto

修改 shop 下的 user.proto

这里提供对外访问的接口,会聚合从不同的服务之间获取数据,接口路由通过 Protobuf IDL 定义对应的 REST API 和 gRPC API,

参数校验使用 Validate 中间件,使用 proto-gen-validate 生成
在使用 validate 之前首先需要安装  proto-gen-validate

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
syntax = "proto3";

package shop.shop.v1;
// 这里可以把 proto 文件下载下来,放到项目的 third_party 目录下
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "validate/validate.proto";

option go_package = "shop/api/shop/v1;v1";
// The Shop service definition.
service Shop {
rpc Register (RegisterReq) returns (RegisterReply) {
option (google.api.http) = {
post: "/api/users/register",
body: "*",
};
}
rpc Login (LoginReq) returns (RegisterReply) {
option (google.api.http) = {
post: "/api/users/login",
body: "*",
};
}
rpc Captcha (google.protobuf.Empty) returns (CaptchaReply) {
option (google.api.http) = {
get: "/api/users/captcha",
};
}
rpc Detail (google.protobuf.Empty) returns (UserDetailResponse) {
option (google.api.http) = {
get: "/api/users/detail",
};
}
}

// Data returned by registration and login
message RegisterReply {
int64 id = 1;
string mobile = 3;
string username = 4;
string token = 5;
int64 expiredAt = 6;
}

message RegisterReq {
string mobile = 1 [(validate.rules).string.len = 11];
string username = 2 [(validate.rules).string = {min_len: 3, max_len: 15}];
string password = 3 [(validate.rules).string = {min_len: 8}];
}

message LoginReq {
string mobile = 1 [(validate.rules).string.len = 11];
string password = 2 [(validate.rules).string = {min_len: 8}];
string captcha = 3 [(validate.rules).string = {min_len: 5,max_len:5}];
string captchaId = 4 [(validate.rules).string ={min_len: 1}];
}

// user Detail returned
message UserDetailResponse{
int64 id = 1;
string mobile = 2;
string nickName = 3;
int64 birthday = 4;
string gender = 5;
int32 role = 6;
}

message CaptchaReply{
string captchaId = 1;
string picPath = 2;
}

这里一共定义了 4 个 rpc 方法,其中三个是需要跟之前写的用户服务交互的,还有个获取图片验证码的接口,自己内部实现。

  • shop 根目录执行  make api ,生成对应的  *pb.go  文件

修改配置文件

  • 修改  shop/configs/config.yaml  文件

项目中引入了 consul 配置需要把相关的配置设置好,service 就是 consul 用来服务发现的。这里的 trace 并没有用到呢先在这里定义了,之后会专门拿一篇来说说。auth 是用来 jwt 验证。

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
name: shop.api
server:
http:
addr: 0.0.0.0:8097
timeout: 1s
grpc:
addr: 0.0.0.0:9001
timeout: 1s
data:
database:
driver: mysql
source: root:root@tcp(127.0.0.1:3306)/test
redis:
addr: 127.0.0.1:6379
read_timeout: 0.2s
write_timeout: 0.2s
trace:
endpoint: http://127.0.0.1:14268/api/traces
auth:
jwt_key: hqFr%3ddt32DGlSTOI5cO6@TH#fFwYnP$S
service:
user:
endpoint: discovery:///shop.user.service
goods:
endpoint: discovery:///shop.goods.service
  • 新增  shop/configs/registry.yaml  文件
1
2
3
consul:
address: 127.0.0.1:8500
scheme: http
  • 修改  internal/conf/conf.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
syntax = "proto3";
package shop.api;

option go_package = "shop/internal/conf;conf";

import "google/protobuf/duration.proto";

message Bootstrap {
Server server = 1;
Data data = 2;
Trace trace = 3; // 链路追踪
Auth auth = 4; // 认证鉴权
Service service = 5; // 服务注册与发现
}

message Server {
message HTTP {
string network = 1;
string addr = 2;
google.protobuf.Duration timeout = 3;
}
message GRPC {
string network = 1;
string addr = 2;
google.protobuf.Duration timeout = 3;
}
HTTP http = 1;
GRPC grpc = 2;
}

message Data {
message Database {
string driver = 1;
string source = 2;
}
message Redis {
string network = 1;
string addr = 2;
google.protobuf.Duration read_timeout = 3;
google.protobuf.Duration write_timeout = 4;
}
Database database = 1;
Redis redis = 2;
}

message Service {
message User { // 用户服务
string endpoint = 1;
}
message Goods { // 商品服务
string endpoint = 1;
}
User user = 1;
Goods goods = 2;
}

message Trace {
string endpoint = 1;
}

message Registry {
message Consul {
string address = 1;
string scheme = 2;
}
Consul consul = 1;
}

message Auth {
string jwt_key = 1;
}
  • 生成新的配置文件
1
2
3
shop 根目录执行
make config
生成 shop/internal/conf/conf.pb.go 文件

修改 HTTP 服务

  • 修改  internal/server/http.go  文件

    这里用到的一些 middleware 中间件都是 kratos 官方支持的,jwt、validate、tracing,具体使用方式可参考 kratos 的middleware 文档

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
package server

import (
"context"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/auth/jwt"
"github.com/go-kratos/kratos/v2/middleware/logging"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/middleware/selector"
"github.com/go-kratos/kratos/v2/middleware/validate"
"github.com/go-kratos/kratos/v2/transport/http"
jwt2 "github.com/golang-jwt/jwt/v4"
"github.com/gorilla/handlers"
v1 "shop/api/shop/v1"
"shop/internal/conf"
"shop/internal/service"
)

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, ac *conf.Auth, s *service.ShopService, logger log.Logger) *http.Server {
var opts = []http.ServerOption{
http.Middleware(
recovery.Recovery(),
validate.Validator(), // 接口访问的参数校验
selector.Server( // jwt 验证
jwt.Server(func(token *jwt2.Token) (interface{}, error) {
return []byte(ac.JwtKey), nil
}, jwt.WithSigningMethod(jwt2.SigningMethodHS256)),
).Match(NewWhiteListMatcher()).Build(),
logging.Server(logger),
),
http.Filter(handlers.CORS( // 浏览器跨域
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}),
handlers.AllowedOrigins([]string{"*"}),
)),
}
if c.Http.Network != "" {
opts = append(opts, http.Network(c.Http.Network))
}
if c.Http.Addr != "" {
opts = append(opts, http.Address(c.Http.Addr))
}
if c.Http.Timeout != nil {
opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
}
srv := http.NewServer(opts...)
v1.RegisterShopHTTPServer(srv, s)
return srv
}

// NewWhiteListMatcher 设置白名单,不需要 token 验证的接口
func NewWhiteListMatcher() selector.MatchFunc {
whiteList := make(map[string]struct{})
whiteList["/shop.shop.v1.Shop/Captcha"] = struct{}{}
whiteList["/shop.shop.v1.Shop/Login"] = struct{}{}
whiteList["/shop.shop.v1.Shop/Register"] = struct{}{}
return func(ctx context.Context, operation string) bool {
if _, ok := whiteList[operation]; ok {
return false
}
return true
}
}
  • 修改  internal/server/server.go

    由于此服务只对外提供 http 服务,所以同目录下 grpc 文件可以删除,这样子注册服务的时候也把 grpc 服务去掉。

1
2
3
4
5
6
7
8
package server

import (
"github.com/google/wire"
)

// ProviderSet is server providers.
var ProviderSet = wire.NewSet(NewHTTPServer)

实现接口

  • 修改  shop/internal/service/service.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
package service

import (
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
v1 "shop/api/shop/v1"
"shop/internal/biz"
)

// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewShopService)

// ShopService is a shop service.
type ShopService struct {
v1.UnimplementedShopServer

uc *biz.UserUsecase
log *log.Helper
}

// NewShopService new a shop service.
func NewShopService(uc *biz.UserUsecase, logger log.Logger) *ShopService {
return &ShopService{
uc: uc,
log: log.NewHelper(log.With(logger, "module", "service/shop")),
}
}

  • 修改 shop/internal/service/user.go

这里的 ShopService 的 usecase 还没实现,编辑器可能会有错误提示,先忽略

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
package service

import (
"context"
"google.golang.org/protobuf/types/known/emptypb"

v1 "shop/api/shop/v1"
)

func (s *ShopService) Register(ctx context.Context, req *v1.RegisterReq) (*v1.RegisterReply, error) {
return s.uc.CreateUser(ctx, req)
}

func (s *ShopService) Login(ctx context.Context, req *v1.LoginReq) (*v1.RegisterReply, error) {
return s.uc.PassWordLogin(ctx, req)
}

func (s *ShopService) Captcha(ctx context.Context, r *emptypb.Empty) (*v1.CaptchaReply, error) {
return s.uc.GetCaptcha(ctx)
}

func (s *ShopService) Detail(ctx context.Context, r *emptypb.Empty) (*v1.UserDetailResponse, error) {
return s.uc.UserDetailByID(ctx)
}

新增 jwt 验证的 Middleware

  • 新建文件 internal/pkg/middleware/auth/auth.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 auth

import (
"errors"
"github.com/golang-jwt/jwt/v4"
)

type CustomClaims struct {
ID int64
NickName string
AuthorityId int
jwt.StandardClaims
}

// CreateToken generate token
func CreateToken(c CustomClaims, key string) (string, error) {
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
signedString, err := claims.SignedString([]byte(key))
if err != nil {
return "", errors.New("generate token failed" + err.Error())
}
return signedString, nil
}

新增生成验证码的文件

  • 新建文件 internal/pkg/captcha/captcha.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
package captcha

import (
"context"
"github.com/mojocn/base64Captcha"
)

var Store = base64Captcha.DefaultMemStore

type CaptchaInfo struct {
CaptchaId string
PicPath string
}

// GetCaptcha 生成验证码
func GetCaptcha(ctx context.Context) (*CaptchaInfo, error) {
driver := base64Captcha.NewDriverDigit(80, 250, 5, 0.7, 80)
cp := base64Captcha.NewCaptcha(driver, Store)
id, b64s, err := cp.Generate()
if err != nil {
return nil, err
}

return &CaptchaInfo{
CaptchaId: id,
PicPath: b64s,
}, nil
}

  • 修改 shop/internal/biz/user.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
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
184
185
186
187
188
189
190
191
192
193
194
195
package biz

import (
"context"
"errors"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/auth/jwt"
jwt2 "github.com/golang-jwt/jwt/v4"
v1 "shop/api/shop/v1"
"shop/internal/conf"
"shop/internal/pkg/captcha"
"shop/internal/pkg/middleware/auth"
"time"
)

// 定义错误信息
var (
ErrPasswordInvalid = errors.New("password invalid")
ErrUsernameInvalid = errors.New("username invalid")
ErrCaptchaInvalid = errors.New("verification code error")
ErrMobileInvalid = errors.New("mobile invalid")
ErrUserNotFound = errors.New("user not found")
ErrLoginFailed = errors.New("login failed")
ErrGenerateTokenFailed = errors.New("generate token failed")
ErrAuthFailed = errors.New("authentication failed")
)

// 定义返回的数据的结构体
type User struct {
ID int64
Mobile string
NickName string
Birthday int64
Gender string
Role int
CreatedAt time.Time
}

type UserRepo interface {
CreateUser(c context.Context, u *User) (*User, error)
UserByMobile(ctx context.Context, mobile string) (*User, error)
UserById(ctx context.Context, Id int64) (*User, error)
CheckPassword(ctx context.Context, password, encryptedPassword string) (bool, error)

}

type UserUsecase struct {
uRepo UserRepo
log *log.Helper
signingKey string // 这里是为了生存 token 的时候可以直接取配置文件里面的配置
}

func NewUserUsecase(repo UserRepo, logger log.Logger, conf *conf.Auth) *UserUsecase {
helper := log.NewHelper(log.With(logger, "module", "usecase/shop"))
return &UserUsecase{uRepo: repo, log: helper, signingKey: conf.JwtKey}
}

// GetCaptcha 验证码
func (uc *UserUsecase) GetCaptcha(ctx context.Context) (*v1.CaptchaReply, error) {
captchaInfo, err := captcha.GetCaptcha(ctx)
if err != nil {
return nil, err
}

return &v1.CaptchaReply{
CaptchaId: captchaInfo.CaptchaId,
PicPath: captchaInfo.PicPath,
}, nil
}

func (uc *UserUsecase) UserDetailByID(ctx context.Context) (*v1.UserDetailResponse, error) {
// 在上下文 context 中取出 claims 对象
var uId int64
if claims, ok := jwt.FromContext(ctx); ok {
c := claims.(jwt2.MapClaims)
if c["ID"] == nil {
return nil, ErrAuthFailed
}
uId = int64(c["ID"].(float64))
}

user, err := uc.uRepo.UserById(ctx, uId)
if err != nil {
return nil, err
}
return &v1.UserDetailResponse{
Id: user.ID,
NickName: user.NickName,
Mobile: user.Mobile,
}, nil
}

func (uc *UserUsecase) PassWordLogin(ctx context.Context, req *v1.LoginReq) (*v1.RegisterReply, error) {
// 表单验证
if len(req.Mobile) <= 0 {
return nil, ErrMobileInvalid
}
if len(req.Password) <= 0 {
return nil, ErrUsernameInvalid
}
// 验证验证码是否正确
if !captcha.Store.Verify(req.CaptchaId, req.Captcha, true) {
return nil, ErrCaptchaInvalid
}

if user, err := uc.uRepo.UserByMobile(ctx, req.Mobile); err != nil {
return nil, ErrUserNotFound
} else {
// 用户存在检查密码
if passRsp, pasErr := uc.uRepo.CheckPassword(ctx, req.Password, user.Password); pasErr != nil {
return nil, ErrPasswordInvalid
} else {
if passRsp {
claims := auth.CustomClaims{
ID: user.ID,
NickName: user.NickName,
AuthorityId: user.Role,
StandardClaims: jwt2.StandardClaims{
NotBefore: time.Now().Unix(), // 签名的生效时间
ExpiresAt: time.Now().Unix() + 60*60*24*30, // 30天过期
Issuer: "Gyl",
},
}

token, err := auth.CreateToken(claims, uc.signingKey)
if err != nil {
return nil, ErrGenerateTokenFailed
}
return &v1.RegisterReply{
Id: user.ID,
Mobile: user.Mobile,
Username: user.NickName,
Token: token,
ExpiredAt: time.Now().Unix() + 60*60*24*30,
}, nil
} else {
return nil, ErrLoginFailed
}
}
}
}

func (uc *UserUsecase) CreateUser(ctx context.Context, req *v1.RegisterReq) (*v1.RegisterReply, error) {
newUser, err := NewUser(req.Mobile, req.Username, req.Password)
if err != nil {
return nil, err
}
createUser, err := uc.uRepo.CreateUser(ctx, &newUser)
if err != nil {
return nil, err
}
claims := auth.CustomClaims{
ID: createUser.ID,
NickName: createUser.NickName,
AuthorityId: createUser.Role,
StandardClaims: jwt2.StandardClaims{
NotBefore: time.Now().Unix(), // 签名的生效时间
ExpiresAt: time.Now().Unix() + 60*60*24*30, // 30天过期
Issuer: "Gyl",
},
}
token, err := auth.CreateToken(claims, uc.signingKey)
if err != nil {
return nil, err
}

return &v1.RegisterReply{
Id: createUser.ID,
Mobile: createUser.Mobile,
Username: createUser.NickName,
Token: token,
ExpiredAt: time.Now().Unix() + 60*60*24*30,
}, nil
}

func NewUser(mobile, username, password string) (User, error) {
// check mobile
if len(mobile) <= 0 {
return User{}, ErrMobileInvalid
}
// check username
if len(username) <= 0 {
return User{}, ErrUsernameInvalid
}
// check password
if len(password) <= 0 {
return User{}, ErrPasswordInvalid
}
return User{
Mobile: mobile,
NickName: username,
Password: password,
}, nil
}

  • 修改  shop/internal/biz/biz.go  文件
1
2
3
4
5
6
package biz

import "github.com/google/wire"

// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewUserUsecase)
  • 修改 internal/data/data.go

    这里比较重要,data 不是直接链接本机器配置的数据库的,而是链接各个服务,通过服务提供的 rpc 接口去获取数据的。

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
package data

import (
"context"
consul "github.com/go-kratos/kratos/contrib/registry/consul/v2"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/registry"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/google/wire"
consulAPI "github.com/hashicorp/consul/api"
grpcx "google.golang.org/grpc"
userV1 "shop/api/service/user/v1"
"shop/internal/conf"
"time"
)

// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewUserServiceClient, NewRegistrar, NewDiscovery)

// Data .
type Data struct {
log *log.Helper
uc userV1.UserClient // 用户服务的客户端
}

// NewData .
func NewData(c *conf.Data, uc userV1.UserClient, logger log.Logger) (*Data, error) {
l := log.NewHelper(log.With(logger, "module", "data"))
return &Data{log: l, uc: uc}, nil
}

// NewUserServiceClient 链接用户服务
func NewUserServiceClient(ac *conf.Auth, sr *conf.Service, rr registry.Discovery) userV1.UserClient {
conn, err := grpc.DialInsecure(
context.Background(),
grpc.WithEndpoint(sr.User.Endpoint),// consul
grpc.WithDiscovery(rr),// consul
grpc.WithMiddleware(
recovery.Recovery(),
),
grpc.WithTimeout(2*time.Second),
)
if err != nil {
panic(err)
}
c := userV1.NewUserClient(conn)
return c
}

// NewRegistrar add consul
func NewRegistrar(conf *conf.Registry) registry.Registrar {
c := consulAPI.DefaultConfig()
c.Address = conf.Consul.Address
c.Scheme = conf.Consul.Scheme
cli, err := consulAPI.NewClient(c)
if err != nil {
panic(err)
}
r := consul.New(cli, consul.WithHealthCheck(false))
return r
}

func NewDiscovery(conf *conf.Registry) registry.Discovery {
c := consulAPI.DefaultConfig()
c.Address = conf.Consul.Address
c.Scheme = conf.Consul.Scheme
cli, err := consulAPI.NewClient(c)
if err != nil {
panic(err)
}
r := consul.New(cli, consul.WithHealthCheck(false))
return r
}

  • 修改 shop/internal/data/user.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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package data

import (
"context"
"github.com/go-kratos/kratos/v2/log"
userService "shop/api/service/user/v1"
"shop/internal/biz"
)

type userRepo struct {
data *Data
log *log.Helper
}

// NewUserRepo .
func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo {
return &userRepo{
data: data,
log: log.NewHelper(log.With(logger, "module", "repo/user")),
}
}

func (u *userRepo) CreateUser(c context.Context, user *biz.User) (*biz.User, error) {
createUser, err := u.data.uc.CreateUser(c, &userService.CreateUserInfo{
NickName: user.NickName,
Password: user.Password,
Mobile: user.Mobile,
})
if err != nil {
return nil, err
}
return &biz.User{
ID: createUser.Id,
Mobile: createUser.Mobile,
NickName: createUser.NickName,
}, nil
}

func (u *userRepo) UserByMobile(c context.Context, mobile string) (*biz.User, error) {
byMobile, err := u.data.uc.GetUserByMobile(c, &userService.MobileRequest{Mobile: mobile})
if err != nil {
return nil, err
}
return &biz.User{
Mobile: byMobile.Mobile,
ID: byMobile.Id,
NickName: byMobile.NickName,
}, nil
}

func (u *userRepo) CheckPassword(c context.Context, password, encryptedPassword string) (bool, error) {
if byMobile, err := u.data.uc.CheckPassword(c, &userService.PasswordCheckInfo{Password: password, EncryptedPassword: encryptedPassword}); err != nil {
return false, err
} else {
return byMobile.Success, nil
}
}

func (u *userRepo) UserById(c context.Context, id int64) (*biz.User, error) {
user, err := u.data.uc.GetUserById(c, &userService.IdRequest{Id: id})
if err != nil {
return nil, err
}
return &biz.User{
ID: user.Id,
Mobile: user.Mobile,
NickName: user.NickName,
Gender: user.Gender,
Role: int(user.Role),
}, nil
}

修改启动服务

  • 修改  wire.go

    一定要注意这里的注入的参数,多了少了都会报错的

1
2
3
4
5
6
7
package main

...

func initApp(*conf.Server, *conf.Data, *conf.Auth, *conf.Service, *conf.Registry, log.Logger) (*kratos.App, func(), error) {
panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
}
  • 重新生成依赖注入关系
1
2
根目录执行
make wire
  • 修改入口文件  main.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
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
package main

import (
"flag"
"os"

"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/config/file"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/registry"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http"
"shop/internal/conf"
)

// go build -ldflags "-X main.Version=x.y.z"
var (
// Name is the name of the compiled software.
Name = "shop.api"
// Version is the version of the compiled software.
Version = "shop.api.v1"
// flagconf is the config flag.
flagconf string

id, _ = os.Hostname()
)

func init() {
flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml")
}

func newApp(logger log.Logger, hs *http.Server, gs *grpc.Server, rr registry.Registrar) *kratos.App {
return kratos.New(
kratos.ID(id+"shop.api"),
kratos.Name(Name),
kratos.Version(Version),
kratos.Metadata(map[string]string{}),
kratos.Logger(logger),
kratos.Server(
hs,
),
kratos.Registrar(rr),
)
}

func main() {
flag.Parse()
logger := log.With(log.NewStdLogger(os.Stdout),
"ts", log.DefaultTimestamp,
"caller", log.DefaultCaller,
"service.id", id,
"service.name", Name,
"service.version", Version,
"trace_id", tracing.TraceID(),
"span_id", tracing.SpanID(),
)
c := config.New(
config.WithSource(
file.NewSource(flagconf),
),
)
defer c.Close()

if err := c.Load(); err != nil {
panic(err)
}

var bc conf.Bootstrap
if err := c.Scan(&bc); err != nil {
panic(err)
}

var rc conf.Registry
if err := c.Scan(&rc); err != nil {
panic(err)
}

app, cleanup, err := initApp(bc.Server, bc.Data, bc.Auth, bc.Service, &rc, logger)
if err != nil {
panic(err)
}
defer cleanup()

// start and wait for stop signal
if err := app.Run(); err != nil {
panic(err)
}
}

结束语

这一篇主要是前期的准备工作,下一篇开始测试接口、测试服务注册与发现、加入链路追踪并测试。

感谢您的耐心阅读,动动手指点个赞吧。

关注我获取更新