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

一、简介

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

流程如下:

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: 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值