2 商户注册
2.1 需求分析
2.1.1 系统交互流程
商户注册交互流程如下:
商户注册的流程由商户平台应用、商户服务、SaaS平台、验证码服务四个微服务之间进行交互完成,各微服务的职 责介绍如下:
1) 商户平台应用:此应用主要为商户提供业务功能,包括:商户资质申请、员工管理、门店管理等功能。
2) 商户服务: 提供商户管理的相关服务接口,供其它微服务调用,主要为商户平台应用提供接口服务,功能包括:商户基本信息管理、资质申请、商户应用管理、渠道参数配置、商户员工信息管理、商户门店管理等。
- SaaS平台:惠民支付项目是一个SaaS平台 ,所谓SaaS平台即多个用户租用平台的业务功能,这样用户即可省去软件系统开发的成本,每个商户就是一个租户,所以又称为多租户系统。
SaaS平台提供租户管理、账号管理、权限管理、资源管理、套餐管理、系统认证授权等功业务功能。在上图商户注 册的流程中,商户注册的账号等信息需要写入SaaS平台,由SaaS平台统一管理账号,分配权限,商户统一通过SaaS平台登录惠民支付。 - 验证码服务:提供获取短信验证码、校验验证码的接口。
商户使用手机号进行注册,平台通过校验手机验证码来确认是否本人在注册。
交互流程如下:
- 前端请求商户平台应用进行注册
- 商户平台应用获取短信验证码
- 前端携带手机验证码、账号、密码等信息请求商户平台应用确认注册
- 验证码校验通过后请求商户服务新增商户
- 商户服务请求SaaS平台新增租户并初始化管理员
- SaaS平台返回创建成功给商户服务商户服务新增商户下根门店信息
- 商户服务新增商户下员工信息
- 注册成功
2.1.2 开发步骤
整个商户注册流程比较复杂,本模块采用迭代开发方式,具体开发步骤如下:
- 首先实现商户信息在商户服务注册成功(暂时不与SaaS平台交互) 商户信息只写入商户数据库,暂时不与SaaS平台交互。
- 待商户信息注册成功,资质申请通过、支付参数配置完成再与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上配置验证码的接口地址
- 登录nacos,编辑merchant-application.yaml配置:
- 配置如下参数
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