一、简介
本篇博客实现我们的注册接口。
流程如下:
1、会员服务提供接口——根据手机号码查询用户是否存在。
2、微信服务调用步骤1的接口,若手机号查询用户存在,则提示已注册;否则发送短信验证码,并将验证码存在redis中。
3、微信服务提供接口——根据手机号和验证码查询验证码是否正确。
4、会员服务注册接口调用步骤3接口校验验证码是否正确,正确的话完成注册,将数据存入mysql。
注册接口校验参数,通过则将数据存入mysql;我们使用JPA来实现数据持久化。
如此分工明确,各个服务有各自的职责。
docker安装mysql mysql版本8.0.19
需要注意的是,在我们的项目中,Entity分为DO和DTO两种,
DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。
DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
详情概念请查看浅析VO、DTO、DO、PO的概念、区别和用处
使用navicat创建数据库,每个服务都有独立的数据库,这里我们创建的是会员服务的数据库shop-member
创建用户表
CREATE TABLE `u_user` (
`user_id` INT (11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_name` VARCHAR (50) DEFAULT NULL COMMENT '用户名',
`phone` VARCHAR (11) NOT NULL COMMENT '手机号',
`email` VARCHAR (50) NOT NULL COMMENT '邮箱号',
`password` VARCHAR (64) NOT NULL COMMENT '密码',
`sex` TINYINT (1) DEFAULT '0' COMMENT '性别 0=女,1=男',
`age` TINYINT (3) DEFAULT '0' COMMENT '年龄',
`pic_url` VARCHAR (255) DEFAULT NULL COMMENT '用户头像',
`status` TINYINT (1) DEFAULT '1' COMMENT '是否可用 1=正常,2=冻结',
`create_time` TIMESTAMP NULL DEFAULT NULL COMMENT '创建时间',
`update_time` TIMESTAMP NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`user_id`),
UNIQUE KEY `phone_unique` (`phone`)
) ENGINE = INNODB AUTO_INCREMENT = 25 DEFAULT CHARSET = utf8 COMMENT = '用户表';
二、在parent项目基础上创建DTO Module——shop-api-dto
架构如下图所示:
三、实战
首先整合JPA到项目中。
shop-service-impl-member模块添加依赖
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
<scope>runtime</scope>
</dependency>
<!-- druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--jpa数据持久化-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
3.1 会员服务提供接口——根据用户手机号码查询用户信息
修改github上的配置文件member-dev.yml添加jpa和mysql配置
#服务端口号
server:
port: 8300
spring:
application:
name: liazhan-member
datasource:
druid:
# 数据库访问配置, 使用druid数据源
db-type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.98.183.103:3306/shop-member?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
# 连接池配置
initial-size: 5
min-idle: 5
max-active: 20
# 连接等待超时时间
max-wait: 30000
# 配置检测可以关闭的空闲连接间隔时间
time-between-eviction-runs-millis: 60000
##Jpa配置
jpa:
hibernate:
ddl-auto: update
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
####swagger相关配置
swagger:
base-package: com.liazhan.member.service
title: 微服务电商项目-会员服务接口
description: 会员服务
version: 1.1
terms-of-service-url: www.baidu.com
contact:
name: liazhan
email: 33421352+liazhan@users.noreply.github.com
如此JPA就整合好了,我们可以在会员服务使用JPA了。
配置文件github地址:
https://github.com/liazhan/shop-project-config/tree/eab1db53b4bff6d7e5dba322bb31b6055174e7f1
shop-service-impl-member创建dao层
package com.liazhan.member.dao.entity;
import lombok.Data;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
/**
* @version:V1.0
* @Description: 用户表
* @author: Liazhan
* @date 2020/4/21 10:58
*/
@Entity(name="u_user")
@Data
//动态insert与update,过滤null值
@DynamicInsert
@DynamicUpdate
//监听,配合注释@CreatedDate和@LastModifiedDate,当insert时会自动获取创建时间,修改时自动更新修改时间,需要在启动类添加注释@EnableJpaAuditing
@EntityListeners(AuditingEntityListener.class)
public class UserDO {
/**
* 用户id
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* 用户手机号
*/
private String phone;
/**
* 用户邮箱号
*/
private String email;
/**
* 用户密码
*/
private String password;
/**
* 用户性别
*/
private Integer sex;
/**
* 用户年龄
*/
private Integer age;
/**
* 用户头像
*/
private String picUrl;
/**
* 用户状态 1=正常,2=冻结
*/
private Integer status;
/**
* 创建时间
*/
@CreatedDate
private Date createTime;
/**
* 修改时间
*/
@LastModifiedDate
private Date updateTime;
}
package com.liazhan.member.dao;
import com.liazhan.member.dao.entity.UserDO;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @version:V1.0
* @Description: 用户dao层
* @author: Liazhan
* @date 2020/4/21 10:58
*/
public interface UserDao extends JpaRepository<UserDO,Integer> {
/**
* 根据手机号查询用户是否存在
*/
boolean existsByPhone(String phone);
}
修改会员服务入口类
package com.liazhan.member;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* @version V1.0
* @description: 会员服务入口类
* @author: Liazhan
* @date: 2020/4/8 0:13a
*/
@SpringBootApplication
@EnableFeignClients
@EnableSwagger2Doc
@EnableJpaAuditing
public class AppMember {
public static void main(String[] args) {
SpringApplication.run(AppMember.class,args);
}
}
接下来修改一下我们的公共常量类,添加状态码201
package com.liazhan.base.consts;
/**
* @version:V1.0
* @Description: 常量
* @author: Liazhan
* @date 2020/4/14 15:31
*/
public interface BaseConst {
//响应成功码
Integer HTTP_RES_CODE_200 = 200;
//响应系统错误码
Integer HTTP_RES_CODE_500 = 500;
//常用响应成功消息
String HTTP_RES_CODE_200_MSG = "success";
//用户不存在
Integer HTTP_RES_CODE_201 = 201;
}
然后创建一个正则表达式工具类,用来校验手机号码等。
package com.liazhan.core.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @version:V1.0
* @Description: 正则表达式工具类
* @author: Liazhan
* @date 2020/4/22 16:26
*/
public class RegexUtil {
/**
* 验证Email
*
* @param email
* email地址,格式:zhangsan@zuidaima.com,zhangsan@xxx.com.cn,
* xxx代表邮件服务商
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkEmail(String email) {
String regex = "\\w+@\\w+\\.[a-z]+(\\.[a-z]+)?";
return Pattern.matches(regex, email);
}
/**
* 验证身份证号码
*
* @param idCard
* 居民身份证号码15位或18位,最后一位可能是数字或字母
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkIdCard(String idCard) {
String regex = "[1-9]\\d{13,16}[a-zA-Z0-9]{1}";
return Pattern.matches(regex, idCard);
}
/**
* 验证手机号码(支持国际格式,+86135xxxx...(中国内地),+00852137xxxx...(中国香港))
*
* @param mobile
* 移动、联通、电信运营商的号码段
* <p>
* 移动的号段:134(0-8)、135、136、137、138、139、147(预计用于TD上网卡)
* 、150、151、152、157(TD专用)、158、159、187(未启用)、188(TD专用) 177 170 166
* 开头
* </p>
* <p>
* 联通的号段:130、131、132、155、156(世界风专用)、185(未启用)、186(3g)
* </p>
* <p>
* 电信的号段:133、153、180(未启用)、189
* </p>
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkMobile(String mobile) {
String regex = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$";
return Pattern.matches(regex, mobile);
}
/**
* 验证固定电话号码
*
* @param phone
* 电话号码,格式:国家(地区)电话代码 + 区号(城市代码) + 电话号码,如:+8602085588447
* <p>
* <b>国家(地区) 代码 :</b>标识电话号码的国家(地区)的标准国家(地区)代码。它包含从 0 到 9
* 的一位或多位数字, 数字之后是空格分隔的国家(地区)代码。
* </p>
* <p>
* <b>区号(城市代码):</b>这可能包含一个或多个从 0 到 9 的数字,地区或城市代码放在圆括号——
* 对不使用地区或城市代码的国家(地区),则省略该组件。
* </p>
* <p>
* <b>电话号码:</b>这包含从 0 到 9 的一个或多个数字
* </p>
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkPhone(String phone) {
String regex = "(\\+\\d+)?(\\d{3,4}\\-?)?\\d{7,8}$";
return Pattern.matches(regex, phone);
}
/**
* 验证整数(正整数和负整数)
*
* @param digit
* 一位或多位0-9之间的整数
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkDigit(String digit) {
String regex = "\\-?[1-9]\\d+";
return Pattern.matches(regex, digit);
}
/**
* 验证整数和浮点数(正负整数和正负浮点数)
*
* @param decimals
* 一位或多位0-9之间的浮点数,如:1.23,233.30
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkDecimals(String decimals) {
String regex = "\\-?[1-9]\\d+(\\.\\d+)?";
return Pattern.matches(regex, decimals);
}
/**
* 验证空白字符
*
* @param blankSpace
* 空白字符,包括:空格、\t、\n、\r、\f、\x0B
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkBlankSpace(String blankSpace) {
String regex = "\\s+";
return Pattern.matches(regex, blankSpace);
}
/**
* 验证中文
*
* @param chinese
* 中文字符
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkChinese(String chinese) {
String regex = "^[\u4E00-\u9FA5]+$";
return Pattern.matches(regex, chinese);
}
/**
* 验证日期(年月日)
*
* @param birthday
* 日期,格式:1992-09-03,或1992.09.03
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkBirthday(String birthday) {
String regex = "[1-9]{4}([-./])\\d{1,2}\\1\\d{1,2}";
return Pattern.matches(regex, birthday);
}
/**
* 验证URL地址
*
* @param url
* 格式:http://blog.csdn.net:80/xyang81/article/details/7705960? 或
* http://www.csdn.net:80
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkURL(String url) {
String regex = "(https?://(w{3}\\.)?)?\\w+\\.\\w+(\\.[a-zA-Z]+)*(:\\d{1,5})?(/\\w*)*(\\??(.+=.*)?(&.+=.*)?)?";
return Pattern.matches(regex, url);
}
/**
* <pre>
* 获取网址 URL 的一级域
* </pre>
*
* @param url
* @return
*/
public static String getDomain(String url) {
Pattern p = Pattern.compile("(?<=http://|\\.)[^.]*?\\.(com|cn|net|org|biz|info|cc|tv)",
Pattern.CASE_INSENSITIVE);
// 获取完整的域名
// Pattern
// p=Pattern.compile("[^//]*?\\.(com|cn|net|org|biz|info|cc|tv)",
// Pattern.CASE_INSENSITIVE);
Matcher matcher = p.matcher(url);
matcher.find();
return matcher.group();
}
/**
* 匹配中国邮政编码
*
* @param postcode
* 邮政编码
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkPostcode(String postcode) {
String regex = "[1-9]\\d{5}";
return Pattern.matches(regex, postcode);
}
/**
* 匹配IP地址(简单匹配,格式,如:192.168.1.1,127.0.0.1,没有匹配IP段的大小)
*
* @param ipAddress
* IPv4标准地址
* @return 验证成功返回true,验证失败返回false
*/
public static boolean checkIpAddress(String ipAddress) {
String regex = "[1-9](\\d{1,2})?\\.(0|([1-9](\\d{1,2})?))\\.(0|([1-9](\\d{1,2})?))\\.(0|([1-9](\\d{1,2})?))";
return Pattern.matches(regex, ipAddress);
}
}
修改MemberService类,将之前的测试接口删除,添加新接口
package com.liazhan.member.service;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @version V1.0
* @description: 会员服务接口
* @author: Liazhan
* @date: 2020/4/7 23:54
*/
@Api(tags = "会员服务接口")
public interface MemberService {
@ApiOperation(value = "根据手机号查询用户是否存在 code 500=接口错误,200=用户存在,201=用户不存在")
@GetMapping("/existsByPhone")
@ApiImplicitParam(paramType = "query",name = "phone",dataType = "String",required = true,value = "手机号码")
public BaseResponse<JSONObject> existsByPhone(@RequestParam(value = "phone") String phone);
}
修改MemberServiceImpl类,将之前的测试接口删除
package com.liazhan.member.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.base.BaseServiceImpl;
import com.liazhan.base.consts.BaseConst;
import com.liazhan.core.utils.RegexUtil;
import com.liazhan.member.dao.UserDao;
import com.liazhan.member.service.MemberService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
/**
* @version V1.0
* @description: 会员服务接口实现类
* @author: Liazhan
* @date: 2020/4/8 0:08
*/
@RestController
public class MemberServiceImpl extends BaseServiceImpl<JSONObject> implements MemberService {
@Autowired
private UserDao userDao;
@Override
public BaseResponse<JSONObject> existsByPhone(String phone) {
//1.校验参数
if(StringUtils.isBlank(phone)){
return getResultError("手机号码不能为空!");
}
if(!RegexUtil.checkMobile(phone)){
return getResultError("手机号码错误!");
}
//2.根据手机号查询用户是否存在
boolean isExists = userDao.existsByPhone(phone);
if(!isExists){
return getRusult(BaseConst.HTTP_RES_CODE_201,"用户不存在!",null);
}
return getResultSuccess("用户存在!");
}
}
分别启动config、eureka、member服务。
访问http://localhost:8300/swagger-ui.html进行接口测试。
ok,接口测试成功。
3.2微信服务调用步骤1的接口,若手机号查询用户存在,则提示已注册;否则发送短信验证码,并将验证码存在redis中。
首先整合redis,在shop-common-core模块添加依赖,pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shop-common</artifactId>
<groupId>com.liazhan</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop-common-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
接着创建redis工具类
package com.liazhan.core.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @version:V1.0
* @Description: Redis工具类
* @author: Liazhan
* @date 2020/4/22 15:56
*/
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 存放String类型,有过期时间
* @param key
* @param data
* @param timeout 过期时间,单位为秒
*/
public void setString(String key,String data,Long timeout){
stringRedisTemplate.opsForValue().set(key,data);
if(timeout!=null){
stringRedisTemplate.expire(key,timeout,TimeUnit.SECONDS);
}
}
/**
* 存放String类型
* @param key
* @param data
*/
public void setString(String key,String data){
stringRedisTemplate.opsForValue().set(key,data);
}
/**
* 根据key获取String类型数据
* @param key
* @return String
*/
public String getString(String key){
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 根据key删除
* @param key
* @return Boolean
*/
public Boolean delKey(String key){
return stringRedisTemplate.delete(key);
}
}
微信服务添加redis配置;添加公众号回复的文字参数,好处是可以实时修改;在github上修改微信服务配置文件weixin-dev.yml
#服务端口号
server:
port: 8200
spring:
application:
name: liazhan-weixin
#redis配置
redis:
host: 47.98.183.103
password: 123456
port: 6379
pool:
max-idle: 100
min-idle: 1
max-active: 1000
max-wait: -1
####swagger相关配置
swagger:
base-package: com.liazhan.weixin.service
title: 微服务电商项目-微信服务接口
description: 微信服务
version: 1.1
terms-of-service-url: www.baidu.com
contact:
name: liazhan
email: 33421352+liazhan@users.noreply.github.com
#wxjava公众号配置
wx:
mp:
configs:
- appId: wx342b2a9312d0084c #微信公众号的appID
secret: 33144fa93ee3940fd6d37db8ad28fc0e #微信公众号的appsecret
token: liazhan #微信公众号的Token
aesKey: 111 #微信公众号的EncodingAESKey值,在测试号中测试不需要这个值
#公众号回复消息
weixin:
mp:
regist:
code:
message: "已发送验证码,请注意查收!"
exists:
message: "该手机号码已注册!"
frequently:
message: "操作频繁,请稍后再试!"
default:
message: "您的消息我们已经收到!"
配置文件github地址:
https://github.com/liazhan/shop-project-config/tree/aa3ccd0925dd32928135df178d2124948bf38510
接着创建微信服务的常量类,存放redis的key前缀,过期时间等属性,这些不需要经常修改
package com.liazhan.weixin.consts;
/**
* @version:V1.0
* @Description: 公众号相关常量类
* @author: Liazhan
* @date 2020/4/22 17:13
*/
public interface MPConst {
//注册验证码存放的key前缀
String REGIST_CODE_KEY_PREFIX = "regist.code";
//注册验证码存放的key过期时间 10分钟
Long REGIST_CODE_KEY_TIMEOUT = 600L;
}
接着我们需要创建会员服务Feign调用类,在这之前shop-service-impl-weixin先添加会员服务api依赖
<!--member api-->
<dependency>
<artifactId>shop-service-api-member</artifactId>
<groupId>com.liazhan</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
package com.liazhan.weixin.feign;
import com.liazhan.member.service.MemberService;
import org.springframework.cloud.openfeign.FeignClient;
/**
* @version:V1.0
* @Description: 会员服务feign调用
* @author: Liazhan
* @date 2020/4/22 16:51
*/
@FeignClient(name = "liazhan-member")
public interface MemberServiceFeign extends MemberService {
}
修改微信服务入口类,添加开启feign注释
package com.liazhan.weixin;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @version V1.0
* @description: 微信服务入口类
* @author: Liazhan
* @date: 2020/4/8 0:14
*/
@SpringBootApplication
@EnableSwagger2Doc
@EnableFeignClients
public class AppWeiXin {
public static void main(String[] args) {
SpringApplication.run(AppWeiXin.class,args);
}
}
ok,准备工作都做好了,现在修改我们的公众号消息处理类
package com.liazhan.weixin.mp.handler;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.base.consts.BaseConst;
import com.liazhan.core.utils.RedisUtil;
import com.liazhan.core.utils.RegexUtil;
import com.liazhan.weixin.consts.MPConst;
import com.liazhan.weixin.feign.MemberServiceFeign;
import com.liazhan.weixin.mp.builder.TextBuilder;
import com.liazhan.weixin.sms.SendSms;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
@RefreshScope
public class MsgHandler extends AbstractHandler {
//发送验证码短信后的提示回复
@Value("${weixin.mp.regist.code.message}")
private String registCodeMsg;
//手机号已注册时的提示回复
@Value("${weixin.mp.regist.exists.message}")
private String existsMsg;
//操作频繁时的提示回复
@Value("${weixin.mp.regist.frequently.message}")
private String frequentlyMsg;
//默认的回复
@Value("${weixin.mp.default.message}")
private String defaultMsg;
@Autowired
private RedisUtil redisUtil;
@Autowired
private MemberServiceFeign memberServiceFeign;
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) {
//1.公众号接收的消息
String content = wxMessage.getContent();
//2.校验消息是否是手机号,不是手机号直接回复默认消息
if(!RegexUtil.checkMobile(content)){
return new TextBuilder().build(defaultMsg, wxMessage, weixinService);
}
//3.校验手机号是否已注册
BaseResponse<JSONObject> baseResponse = memberServiceFeign.existsByPhone(content);
//已注册
if(BaseConst.HTTP_RES_CODE_200.equals(baseResponse.getCode())){
return new TextBuilder().build(existsMsg, wxMessage, weixinService);
}
//未注册
if(BaseConst.HTTP_RES_CODE_201.equals(baseResponse.getCode())){
//判断验证码是否已存在,若已存在,则提示操作频繁,稍后重试
if(redisUtil.getString(MPConst.REGIST_CODE_KEY_PREFIX+content)!=null){
return new TextBuilder().build(frequentlyMsg, wxMessage, weixinService);
}
//4.向手机号发送短信
String code = SendSms.send(content);
//5.将验证码存入redis,key为前缀+手机号
redisUtil.setString(MPConst.REGIST_CODE_KEY_PREFIX+content,code,MPConst.REGIST_CODE_KEY_TIMEOUT);
}else{ //接口错误
return new TextBuilder().build(baseResponse.getMsg(), wxMessage, weixinService);
}
return new TextBuilder().build(registCodeMsg, wxMessage, weixinService);
}
}
依次启动config、eureka、gateway、member、weixin服务。准备好内网穿透。
发现weixin服务报错了,
***************************
APPLICATION FAILED TO START
***************************
Description:
Field redisUtil in com.liazhan.weixin.mp.handler.MsgHandler required a bean of type 'com.liazhan.core.utils.RedisUtil' that could not be found.
Action:
Consider defining a bean of type 'com.liazhan.core.utils.RedisUtil' in your configuration.
查看日志发现是找不到RedisUtil类,扫包没扫到。因为微信服务启动类是在com.liazhan.weixin包下,而RedisUtil是在com.liazhan.core.utils包下。
我们将微信服务启动类移动到com.liazhan包下,这样就扫描得到了。
ok,再次启动。
向测试公众号发送消息。
手机也接收到了验证码短信。
我们用工具查看redis,可以看到验证码已存入redis中
3.3 微信服务提供接口——根据手机号和验证码查询验证码是否正确
创建微信验证码接口
package com.liazhan.weixin.service;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @version:V1.0
* @Description: 微信服务验证码接口
* @author: Liazhan
* @date 2020/4/23 16:43
*/
@Api(tags = "微信服务验证码接口")
public interface VerificationCodeService {
@ApiOperation(value = "根据手机号码和验证码校验验证码是否正确")
@GetMapping("/checkVerificationCode")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query",name = "phone",dataType = "String",required = true,value = "手机号码"),
@ApiImplicitParam(paramType = "query",name = "verificationCode",dataType = "String",required = true,value = "验证码")
})
public BaseResponse<JSONObject> checkVerificationCode(
@RequestParam("phone") String phone,
@RequestParam("verificationCode") String verificationCode);
}
创建微信验证码接口实现类
package com.liazhan.weixin.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.base.BaseServiceImpl;
import com.liazhan.core.utils.RedisUtil;
import com.liazhan.core.utils.RegexUtil;
import com.liazhan.weixin.consts.MPConst;
import com.liazhan.weixin.service.VerificationCodeService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
/**
* @version:V1.0
* @Description: 微信服务验证码接口实现类
* @author: Liazhan
* @date 2020/4/23 16:56
*/
@RestController
public class VerificationCodeServiceImpl extends BaseServiceImpl<JSONObject> implements VerificationCodeService {
@Autowired
private RedisUtil redisUtil;
@Override
public BaseResponse<JSONObject> checkVerificationCode(String phone, String verificationCode) {
//1.校验参数
if(StringUtils.isBlank(phone)){
return getResultError("手机号码不能为空!");
}
if(!RegexUtil.checkMobile(phone)){
return getResultError("手机号码错误!");
}
if(StringUtils.isBlank(verificationCode)){
return getResultError("验证码不能为空!");
}
//2.从redis取出验证码
String codeKey = MPConst.REGIST_CODE_KEY_PREFIX + phone;
String code = redisUtil.getString(codeKey);
//3.比对验证码是否一致
if(code==null){
return getResultError("验证码可能已经过期!");
}
if(!verificationCode.equals(code)){
return getResultError("验证码错误!");
}
//4.移除redis验证码
redisUtil.delKey(codeKey);
return getResultSuccess("验证码正确!");
}
}
分别启动config、eureka、weixin服务。
访问http://localhost:8200/swagger-ui.html进行接口测试。
输入正确的手机号和验证码的话,redis的验证码会被删除。
3.4 会员服务注册接口调用步骤3接口校验验证码是否正确,正确的话完成注册,将数据存入mysql
首先我们在shop-api-dto中添加swagger依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shop-parent</artifactId>
<groupId>com.liazhan</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop-api-dto</artifactId>
<packaging>pom</packaging>
<modules>
<module>shop-api-member-dto</module>
<module>shop-api-weixin-dto</module>
</modules>
<dependencies>
<!-- swagger-spring-boot -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
</dependencies>
</project>
然后创建用户注册输入实体类UserRegistInpDTO
package com.liazhan.member.input.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
/**
* @version:V1.0
* @Description: 用户注册输入实体类
* @author: Liazhan
* @date 2020/4/21 16:23
*/
@Data
@ApiModel(value = "用户注册输入实体类")
public class UserRegistInpDTO {
@NotBlank(message = "请输入用户名称!")
@Size(min = 1,max = 11,message = "用户名称长度必须是1-11个字符")
@ApiModelProperty(value = "用户名称")
private String userName;
@NotBlank(message = "请输入手机号!")
@Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$",message = "手机号格式错误!")
@ApiModelProperty(value = "用户手机号")
private String phone;
@NotBlank(message = "请输入验证码!")
@ApiModelProperty(value = "验证码")
private String verificationCode;
@NotBlank(message = "请输入密码!")
@Size(min = 6,max = 16,message = "密码长度必须是6-16个字符")
@ApiModelProperty(value = "密码")
private String password;
@NotBlank(message = "请输入邮箱号!")
@Email(message = "邮箱格式不正确")
@ApiModelProperty(value = "用户邮箱号")
private String email;
@NotNull(message = "请选择性别!")
@Range(min = 0,max = 1)
@ApiModelProperty(value = "用户性别")
private Integer sex;
@NotNull(message = "请输入年龄!")
@ApiModelProperty(value = "用户年龄")
private Integer age;
@NotBlank(message = "请上传头像!")
@ApiModelProperty(value = "用户头像")
private String picUrl;
}
这里我们校验参数是通过在实体类添加注释来实现的,并通过全局异常捕捉来返回错误信息,下面我们在shop-common-core创建全局异常捕捉类。
package com.liazhan.core.error;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.base.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
/**
* @version:V1.0
* @Description: 全局异常捕捉类
* @author: Liazhan
* @date 2020/4/24 11:40
*/
@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends BaseServiceImpl<JSONObject> {
/**
* 接口的参数校验异常处理
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseResponse<JSONObject> methodArgumentsNotValidExceptionHanlder(MethodArgumentNotValidException e){
//从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
//返回错误信息
return getResultError(objectError.getDefaultMessage());
}
/**
* 运行时异常处理
* @param e
* @return
*/
@ExceptionHandler(RuntimeException.class)
public BaseResponse<JSONObject> runtimeExceptionHandler(RuntimeException e){
log.error("###全局捕获运行时异常###,error:{}",e);
return getResultError("系统错误!");
}
}
然后将会员服务入口类AppMember移动到com.liazhan包下,这样扫包就扫得到全局异常捕捉类了。
在编写接口之前,我们准备一个DO与DTO的转换工具类
package com.liazhan.core.utils;
import org.springframework.beans.BeanUtils;
/**
* @version:V1.0
* @Description: DTO工具类
* @author: Liazhan
* @date 2020/4/21 17:00
*/
public class DtoUtil<Dto,Do>{
/**
* dto转do
*/
public static <Do> Do dtoToDo(Object dtoEntity, Class<Do> doClass) {
// 判断dtoEntity是否为空!
if (dtoEntity == null) {
return null;
}
// 判断doClass是否为空
if (doClass == null) {
return null;
}
try {
// dto转为do
Do newInstance = doClass.newInstance();
BeanUtils.copyProperties(dtoEntity, newInstance);
return newInstance;
} catch (Exception e) {
return null;
}
}
/**
* do转dto
*/
public static <Dto> Dto doToDto(Object doEntity, Class<Dto> dtoClass) {
// 判断doEntity是否为空!
if (doEntity == null) {
return null;
}
// 判断dtoClass是否为空
if (dtoClass == null) {
return null;
}
try {
// do转为dto
Dto newInstance = dtoClass.newInstance();
BeanUtils.copyProperties(doEntity, newInstance);
return newInstance;
} catch (Exception e) {
return null;
}
}
}
在shop-service-api-member中添加dto依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shop-service-api</artifactId>
<groupId>com.liazhan</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop-service-api-member</artifactId>
<packaging>jar</packaging>
<dependencies>
<!--member dto-->
<dependency>
<artifactId>shop-api-member-dto</artifactId>
<groupId>com.liazhan</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--weixin api-->
<dependency>
<artifactId>shop-service-api-weixin</artifactId>
<groupId>com.liazhan</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
创建微信服务验证码接口Feign调用类
package com.liazhan.member.feign;
import com.liazhan.weixin.service.VerificationCodeService;
import org.springframework.cloud.openfeign.FeignClient;
/**
* @version:V1.0
* @Description: 微信服务验证码接口Feign调用
* @author: Liazhan
* @date 2020/4/24 14:42
*/
@FeignClient(name = "liazhan-weixin")
public interface WeiXinVerificationCodeServiceFeign extends VerificationCodeService {
}
在shop-service-api-member创建注册接口类
package com.liazhan.member.service;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.member.input.dto.UserRegistInpDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
/**
* @version:V1.0
* @Description: 会员注册相关接口
* @author: Liazhan
* @date 2020/4/24 11:13
*/
@Api(tags = "会员注册相关接口")
public interface MemberRegistService {
@PostMapping("/regist")
@ApiOperation(value = "会员注册接口")
BaseResponse<JSONObject> regist(@RequestBody @Valid UserRegistInpDTO userInpDTO);
}
在shop-service-impl-member创建注册接口实现类
package com.liazhan.member.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.base.BaseServiceImpl;
import com.liazhan.base.consts.BaseConst;
import com.liazhan.core.utils.DtoUtil;
import com.liazhan.member.dao.UserDao;
import com.liazhan.member.dao.entity.UserDO;
import com.liazhan.member.feign.WeiXinVerificationCodeServiceFeign;
import com.liazhan.member.input.dto.UserRegistInpDTO;
import com.liazhan.member.service.MemberRegistService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Date;
/**
* @version:V1.0
* @Description: 会员注册相关接口实现类
* @author: Liazhan
* @date 2020/4/24 11:18
*/
@RestController
public class MemberRegistServiceImpl extends BaseServiceImpl<JSONObject> implements MemberRegistService {
@Autowired
private WeiXinVerificationCodeServiceFeign weiXinVerificationCodeServiceFeign;
@Autowired
private UserDao userDao;
@Override
public BaseResponse<JSONObject> regist(@Valid UserRegistInpDTO userInpDTO) {
//1.校验验证码是否正确
BaseResponse<JSONObject> checkResponse = weiXinVerificationCodeServiceFeign.checkVerificationCode(userInpDTO.getPhone(), userInpDTO.getVerificationCode());
if(!BaseConst.HTTP_RES_CODE_200.equals(checkResponse.getCode())){
return getResultError(checkResponse.getMsg());
}
//2.密码用md5加密
String encodePassword = DigestUtils.md5DigestAsHex(userInpDTO.getPassword().getBytes());
userInpDTO.setPassword(encodePassword);
//3.DTO转do
UserDO userDO = DtoUtil.dtoToDo(userInpDTO, UserDO.class);
//4.保存到数据库
UserDO save = userDao.save(userDO);
return save.getUserId()==null?getResultError("注册失败!"):getResultSuccess("注册成功!");
}
}
依次启动config、eureka、gateway、weixin、member服务。
发现member服务报错了
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'liazhan-weixin.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
该错误原因是springboot2.0以后,默认不允许同一个服务中接口中存在多个feign实体bean,即@FeignClient(“相同服务名”)会报错。
而我们的会员服务中有两个feign调用类,它们调用的都是微信服务,name指向liazhan-weixin。但是我们为了方便管理,需要划分成多个feign调用类。
这时候我们只要在bootstrap.yml加上一个配置就可以解决了。
bootstrap.yml如下
spring:
cloud:
config:
uri: http://localhost:8000 # 配置中心的具体地址,即 config-server
name: member,common # 对应 {application} 部分
profile: dev # 对应 {profile} 部分
label: master # 对应 {label} 部分,即 Git 的分支。如果配置中心使用的是本地存储,则该参数无用
#解决不允许同一个服务中接口中存在多个feign实体bean问题
main:
allow-bean-definition-overriding: true
该配置不能在配置中心配置,只能在bootsrap.yml配置。
然后进行测试
执行成功后在数据库也看到了数据,ok,测试通过,大功告成。
github项目地址https://github.com/liazhan/shop-project/tree/ab59f98c3f73c2e476fa23b12602e8a3e8349758
版本号为ab59f98c3f73c2e476fa23b12602e8a3e8349758