2 商户注册

2.1 需求分析

2.1.1 系统交互流程

商户注册交互流程如下:

商户注册的流程由商户平台应用、商户服务、SaaS平台、验证码服务四个微服务之间进行交互完成,各微服务的职 责介绍如下:

1) 商户平台应用:此应用主要为商户提供业务功能,包括:商户资质申请、员工管理、门店管理等功能。

2) 商户服务: 提供商户管理的相关服务接口,供其它微服务调用,主要为商户平台应用提供接口服务,功能包括:商户基本信息管理、资质申请、商户应用管理、渠道参数配置、商户员工信息管理、商户门店管理等。

  1. SaaS平台:惠民支付项目是一个SaaS平台 ,所谓SaaS平台即多个用户租用平台的业务功能,这样用户即可省去软件系统开发的成本,每个商户就是一个租户,所以又称为多租户系统。
    SaaS平台提供租户管理、账号管理、权限管理、资源管理、套餐管理、系统认证授权等功业务功能。在上图商户注 册的流程中,商户注册的账号等信息需要写入SaaS平台,由SaaS平台统一管理账号,分配权限,商户统一通过SaaS平台登录惠民支付。
  2. 验证码服务:提供获取短信验证码、校验验证码的接口。
    商户使用手机号进行注册,平台通过校验手机验证码来确认是否本人在注册。

交互流程如下:

  1. 前端请求商户平台应用进行注册
  2. 商户平台应用获取短信验证码
  3. 前端携带手机验证码、账号、密码等信息请求商户平台应用确认注册
  4. 验证码校验通过后请求商户服务新增商户
  5. 商户服务请求SaaS平台新增租户并初始化管理员
  6. SaaS平台返回创建成功给商户服务商户服务新增商户下根门店信息
  7. 商户服务新增商户下员工信息
  8. 注册成功

2.1.2 开发步骤

整个商户注册流程比较复杂,本模块采用迭代开发方式,具体开发步骤如下:

  1. 首先实现商户信息在商户服务注册成功(暂时不与SaaS平台交互) 商户信息只写入商户数据库,暂时不与SaaS平台交互。
  2. 待商户信息注册成功,资质申请通过、支付参数配置完成再与SaaS平台进行对接。

与SaaS平台交互前需要部署SaaS平台,学习SaaS暴露的接口及认证接口,

接通SaaS平台方可实现用户登录,此部分放在本章节最后实现。

2.2 部署验证码服务

系统中所有验证码相关的功能由验证码服务提供,验证码服务是一个开源的项目,接入了腾讯、阿里等短信接口, 本系统只需要和验证码服务接入即可使用腾讯、阿里等短信接口。

参考:“验证码服务使用指南.md”部署验证码服务。

2.3 获取短信验证码

根据系统整体交互流程,需要首先获取短信验证码。

2.3.1 RestTemplate技术预研

1、认识RestTemplate

验证码服务对外提供http接口,我们使用的postman和swagger-ui都属于http客户端的一种,使用它们可以调用验证码服务的接口获取验证码。现在我们需要使用Java程序模拟http客户端调用验证码服务的接口获取验证码。

RestTemplate是Spring提供的用于访问RESTful服务的客户端,RestTemplate提供了多种便捷访问远程Http服务 的方法,能够大大提高客户端的编写效率。

RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),也可以通过替换为例如Apache HttpComponents、Netty 或 OkHttp等其它HTTP客户端 ,

OkHttp的性能优越 ,本项目使用OkHttp,官网:https://square.github.io/okhttp/,github:https://github.com/square/okhttp

在huiminpay-merchant-application工程中引入依赖:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
</dependency>

在父工程已规范了okhttp的版本

在MerchantApplicationBootstrap类中添加RestTemplate初始化:

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

在test下创建测试程序如下:

使用RestTemplate获取百度的网页内容:

@Autowired
RestTemplate restTemplate;

@Test
public void getThml(){
    ResponseEntity<String> forEntity = restTemplate.getForEntity("https:www.//baidu.com", String.class);
    String body = forEntity.getBody();
    log.info("获取网页内容:【{}】",body);
}

通过测试发现可以成功获取百度的网页内容。网页内容中中文乱码解决方案:

原因:

当RestTemplate默认使用String存储body内容时默认使用ISO_8859_1字符集。

