乐优商城第十七天(注册与阿里云)

在estore阶段,我们已经做过注册功能,而且也是阿里云短信,所以,这里相对来说较为轻松

接下来,我们将进行注册中心的搭建

第一步,新建3个模块

聚合工程,一个父工程,两个子工程,一个是注册服务,一个是实体类

我们的service工程还是那些依赖,不同的是,我们这里多了一个mq的依赖,我们希望,消息的发送是异步的,通过短信发送的微服务发送短信

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- mybatis启动器 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- 通用Mapper启动器 -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--springmq的支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
    <groupId>com.leyou.service</groupId>
    <artifactId>leyou-user-interface</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

第二步,导入配置文件,这是我们第五个微服务,我们这边多了一个redis的配置和一个mq的配置

server:
  port: 8085
spring:
  application:
    name: user-service
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/leyou
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
  redis:
      host: 192.168.56.101
  rabbitmq:
      host: 192.168.56.101
      username: admin
      password: admin
      virtual-host: /leyou
      template:
        retry:
          enabled: true
          initial-interval: 10000ms
          max-interval: 210000ms
          multiplier: 2
      publisher-confirms: true
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1
    instance-id: ${eureka.instance.ip-address}.${server.port}
    lease-renewal-interval-in-seconds: 3
    lease-expiration-duration-in-seconds: 10

mybatis:
  type-aliases-package: com.leyou.user.service.pojo

第三步,controller层的书写

1.用户注册,肯定要有数据的校验,手机号是否存在等信息,这里,我们首先写一个数据的校验

/**
 * 校验输入的信息
 *
 * @param data
 * @param type
 * @return
 */
@GetMapping("check/{data}/{type}")
public ResponseEntity<Boolean> checkUser(
        @PathVariable(value = "data", required = true) String data,
        @PathVariable(value = "type") Integer type
) {
    Boolean flag = this.userService.checkUser(data, type);
    return ResponseEntity.status(HttpStatus.OK).body(flag);
}
public Boolean checkUser(String data, Integer type) {
    User user = new User();
    if (type==1){
        user.setUsername(data);
    }else if (type==2){
        user.setPhone(data);
    }else {
        return null;
    }
    return this.userMapper.selectCount(user)==0;
}

2.发送短信

/**
 * 发送短信
 *
 * @param phone
 * @return
 */
@PostMapping("send")
public ResponseEntity<Void> sendMessage(@RequestParam("phone") String phone) {
    //校验手机号是否正确
    String regex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$";
    if (!phone.matches(regex)) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
    }
    this.userService.sendMessage(phone);
    return ResponseEntity.status(HttpStatus.OK).build();
}

发送短信的service

/**
 * 发送短信的方法
 * @param phone
 */
public void sendMessage(String phone) {
    /*封装信息,发送消息*/
    //生成一个验证码
    String code = NumberUtils.generateCode(6);
    System.out.println(code);
    try {
        Map<String, String> map = new HashMap<>();
        map.put("code",code);
        map.put("phone",phone);
        /*rabbitmq发送消息*/
     
this.amqpTemplate.convertAndSend("ly.sms.exchange","sms.verify.code",map);
// code 放到 redis this . redisTemplate .opsForValue () .set ( KEY_PREFIX +phone ,code , 5 , TimeUnit . MINUTES ) ; } catch ( AmqpException e ) { logger .error ( " 发送短息失败, phone:{},code:{}" ,phone ,code ) ; } }

这里,我们将短信的验证码放入redis中,用消息队列异步发送短信

这里有个注意点,我们的redisTemplate用的是StringRedisTemplate

@Autowired
private StringRedisTemplate redisTemplate;

我们这里使用的是StringRedisTemplate而不是redisTemplate,因为我们要指定我们的key和value的值都是string类型的,

redisTemplate采用jdk自带的序列化手段对我们的对象进行序列化,他生成的数据时十分庞大的,所以,我们选择自己转为json。

3.用户的注册

/**
 * 用户注册的方法
 * @param code
 * @param user
 * @return
 */
@PostMapping("register")
public ResponseEntity<Void> userRegister(@RequestParam("code")String code, @Valid User user) {
    Boolean boo = this.userService.register(user,code);
    if (boo == null || !boo) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
    }
    return ResponseEntity.status(HttpStatus.CREATED).build();
}

service

/**
 * 用户注册的方法
 * @param user
 * @param code
 * @return
 */
public Boolean register(User user, String code) {
    //判断用户的数据类型是否正确
    String username = user.getUsername();
    String password = user.getPassword();
    String phone = user.getPhone();
    String regex = "^[0-9a-zA-Z_]{1,26}$";
    if (!username.matches(regex)||!password.matches(regex)) {
        return false;
    }
    //判断验证码是否正确
    String redisCode = this.redisTemplate.opsForValue().get(KEY_PREFIX + phone);
    if (redisCode==null&&code==null&&!redisCode.equals(code)){
        return false;
    }
    //对密码进行加密
    String salt = Md5Utils.generate();
    String md5Password = Md5Utils.encryptPassword(password, salt);
    user.setSalt(salt);
    user.setPassword(md5Password);
    Date createTime = new Date(System.currentTimeMillis());
    user.setCreated(createTime);
    //向数据库中添加数据
    Boolean result = this.userMapper.insertSelective(user)==1;
    //注册成功,将redis中的验证码删除
    if (result) {
        try {
            this.redisTemplate.delete(KEY_PREFIX + phone);
        } catch (Exception e) {
            logger.error("删除redis中的数据失败,key{}"+KEY_PREFIX + phone);
        }
    }
    return result;
}

这里,我们实现注册,并删除redis中的验证码。这里我们的密码采用加盐的方式进行处理的。

4.验证用户名和密码是否正确

/**
 * 校验用户名和密码是否正确
 * @param username
 * @param password
 * @return
 */
@GetMapping("query")
public ResponseEntity<User> queryUser(
        @RequestParam(value = "username",required = true)String username,
        @RequestParam(value = "password",required = true)String password
){
  User user = this.userService.queryUserByIdAndPassword(username,password);
  if (user==null){
      return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
  }
  return ResponseEntity.status(HttpStatus.OK).body(user);
}

这是我们主要的几个功能

我们为了实现服务端的数据校验,还用了一个框架hibernate Validator ,这个歌框架支持用注解的方式实现服务端的校验

引入依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

对实体类加注解进行限制

@Table(name = "tb_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Length(min = 4,max = 30,message = "用户的值只能在4-30位之间")
    private String username;// 用户名

    @JsonIgnore
    @Length(min = 4,max = 30,message = "密码的值只能在4-30位之间")
    private String password;// 密码

    @Pattern(regexp = "^1[35678]\\d{9}$", message = "手机号格式不正确")
    private String phone;// 电话

这里的@JsonIgnore并不属于这个框架,而是jackson为我们提供的对象转为json时的忽略注解

第四步,用阿里云发送短信

我们首先通过mq接受消息

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "ly.sms.queue", durable = "true"),
        exchange = @Exchange(value = "ly.sms.exchange",
                ignoreDeclarationExceptions = "true"),
        key = {"sms.verify.code"}))
