乐优商城笔记九:用户中心

完成乐优商城用户中心基本功能

搭建用户中心微服务

ly-user:父工程,包含2个子工程:

  • ly-user-interface:实体及接口
  • ly-user-service:业务和服务

创建父工程

创建Module

  • GroupId:com.leyou.service
  • ArtifactId:ly-user

ly-user-interface

创建Module

  • GroupId:com.leyou.service
  • ArtifactId:ly-user-interface

ly-user-service

创建Module

  • GroupId:com.leyou.service
  • ArtifactId:ly-user-service

pom.xml

<?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>ly-user</artifactId>
        <groupId>com.leyou.service</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>ly-user-service</artifactId>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</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>
        <dependency>
            <groupId>com.leyou.service</groupId>
            <artifactId>ly-user-interface</artifactId>
            <version>${leyou.latest.version}</version>
        </dependency>
    </dependencies>
</project>

启动类

package com.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.leyou.user.mapper")
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

配置application.yml

server:
  port: 8006
spring:
  application:
    name: user-service
  datasource:
    url: jdbc:mysql://localhost:3306/leyou
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    hikari:
      maximum-pool-size: 30
      minimum-idle: 10
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9999/eureka
  instance:
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 15
mybatis:
  type-aliases-package: com.leyou.user.pojo

添加用户微服务路由

ly-gateway工程配置文件中新增user路由配置:

zuul:
  routes:
    user-service: /user/**

接口开发

准备工作

在开发接口之前,先将基本的工程架构搭建起来,包括mapperservicecontroller常量类以及实体类

实体类

package com.leyou.pojo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

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

    private String username;// 用户名

    @JsonIgnore
    private String password;// 密码

    private String phone;// 电话

    private Date created;// 创建时间

    @JsonIgnore
    private String salt;// 密码的盐值
}

mapper

package com.leyou.user.mapper;

import com.leyou.pojo.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}

service

package com.leyou.user.service;

import com.leyou.user.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
}

controller

package com.leyou.user.controller;

import com.leyou.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;

@RestController
@RequestMapping
public class UserController {

    @Autowired
    private UserService userService;

}

UserConstants

package com.leyou.util;

/**
 * 用户微服务常量类
 */
public final class UserConstants {
}

数据验证功能

接口说明

实现用户数据的校验,主要包括对:手机号、用户名的唯一性校验。

接口路径

GET /check/{data}/{type}

参数说明

参数 说明 是否必须 数据类型 默认值
data 要校验的数据 String
type 要校验的数据类型:1,用户名;2,手机; Integer 1

返回结果

返回布尔类型结果:

  • true:可用
  • false:不可用

状态码:

  • 200:校验成功
  • 400:参数有误
  • 500:服务器内部异常

后端代码

controller

    /**
     * 校验用户数据是否已存在
     *
     * @param data 数据
     * @param type 数据类型。1:用户名 2:手机
     * @return 返回true表示该信息已被使用,否则返回false
     */
    @GetMapping("/check/{data}/{type}")
    public ResponseEntity<Boolean> checkData(@PathVariable("data") String data,
                                             @PathVariable(value = "type") String type) {
        Boolean result = userService.checkUserData(data, type);
        if (result == null) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }
        return ResponseEntity.ok(result);
    }

service

    /**
     * 校验用户数据
     *
     * @param data 数据
     * @param type 数据类型。1:用户名 2:手机
     * @return 返回true表示该信息已被使用,否则返回false
     */
    public Boolean checkUserData(String data, String type) {
        User param = new User();
        switch (type) {
            case USER_DATA_USERNAME:
                param.setUsername(data);
                break;
            case USER_DATA_PHONE:
                param.setPhone(data);
                break;
            default:
                throw new LyException(LyExceptionEnum.NOT_SUPPORT_DATA_TYPE);
        }

        return userMapper.select(param).size() == 0;
    }

UserConstants

    public static final String USER_DATA_USERNAME = "1";
    public static final String USER_DATA_PHONE = "2";

验证码短信发送

接口说明

根据用户输入的手机号,生成随机验证码,长度为6位,纯数字。发送短信验证码到该手机号。

接口路径

POST /code

参数说明

参数 说明 是否必须 数据类型 默认值
phone 手机号码 String

返回结果

状态码:

  • 204:请求已接受
  • 400:参数有误
  • 500:服务器内部错误

后端代码

controller

    /**
     * 发送短信验证码
     *
     * @param phone 手机号码
     */
    @PostMapping("/code")
    public ResponseEntity<Void> sendVerifyCode(String phone) {
        userService.sendVerifyCode(phone);

        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }

service

	private static final String REDIS_PREFIX_SMS = "sms:verify:code:phone:";

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private AmqpTemplate amqpTemplate;
    /**
     * 发送短信验证码
     *
     * @param phone 手机号码
     */
    public void sendVerifyCode(String phone) {
        // 生成验证码
        String verifyCode = NumberUtils.generateCode(6);
        try {
            // 发送短信验证码
            Map<String, String> msg = new HashMap<>();
            msg.put("phoneNumber", phone);
            msg.put("code", verifyCode);
            amqpTemplate.convertAndSend(LeyouConstants.EXCHANGE_SMS, LeyouConstants.ROUTING_KEY_VERIFY_CODE_SMS, msg);

            // 保存验证码到redis
            redisTemplate.opsForValue().set(REDIS_PREFIX_SMS + phone, verifyCode, 5, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.error("发送验证码失败, phone = [{}] verifyCode = [{}]", phone, verifyCode, e);
            throw new LyException(LyExceptionEnum.SEND_VERIFY_CODE_FAILURE);
        }
    }

注意:需要引入rabbitmq以及redis的依赖,并配置其相关参数:

  • 依赖
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>
  • application.yml
spring: 
  redis:
    host: 192.168.136.103
    port: 6379
  rabbitmq:
    # 主机地址
    host: 192.168.136.103
    # 用户名、密码
    username: leyou
    password: leyou
    # 虚拟主机
    virtual-host: /leyou
    template:
      retry:
        enabled: true
        initial-interval: 10000ms
        max-interval: 60000ms
        multiplier: 2
    publisher-confirms: true

用户注册

接口说明

实现用户注册功能,需要对用户密码进行加密存储,使用MD5加密,加密过程中使用随机生成的salt为盐。

接口路径

POST /register

参数说明

参数 说明 是否必须 数据类型 默认值
username 用户名。格式为4-30位字母,数字,下划线 String
password 用户密码。格式为4-30位字母,数字,下划线 String
phone 手机号码 String
code 短信验证码 s是 String

返回结果

状态码:

  • 201:注册成功
  • 400:参数有误,注册失败
  • 500:服务器内部异常,注册失败

后端代码

controller

    /**
     * 用户注册
     *
     * @param user 用户数据
     * @param code 短信验证码
     */
    @PostMapping("/register")
    public ResponseEntity<Void> register(@Valid User user,
                                         BindingResult bindingResult,
                                         @RequestParam("code") String code) {
        // 数据校验
        if (bindingResult.hasErrors()) {// 打印错误信息
            bindingResult.getAllErrors().forEach(error -> {
                throw new LyException(LyExceptionEnum.valueOf(error.getDefaultMessage()));
            });
        }
        // 账号以及手机校验,防止非正规调用
        Boolean username_unique = checkData(user.getUsername(), UserConstants.USER_DATA_USERNAME).getBody();
        Boolean phone_unique = checkData(user.getUsername(), UserConstants.USER_DATA_PHONE).getBody();
        if (username_unique == null || !username_unique) {
            throw new LyException(LyExceptionEnum.USERNAME_EXISTED);
        }
        if (phone_unique == null || !phone_unique) {
            throw new LyException(LyExceptionEnum.PHONE_EXISTED);
        }
        // 注册
        userService.register(user, code);

        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

service

    /**
     * 用户注册
     *
     * @param user 用户数据
     * @param code 短信验证码
     * @return 是否注册成功
     */
    public void register(User user, String code) {
        user.setCreated(new Date());
        // 判断验证码
        String key = REDIS_PREFIX_SMS + user.getPhone();
        String veriyCode = redisTemplate.opsForValue().get(key);
        if (!code.equals(veriyCode)) {
            throw new LyException(LyExceptionEnum.VERIFY_CODE_NOT_EQUALS);
        }
        // 生成盐,我这里直接使用uuid作为盐
        String salt = UUID.randomUUID().toString().replace("-","");
        user.setSalt(salt);
        // 生成密码
        String password = DigestUtils.md5Hex(user.getPassword()) + salt;
        user.setPassword(password);

        // 保存用户
        if (userMapper.insert(user) != 1) {
            // 保存失败
            throw new LyException(LyExceptionEnum.REGISTER_FAILURE);
        }
        // 注册成功,清除短信验证码
        redisTemplate.delete(key);
    }

服务端数据校验

使用hibernate-validator,我的另一篇文章中有介绍到,可以参考:

文章链接:使用springmvc开发restful api

package com.leyou.pojo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Pattern;
import java.util.Date;

@Data
@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;// 电话

    private Date created;// 创建时间

    @JsonIgnore
    private String salt;// 密码的盐值
}

按用户名和密码查询

接口说明

查询功能,根据参数中的用户名和密码查询指定用户。

接口路径

GET /query

参数说明

参数 说明 是否必须 数据类型 默认值
username 用户名,格式为4~30位字母、数字、下划线 String
password 用户密码,格式为4~30位字母、数字、下划线 String

返回结果

用户的json格式数据

{
    "id": 6572312,
    "username":"test",
    "phone":"13688886666",
    "created": 1342432424
}

状态码:

  • 200:查询成功
  • 400:用户名或密码错误
  • 500:服务器内部异常,注册失败

后端代码

controller

    /**
     * 按用户名和密码查询用户
     *
     * @param username 用户名
     * @param password 密码
     * @return User 用户信息
     */
    @GetMapping("/query")
    public ResponseEntity<User> queryUserByUsernameAndPassword(@RequestParam("username") String username,
                                                               @RequestParam("password") String password) {
        return ResponseEntity.ok(userService.queryUserByUsernameAndPassword(username, password));
    }

service

    /**
     * 按用户名和密码查询用户
     *
     * @param username 用户名
     * @param password 密码
     * @return User 用户信息
     */
    public User queryUserByUsernameAndPassword(String username, String password) {
        // 使用username查询用户
        User param = new User();
        param.setUsername(username);
        User user = userMapper.selectOne(param);

        // 用户名不存在
        if (user == null) {
            throw new LyException(LyExceptionEnum.INVALID_USERNAME_OR_PASSWORD);
        }

        // 密码错误
        if (!(DigestUtils.md5Hex(password) + user.getSalt()).equals(user.getPassword())) {
            throw new LyException(LyExceptionEnum.INVALID_USERNAME_OR_PASSWORD);
        }

        return user;
    }

你可能感兴趣的:(乐优商城)