解决:

配置StringHttpMessageConverter 消息转换器,使用utf-8字符集。

修改RestTemplate的定义方法

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    List<HttpMessageConverter<?>> httpMessageConverter = restTemplate.getMessageConverters();
    httpMessageConverter.set(1,new StringHttpMessageConverter(StandardCharsets.UTF_8));
    restTemplate.setMessageConverters(httpMessageConverter);
    return restTemplate;
}
2、使用RestTemplate获取验证码

下边通过RestTemplate请求验证码服务获取验证码。

/**
 * 获取验证码内容
 */
@Test
public void testGetSmsCode() {
    String url = "http://127.0.0.1:56085/sailing/generate?effectiveTime=60&name=sms";
    String phone = "13081936214";
    //请求体
    Map<String, Object > body = new HashMap();
    body.put("mobile", phone);
    //请求头
    HttpHeaders httpHeaders = new HttpHeaders();
    //设置数据格式为json
    httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    //封装请求参数
    HttpEntity entity = new HttpEntity(body, httpHeaders);
    ResponseEntity<Map> forEntity = restTemplate.exchange(url, HttpMethod.POST, entity, Map.class);
    Map responseMap = forEntity.getBody();
    log.info("获取验证码:【{}】", responseMap);
    //取出body中的result数据
    if (responseMap != null && responseMap.get("result") != null) {
        Map resultMap = (Map) responseMap.get("result");
        String value = resultMap.get("key").toString();
        System.out.println(value);
    }
}

2.3.2 商户平台应用-获取验证码(接口①)

2.3.2.1 接口定义

在商户平台应用工程定义获取验证码接口:

1、接口交互流程如下:

1)获取手机号

2)向验证码服务请求发送验证码并得到响应

3)响应前端验证码发送结果

2、在MerchantController类中添加getSMSCode接口方法,此接口供前端调用
@GetMapping("/sms")
public String getSMSCode(@RequestParam String phone) {
    log.info("向手机号:{}发送验证码", phone);
    return smsService.sendSms(phone);
}
3、Service接口定义如下:

为了方便程序复用在 SmsService 接口中添加sendMsg方法用来获取验证码:

package com.huiminpay.merchant.service;

public interface SmsService {
    public String sendSms(String phone);
}

2.3.2.2 接口实现

1、配置验证码服务的接口地址

在nacos上配置验证码的接口地址

  1. 登录nacos,编辑merchant-application.yaml配置:
  2. 配置如下参数
sms:
  url: "http://localhost:56085/sailing"
  effectiveTime: 600
  • url:验证码服务地址
  • effectiveTime:验证码过期时间
2、编写接口实现方法

编写SmsServiceImpl实现sendMsg方法。

package com.huiminpay.merchant.service.impl;

import com.huiminpay.merchant.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
public class SmsServiceImpl implements SmsService {
    @Autowired
    RestTemplate restTemplate;

    @Value("${sms.url}")
    private String smsUrl;
    @Value("${sms.effectiveTime}")
    private String effectiveTime;

    @Override
    public String sendSms(String phone) {
        String url = smsUrl + "/generate?name=sms&effectiveTime=" + effectiveTime;//验证码过期时间600秒  10分钟
   
        log.info("调用短信微服务发送验证码:url:{}", url);    
        Map<String, Object > body = new HashMap<>();
        body.put("mobile", phone);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        //封装请求参数
        HttpEntity entity = new HttpEntity(body, httpHeaders);
        ResponseEntity<Map> forEntity = restTemplate.exchange(url, HttpMethod.POST, entity, Map.class);
        Map responseMap = forEntity.getBody();
        log.info("获取验证码:【{}】", responseMap);
        //取出body中的result数据
        if (responseMap == null || responseMap.get("result") == null) {
            throw new RuntimeException("发送验证码出错");
        }
        Map resultMap = (Map) responseMap.get("result");
        String value = resultMap.get("key").toString();
        return value;
    }
}

3、Controller实现方法如下

@GetMapping("/sms")
public String getSMSCode(@RequestParam String phone) {
    log.info("向手机号:{}发送验证码", phone);
    return smsService.sendSms(phone);
}

2.3.2.2 接口测试

使用postman测试获取验证码接口:http://localhost:57010/merchant/sms?phnotallow=17717171717