public void ListenSms(Map<String,String> map){
    if (map==null||map.size()<1){
        return;
    }
    String phone = map.get("phone");
    String code = map.get("code");
    if (StringUtils.isBlank(phone)||StringUtils.isBlank("code")){
        return;
    }
    try {
        SendSmsResponse sendSmsResponse = smsUtils.sendSms(phone, code, smsProperties.getSignName(), smsProperties.getVerifyCodeTemplate());
        if ("ok".equals(sendSmsResponse.getCode())){
            //发送消息成功,结束方法
            return;
        }
    } catch (ClientException e) {
        logger.error("短信发送出现错误");
        throw new RuntimeException("短信发送错误,重试中");
    }
}

我们把阿里云需要的配置放到配置类中

@ConfigurationProperties(prefix = "ly.sms")
public class SmsProperties {

    String accessKeyId;

    String accessKeySecret;

    String signName;

    String verifyCodeTemplate;

还有阿里云发送短信的工具类

@Component
@EnableConfigurationProperties(SmsProperties.class)
public class SmsUtils {

    @Autowired
    private SmsProperties prop;

    //产品名称:云通信短信API产品,开发者无需替换
    static final String product = "Dysmsapi";
    //产品域名,开发者无需替换
    static final String domain = "dysmsapi.aliyuncs.com";

    static final Logger logger = LoggerFactory.getLogger(SmsUtils.class);

    public SendSmsResponse sendSms(String phone, String code, String signName, String template) throws ClientException {

        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");

        //初始化acsClient,暂不支持region        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou",
                prop.getAccessKeyId(), prop.getAccessKeySecret());
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);

        //组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        request.setMethod(MethodType.POST);
        //必填:待发送手机号
        request.setPhoneNumbers(phone);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName(signName);
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(template);
        //可选:模板中的变量替换JSON,如模板内容为"亲爱的${name},您的验证码为${code}",此处的值为
        request.setTemplateParam("{\"code\":\"" + code + "\"}");

        //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
        //request.setSmsUpExtendCode("90997");

        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        request.setOutId("123456");

        //hint 此处可能会抛出异常,注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);

        logger.info("发送短信状态:{}", sendSmsResponse.getCode());
        logger.info("发送短信消息:{}", sendSmsResponse.getMessage());

        return sendSmsResponse;
    }
}
这样,我们的注册功能应该就算是完成了了


另外,RedisTemplate的基本操作

  • redisTemplate.opsForValue() :操作字符串

  • redisTemplate.opsForHash() :操作hash

  • redisTemplate.opsForList():操作list

  • redisTemplate.opsForSet():操作set

  • redisTemplate.opsForZSet():操作zset

其它一些通用命令,如expire,可以通过redisTemplate.xx()来直接调用

5种结构:

  • String:等同于java中的,Map<String,String>

  • list:等同于java中的Map<String,List<String>>

  • set:等同于java中的Map<String,Set<String>>

  • sort_set:可排序的set

  • hash:等同于java中的:`Map<String,Map<String,String>>








猜你喜欢

转载自blog.csdn.net/qpc672456416/article/details/80705831