乐优商城笔记九:用户中心
完成乐优商城用户中心基本功能

用户中心主要用于管理用户的信息,包含用户的注册/登陆、用户的个人信息管理、用户收货地址管理、用户收藏管理、我的订单等。

搭建用户中心微服务

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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>

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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路由配置:

1
2
3
zuul:
routes:
user-service: /user/**

接口开发

准备工作

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

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

1
2
3
4
5
6
7
package com.leyou.user.mapper;

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

public interface UserMapper extends Mapper<User> {
}

service

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
package com.leyou.util;

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

数据验证功能

接口说明

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

接口路径

1
GET /check/{data}/{type}

参数说明

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

返回结果

返回布尔类型结果:

  • true:可用
  • false:不可用

状态码:

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

后端代码

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 校验用户数据是否已存在
*
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 校验用户数据
*
* @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

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

验证码短信发送

接口说明

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

接口路径

1
POST /code

参数说明

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

返回结果

状态码:

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

后端代码

controller

1
2
3
4
5
6
7
8
9
10
11
/**
* 发送短信验证码
*
* @param phone 手机号码
*/
@PostMapping("/code")
public ResponseEntity<Void> sendVerifyCode(String phone) {
userService.sendVerifyCode(phone);

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

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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的依赖,并配置其相关参数:

  • 依赖

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
  • application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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为盐。

接口路径

1
POST /register

参数说明

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

返回结果

状态码:

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

后端代码

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 用户注册
*
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 用户注册
*
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;// 密码的盐值
}

按用户名和密码查询

接口说明

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

接口路径

1
GET /query

参数说明

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

返回结果

用户的json格式数据

1
2
3
4
5
6
{
"id": 6572312,
"username":"test",
"phone":"13688886666",
"created": 1342432424
}

状态码:

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

后端代码

controller

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 按用户名和密码查询用户
*
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 按用户名和密码查询用户
*
* @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;
}
文章作者: imxushuai
文章链接: https://www.imxushuai.com/2002/01/01/乐优商城笔记九:用户中心/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 imxushuai
支付宝打赏
微信打赏