一、简介

本篇博客实现我们的注册接口。

流程如下:

1、会员服务提供接口——根据手机号码查询用户是否存在。

2、微信服务调用步骤1的接口,若手机号查询用户存在,则提示已注册;否则发送短信验证码,并将验证码存在redis中。

3、微信服务提供接口——根据手机号和验证码查询验证码是否正确。

4、会员服务注册接口调用步骤3接口校验验证码是否正确,正确的话完成注册,将数据存入mysql。

注册接口校验参数,通过则将数据存入mysql;我们使用JPA来实现数据持久化。

如此分工明确,各个服务有各自的职责。

docker安装redis

docker安装mysql           mysql版本8.0.19

SpringBoot2数据持久化之Jpa

需要注意的是,在我们的项目中,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: 8300spring:application:name: liazhan-memberdatasource:druid:# 数据库访问配置, 使用druid数据源db-type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://47.98.183.103:3306/shop-member?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: 123456# 连接池配置initial-size: 5min-idle: 5max-active: 20# 连接等待超时时间max-wait: 30000# 配置检测可以关闭的空闲连接间隔时间time-between-eviction-runs-millis: 60000
##Jpa配置jpa:hibernate:ddl-auto: updatedatabase-platform: org.hibernate.dialect.MySQL5InnoDBDialectshow-sql: true####swagger相关配置
swagger:base-package: com.liazhan.member.servicetitle: 微服务电商项目-会员服务接口description: 会员服务version: 1.1terms-of-service-url: www.baidu.comcontact:name: liazhanemail: 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;/*** 创建时间*/@CreatedDateprivate Date createTime;/*** 修改时间*/@LastModifiedDateprivate 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 {@Autowiredprivate UserDao userDao;@Overridepublic 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 {@Autowiredprivate 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: 8200spring:application:name: liazhan-weixin#redis配置redis:host: 47.98.183.103password: 123456port: 6379pool:max-idle: 100min-idle: 1max-active: 1000max-wait: -1####swagger相关配置
swagger:base-package: com.liazhan.weixin.servicetitle: 微服务电商项目-微信服务接口description: 微信服务version: 1.1terms-of-service-url: www.baidu.comcontact:name: liazhanemail: 33421352+liazhan@users.noreply.github.com#wxjava公众号配置
wx:mp:configs:- appId: wx342b2a9312d0084c  #微信公众号的appIDsecret: 33144fa93ee3940fd6d37db8ad28fc0e  #微信公众号的appsecrettoken: liazhan  #微信公众号的TokenaesKey: 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;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate MemberServiceFeign memberServiceFeign;@Overridepublic 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 {@Autowiredprivate RedisUtil redisUtil;@Overridepublic 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转为doDo 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转为dtoDto 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 {@Autowiredprivate WeiXinVerificationCodeServiceFeign weiXinVerificationCodeServiceFeign;@Autowiredprivate UserDao userDao;@Overridepublic 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转doUserDO 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-servername: 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

微服务电商实战(九)注册接口(dto+jpa+mysql+redis)相关推荐

  1. 通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权-百度版...

    目录: 一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr实现一个简单的基于.net的微服务电商系统(二)--通讯框架讲解 三.通过Dapr实现一个简单的基于.net的微服务 ...

  2. 通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权...

    Oauth2授权,熟悉微信开发的同学对这个东西应该不陌生吧.当我们的应用系统需要集成第三方授权时一般都会做oauth集成,今天就来看看在Dapr的语境下我们如何仅通过配置无需修改应用程序的方式让第三方 ...

  3. 微服务电商实战(十一)搭建vue项目对接注册登陆接口,解决跨域问题,使用七牛云实现头像上传

    一.简介 node.js版本:v12.16.3 node.js下载 vue官网教程 iview介绍 上传头像接口,图片存储在七牛云中,注册并实名认证之后可以免费使用 以下会描述使用vue搭建项目框架的 ...

  4. 秒杀全网!SpringCloud微服务电商实战项目(全套源码+视频+文档)

    写文章很久了,听到粉丝问的最多的问题就是:有没有新的完整的项目,因为现在很多流传的项目都太老了,实战意义不是很强.很多程序员每项技术单独拿出来有可能很厉害,例如:springcloud.springb ...

  5. 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式...

    目录: 一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr实现一个简单的基于.net的微服务电商系统(二)--通讯框架讲解 三.通过Dapr实现一个简单的基于.net的微服务 ...

  6. 通过Dapr实现一个简单的基于.net的微服务电商系统(十八)——服务保护之多级缓存...

    很久没有更新dapr系列了.今天带来的是一个小的组件集成,通过多级缓存框架来实现对服务的缓存保护,依旧是一个简易的演示以及对其设计原理思路的讲解,欢迎大家转发留言和star 目录: 一.通过Dapr实 ...

  7. 通过Dapr实现一个简单的基于.net的微服务电商系统(十七)——服务保护之动态配置与热重载...

    在上一篇文章里,我们通过注入sentinel component到apigateway实现了对下游服务的保护,不过受限于目前变更component需要人工的重新注入配置以及重启应用更新componen ...

  8. 通过Dapr实现一个简单的基于.net的微服务电商系统(十六)——dapr+sentinel中间件实现服务保护...

    dapr目前更新到了1.2版本,在之前4月份的时候来自阿里的开发工程师发起了一个dapr集成Alibaba Sentinel的提案,很快被社区加入到了1.2的里程碑中并且在1.2 release 相关 ...

  9. 通过Dapr实现一个简单的基于.net的微服务电商系统(十二)——istio+dapr构建多运行时服务网格...

    多运行时是一个非常新的概念.在 2020 年,Bilgin Ibryam 提出了 Multi-Runtime(多运行时)的理念,对基于 Sidecar 模式的各种产品形态进行了实践总结和理论升华.那到 ...

最新文章

  1. php 获取class id,CSS Class 與 CSS ID
  2. 使用vue-amap
  3. POJ2114-Boatherds-树分治
  4. C++基础知识(一)—— C++程序结构
  5. 《scikit-learn》数据预处理与特征工程(二)数值转换
  6. mongodb for java_【MongoDB for Java】Java操作MongoDB
  7. HandlerInterceptor 处理器拦截器的用法
  8. 打印html文件都是空白页,我打印时的额外空白页面(IE中除外) – 是我的打印css吗?...
  9. easyui-treegrid的案例
  10. `ECS弹性计算服务
  11. ArrayList Vector
  12. 卡巴斯基最新激活码、授权文件,可用卡巴斯基
  13. Add Juniper SRX Cluster into JunOS Space 16.1 Security Director
  14. 云存储安全,主要面临哪些问题
  15. Servlet和Servlet容器概念
  16. Arm电脑出现【GLFWError】WGL: The driver does not appear to support OpenGL问题解决
  17. Redis持久化、备份:RDB和AOF
  18. “网上下单”生活神技能,寄快递在线下单、查快递实时物流信息
  19. 《人生的智慧》- 卡耐基
  20. 【HTML】颜色和选择器

热门文章

  1. 易百教程——JavaFX教程
  2. 微信小程序跳转到任意网页【非webview】
  3. java 获取两个List集合的交集
  4. Pregel单源最短路径问题
  5. rs多个设备同时传输_华为账号真的能同时登陆多个设备吗?华为官方的介绍来了...
  6. linux trim命令,linux shell trim()
  7. kettle spoon 数据同步
  8. 最差的算法工程师能差到什么程度?
  9. LIN通信协议-状态管理
  10. layui添加复选框_layui怎么设置复选框