微服务框架
- 可以在服务发现中搭建集群,引入多个同名服务
- 在浏览器和web服务之间引入nginx,做反向代理
项目准备
-
准备项目环境
- 创建项目目录 web、service
- 在 web 端 使用 MVC
- 创建项目常用目录: conf 配置文件、utils 工具类、bin可执行文件、test测试目录
- util包中导入异常处理文件error.go
- 导入前端资源 html/ 到 view/ 中
-
开发项目
- 开发 微服务端
- 开发 web 服务(客户端)
-
数据库/数据表准备
- 在 项目的 web/model/ 下 创建 model.go 文件。
- 参照讲义《微服务项目讲义1.md》中 2.2.2 小节,将创建表的代码添加到 model.go
- 使用SQL语句创建数据库search_house。修改 InitDb() 函数。同时,指定MySQL使用的时间。初始化全局连接池句柄。
- 将 InitDb() 在 web/main.go 中 调用!
- 运行 web/main.go , 创建 项目中需要使用的表。
项目
1. 获取session信息服务
router.GET("/api/v1.0/session", controller.GetSession) //注册路由
func GetSession(ctx *gin.Context) {
resp := make(map[string]string)
resp["errno"] = utils.RECODE_SESSIONERR
resp["errmsg"] = utils.RecodeText(utils.RECODE_SESSIONERR)
ctx.JSON(http.StatusOK, resp) //json序列化
}
2. 获取验证码图片服务
- Web端实现
-
去 github 中搜索 “captcha” 关键词。 过滤 Go语言。 —— afocus/captcha
-
使用
go get github.com/afocus/captcha
下载源码包。 -
参照 github 中的示例代码,测试生成 图片验证码
router.GET("/api/v1.0/imagecode/:uuid", controller.GetImageCd)
func GetImageCd(ctx *gin.Context) {
// 获取uuid
uuid := ctx.Param("uuid")
fmt.Println(uuid)
// 生成验证码
cap := captcha.New() // 初始化对象
cap.SetFont("/Users/yaxuan/go/src/bj38web/web/conf/comic.ttf") // 设置字体
cap.SetSize(128, 64) // 设置验证码大小
cap.SetDisturbance(captcha.MEDIUM) // 设置干扰强度
cap.SetFrontColor(color.RGBA{0,0,0, 255}) // 设置前景色
cap.SetBkgColor(color.RGBA{100,0,255, 255}, color.RGBA{255,0,127, 255}, color.RGBA{255,255,10, 255}) // 设置背景色
img,str := cap.Create(4,captcha.NUM) // 生成字体
png.Encode(ctx.Writer, img)
fmt.Println(str)
}
- 将图片验证码功能移植微服务 - 微服务端
-
创建 微服务项目:
micro new --type srv bj38web/service/getCaptcha
创建完成,会在项目 bj38web 的 service/ 中,多出 getCaptcha 的微服务项目。
-
修改密码本 —— getCaptcha/proto/getCaptcha.proto
syntax = "proto3"; package go.micro.srv.getCaptcha; service GetCaptcha { rpc Call(Request) returns (Response) {} } message Request { } message Response { // 使用切片存储图片信息, 用 json 序列化 bytes img = 1; }
-
编译 proto 文件。 —— make 命令!得到 getCaptcha.micro.go 和 getCaptcha.pb.go
-
修改 service/getCaptcha/main.go
import (
"github.com/micro/go-micro/util/log"
"github.com/micro/go-micro"
"bj38web/service/getCaptcha/handler"
getCaptcha "bj38web/service/getCaptcha/proto/getCaptcha"
)
func main() {
// New Service
service := micro.NewService(
micro.Name("go.micro.srv.getCaptcha"),
micro.Version("latest"),
)
// Register Handler
getCaptcha.RegisterGetCaptchaHandler(service.Server(), new(handler.GetCaptcha))
// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
- 修改服务发现 : mdns ——> consul
-
初始化consul
-
添加 consul 到 micro.NewSerive( )
consulReg := consul.NewRegistry() // New Service service := micro.NewService( micro.Address("192.168.6.108:12341"), // 防止随机生成 port micro.Name("go.micro.srv.getCaptcha"), micro.Registry(consulReg), // 添加注册 micro.Version("latest"), )
-
启动 consul , consul agent -dev
- 修改 handler/getCaptcha.go 文件,实现生成验证码图片功能
func (e *GetCaptcha) Call(ctx context.Context, req *getCaptcha.Request, rsp *getCaptcha.Response) error {
// 生成验证码
cap := captcha.New() // 初始化对象
cap.SetFont("/Users/yaxuan/go/src/bj38web/service/getCaptcha/conf/comic.ttf") // 设置字体
cap.SetSize(128, 64) // 设置验证码大小
cap.SetDisturbance(captcha.MEDIUM) // 设置干扰强度
cap.SetFrontColor(color.RGBA{0,0,0, 255}) // 设置前景色
cap.SetBkgColor(color.RGBA{100,0,255, 255}, color.RGBA{255,0,127, 255}, color.RGBA{255,255,10, 255}) // 设置背景色
img,_ := cap.Create(4,captcha.NUM) // 生成字体
imgBuf, _ := json.Marshal(img) // 将生成的图片序列化
rsp.Img = imgBuf // 将imgBuf使用rsp传出
return nil
}
- 将图片验证码功能移植微服务 - web端
-
拷贝密码本。 将 service 下的 proto/ 拷贝 web/ 下
-
在 GetImageCd() 中 导入包,起别名:
getCaptcha "bj38web/web/proto/getCaptcha"
-
修改GetImageCd函数,调用远程服务
// GetImageCd 获取验证码图片服务
func GetImageCd(ctx *gin.Context) {
// 获取uuid
uuid := ctx.Param("uuid")
fmt.Println(uuid)
// 调用微服务函数
microClient := getCaptcha.NewGetCaptchaService("go.micro.srv.getCaptcha", client.DefaultClient)
resp, err := microClient.Call(context.TODO(), &getCaptcha.Request{})
if err != nil {
fmt.Println("未找到远程服务")
}
// 将得到的数据反序列化
var img captcha.Image
json.Unmarshal(resp.Img, &img)
// 将图片写出到浏览器
png.Encode(ctx.Writer, img)
}
- 在微服务端添加redis数据库存储 (存储图片验证码的uuid和码值)
- 微服务端
-
修改 service/proto 中 getCaptcha.proto 的 Request 消息体,填加 uuid 成员。使用 make 命令,重新生成 getCaptcha.proto 对应的文件。
message Request { string uuid = 1; }
-
遵循 MVC 代码组织架构,在 service/getCaptcha/ 中 创建 model 目录
-
创建 modelFunc.go 文件 封装并实现 SaveImgCode() 函数
// SaveImgCode 存储图片ID到redis数据库
func SaveImgCode(code, uuid string) error {
// 1. 连接数据库
conn, err := redis.Dial("tcp", ":6379")
if err != nil {
fmt.Println("redis.Dial err: ", err)
return err
}
defer conn.Close()
// 2. 操作数据库 -- 有效时间为5分钟
_, err = conn.Do("setex", uuid, 60*5, code)
return err // 不需要回复助手
}
- 在getCaptcha.go文件的 Call() 方法中,cap.Create() 后,调用 SaveImgCode() 传参。
- web端
-
修改密码本!因为 微服务端修改了 proto/,需要重新拷贝 proto/ 到web
-
修改 web/controller/user.go 中 Call() 方法传参。给 Request{} 初始化。
resp, err := microClient.Call(context.TODO(), &getCaptcha.Request{Uuid:uuid})
if err != nil {
fmt.Println("未找到远程服务...")
return
}
3. 获取短信验证码服务
注册阿里云账号
- 通过实名认证
- 开通短信验证码功能 —— 充值
- 申请 AccessKey
用户登录名称 yaxuanali@1579725908251216.onaliyun.com
AccessKey ID LTAI5tNXHuUUR5K9NdF29qmT
AccessKey Secret v2r9Xb591gzuEGNwXsRU8IMLVFc0M2
- 申请签名。国内消息 —— 签名管理
- 申请模板。国内消息 —— 模板管理
- 测试使用 OpenAPI Explorer
将短信验证码集成到项目
-
修改 router 分组。
--- 在 web/main.go 中 // 添加路由分组 r1 := router.Group("/api/v1.0") { r1.GET("/session", controller.GetSession) r1.GET("/imagecode/:uuid", controller.GetImageCd) r1.GET("/smscode/:phone", controller.GetSmscd) }
-
提取Get请求中的数据
--- 在 web/controller/user.go 中 GET 请求 URL 格式: http://IP:port/资源路径?key=value&key=value&key=value... func GetSmsCd(ctx *gin.Context) { // 获取手机号 phone := ctx.Param("phone") // 获取请求参数 imgCode := ctx.Query("text") uuid := ctx.Query("id")fmt.Println(phone, imgCode, uuid) }
-
封装实现 校验图片 验证码
--- 依据 MVC 代码架构。 创建 model/modelFunc.go // 校验图片验证码 func CheckImgCode(uuid, imgCode string) bool { // 连接redis conn, err := redis.Dial("tcp", "192.168.6.108:6379") if err != nil { fmt.Println("redis.Dial err:", err) return false } defer conn.Close() // 查询redis数据 code, err := redis.String(conn.Do("get", uuid)) if err != nil { fmt.Println("查询错误 err:", err) return false } // 返回校验结果 return code == imgCode }
-
根据校验结果,发送短信验证码
result := model.CheckImgCode(uuid, imgCode) if result { // 校验成功 // 发送短信验证码 response, _ := client.SendSms(request) if response.IsSuccess() { // 发送短信验证码 成功 } else { // 发送端验证码 失败. } } else { // 校验失败 }
-
发送短信验证码实现
client, _ := dysmsapi.NewClientWithAccessKey("cn-hangzhou", "LTAI4FgbQXjf117SX7E75Rmn", "6icOghQlhjevrTM5PxfiB8nDTxB9z6") request := dysmsapi.CreateSendSmsRequest() request.Scheme = "https" request.Domain = "dysmsapi.aliyuncs.com" //域名 ---参考讲义补充! request.PhoneNumbers = phone request.SignName = "爱家租房网" request.TemplateCode = "SMS_183242785" // 生成一个随机 6 位数, 做验证码 rand.Seed(time.Now().UnixNano()) // 播种随机数种子. // 生成6位随机数. smsCode := fmt.Sprintf("%06d", rand.Int31n(1000000)) request.TemplateParam = `{"code":"` + smsCode + `"}` response, _ := client.SendSms(request)
-
根据发送结果,给前端反馈消息
// 校验图片验证码 是否正确 result := model.CheckImgCode(uuid, imgCode) if result { // 发送短信 ..... response, _ := client.SendSms(request) if response.IsSuccess() { // 发送短信验证码 成功 resp["errno"] = utils.RECODE_OK resp["errmsg"] = utils.RecodeText(utils.RECODE_OK) } else { // 发送端验证码 失败. resp["errno"] = utils.RECODE_SMSERR resp["errmsg"] = utils.RecodeText(utils.RECODE_SMSERR) } } else { // 校验失败, 发送错误信息 resp["errno"] = utils.RECODE_DATAERR resp["errmsg"] = utils.RecodeText(utils.RECODE_DATAERR) }
短信验证码存入Redis
-
使用 Redis 连接池!
// redis.Pool —— Ctrl-B 查看 redis库, 连接池属性 type Pool struct { Dial func() (Conn, error) // 连接数据库使用 。。。。 MaxIdle int // 最大空闲数 == 初始化连接数 MaxActive int // 最大存活数 > MaxIdle IdleTimeout time.Duration // 空闲超时时间 。。。。 MaxConnLifetime time.Duration // 最大生命周期 。。。。 }
-
连接池代码实现:
-- 在 model/modelFunc.go 中 创建redis连接池的 初始化函数。 // 创建全局redis 连接池 句柄 var RedisPool redis.Pool // 创建函数, 初始化Redis连接池 func InitRedis() { RedisPool = redis.Pool{ MaxIdle:20, MaxActive:50, MaxConnLifetime:60 * 5, IdleTimeout:60, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "192.168.6.108:6379") }, } } ---在 web/main.go 中,使用该 InitRedis() , 在项目启动时,自动初始化连接池!
-
修改了 CheckImgCode() , 使用连接池。
-
实现 SaveSmsCode() 函数,将数据存入 redis
// 链接 Redis --- 从链接池中获取一条链接 conn := RedisPool.Get() defer conn.Close() // 存储短信验证码到 redis 中 _, err := conn.Do("setex", phone+"_code", 60 * 3, code)
短信验证码分离成微服务
-
创建 微服务。 将 “登录”、“短信验证”、“注册” 使用 user 微服务 实现。
micro new --type srv bj38web/service/user
-
修改密码本 —— proto文件。
// 修改 Call --- SendSms。 删除 Stream、PingPong 函数。 // 删除 除 Request、Response 之外的其他 message 消息体。 // 根据传入、传出修改 Request、Response syntax = "proto3"; package go.micro.srv.user; service User { rpc SendSms(Request) returns (Response) {} } message Request { string phone = 1; string imgCode = 2; string uuid = 3; } message Response { string errno = 1; string errmsg = 2; }
-
编译 proto文件,生成 2 个新文件 xxx.micro.go 和 xxx.pb.go ,用于 grpc 远程调用!
make proto
-
移植 web/controller/user.go 中 “发送短信验证码” 代码,到 service/user/handler/user.go 中,实现微服务版的短信验证码功能。
-
在微服务项目中,初始化 Redis 连接池!service/user/main.go 中 调用 InitRedis()
实现短信验证码客户端调用
- 拷贝密码本。
- 将 service/user/proto/user/ 拷贝至 web/proto/ —— 与 getCaptcha 同级。
-
先导包,起别名。
userMicro "test_go_mod/proto/user" // 给包起别名
-
初始化客户端
microClient := userMicro.NewUserService("go.micro.srv.user", consulService.Client()) 参1: service/user/main.go 中 指定的 服务名。
-
调用远程函数, 封装调用结果程json 发送给浏览器
resp, err := microClient.SendSms(context.TODO(), &userMicro.Request{Phone:phone, ImgCode:imgCode,Uuid:uuid}) if err != nil { fmt.Println("调用远程函数 SendSms 失败:", err) return } // 发送校验结果 给 浏览器 ctx.JSON(http.StatusOK, resp)