GO微服务实战 - 01项目准备/图片及短信验证码

1,454 阅读7分钟

微服务框架

浏览器访问微服务.png

  • 可以在服务发现中搭建集群,引入多个同名服务
  • 在浏览器和web服务之间引入nginx,做反向代理

项目准备

  1. 准备项目环境

    1. 创建项目目录 web、service
    2. 在 web 端 使用 MVC
    3. 创建项目常用目录: conf 配置文件、utils 工具类、bin可执行文件、test测试目录
    4. util包中导入异常处理文件error.go
    5. 导入前端资源 html/ 到 view/ 中
  2. 开发项目

    1. 开发 微服务端
    2. 开发 web 服务(客户端)
  3. 数据库/数据表准备

    1. 在 项目的 web/model/ 下 创建 model.go 文件。
    2. 参照讲义《微服务项目讲义1.md》中 2.2.2 小节,将创建表的代码添加到 model.go
    3. 使用SQL语句创建数据库search_house。修改 InitDb() 函数。同时,指定MySQL使用的时间。初始化全局连接池句柄。
    4. 将 InitDb() 在 web/main.go 中 调用!
    5. 运行 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)
}

- 将图片验证码功能移植微服务 - 微服务端

  1. 创建 微服务项目: micro new --type srv bj38web/service/getCaptcha

    创建完成,会在项目 bj38web 的 service/ 中,多出 getCaptcha 的微服务项目。

  2. 修改密码本 —— 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;
    }
    
  3. 编译 proto 文件。 —— make 命令!得到 getCaptcha.micro.go 和 getCaptcha.pb.go

  4. 修改 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)
	}
}
  1. 修改服务发现 : 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

  1. 修改 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端

  1. 拷贝密码本。 将 service 下的 proto/ 拷贝 web/ 下

  2. 在 GetImageCd() 中 导入包,起别名:

    getCaptcha "bj38web/web/proto/getCaptcha"

  3. 修改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和码值)

  • 微服务端
  1. 修改 service/proto 中 getCaptcha.proto 的 Request 消息体,填加 uuid 成员。使用 make 命令,重新生成 getCaptcha.proto 对应的文件。

    message Request {
    	string uuid = 1;
    }
    
  2. 遵循 MVC 代码组织架构,在 service/getCaptcha/ 中 创建 model 目录

  3. 创建 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 // 不需要回复助手
}
  1. 在getCaptcha.go文件的 Call() 方法中,cap.Create() 后,调用 SaveImgCode() 传参。
  • web端
  1. 修改密码本!因为 微服务端修改了 proto/,需要重新拷贝 proto/ 到web

  2. 修改 web/controller/user.go 中 Call() 方法传参。给 Request{} 初始化。

resp, err := microClient.Call(context.TODO(), &getCaptcha.Request{Uuid:uuid})
if err != nil {
   fmt.Println("未找到远程服务...")
   return
}

3. 获取短信验证码服务

注册阿里云账号

  1. 通过实名认证
  2. 开通短信验证码功能 —— 充值
  3. 申请 AccessKey

用户登录名称 yaxuanali@1579725908251216.onaliyun.com
AccessKey ID LTAI5tNXHuUUR5K9NdF29qmT
AccessKey Secret v2r9Xb591gzuEGNwXsRU8IMLVFc0M2

  1. 申请签名。国内消息 —— 签名管理
  2. 申请模板。国内消息 —— 模板管理
  3. 测试使用 OpenAPI Explorer

将短信验证码集成到项目

  1. 修改 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)
    }
    
  2. 提取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)
     }
    
  3. 封装实现 校验图片 验证码

    --- 依据 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
    }
    
  4. 根据校验结果,发送短信验证码

    result := model.CheckImgCode(uuid, imgCode)
    if result {  // 校验成功
        // 发送短信验证码
        response, _ := client.SendSms(request)
        if response.IsSuccess() {
            // 发送短信验证码 成功
        } else {
            // 发送端验证码 失败.
        }
    } else {
        // 校验失败
    }
    
  5. 发送短信验证码实现

    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)
    
  6. 根据发送结果,给前端反馈消息

    // 校验图片验证码 是否正确
    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)
    

短信验证码分离成微服务

  1. 创建 微服务。 将 “登录”、“短信验证”、“注册” 使用 user 微服务 实现。

    micro new --type srv bj38web/service/user

  2. 修改密码本 —— 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;
    }
    
  3. 编译 proto文件,生成 2 个新文件 xxx.micro.go 和 xxx.pb.go ,用于 grpc 远程调用!

    make proto
    
  4. 移植 web/controller/user.go 中 “发送短信验证码” 代码,到 service/user/handler/user.go 中,实现微服务版的短信验证码功能。

  5. 在微服务项目中,初始化 Redis 连接池!service/user/main.go 中 调用 InitRedis()

实现短信验证码客户端调用

  1. 拷贝密码本。
  • 将 service/user/proto/user/ 拷贝至 web/proto/ —— 与 getCaptcha 同级。
  1. 先导包,起别名。

    • userMicro "test_go_mod/proto/user" // 给包起别名
  2. 初始化客户端

    microClient := userMicro.NewUserService("go.micro.srv.user", consulService.Client())
    参1: service/user/main.go 中 指定的 服务名。
    
  3. 调用远程函数, 封装调用结果程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)