ly-user:父工程,包含2个子工程:
com.leyou.service
ly-user
com.leyou.service
ly-user-interface
com.leyou.service
ly-user-service
<?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);
}
}
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/**
在开发接口之前,先将基本的工程架构搭建起来,包括mapper
,service
,controller
,常量类
以及实体类
。
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;// 密码的盐值
}
package com.leyou.user.mapper;
import com.leyou.pojo.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}
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;
}
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;
}
package com.leyou.util;
/**
* 用户微服务常量类
*/
public final class UserConstants {
}
实现用户数据的校验,主要包括对:手机号、用户名的唯一性校验。
GET /check/{data}/{type}
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
data | 要校验的数据 | 是 | String | 无 |
type | 要校验的数据类型:1,用户名;2,手机; | 否 | Integer | 1 |
返回布尔类型结果:
状态码:
/**
* 校验用户数据是否已存在
*
* @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);
}
/**
* 校验用户数据
*
* @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;
}
public static final String USER_DATA_USERNAME = "1";
public static final String USER_DATA_PHONE = "2";
根据用户输入的手机号,生成随机验证码,长度为6位,纯数字。发送短信验证码到该手机号。
POST /code
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
phone | 手机号码 | 是 | String | 无 |
无
状态码:
/**
* 发送短信验证码
*
* @param phone 手机号码
*/
@PostMapping("/code")
public ResponseEntity<Void> sendVerifyCode(String phone) {
userService.sendVerifyCode(phone);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
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>
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 | 无 |
无
状态码:
/**
* 用户注册
*
* @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();
}
/**
* 用户注册
*
* @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
}
状态码:
/**
* 按用户名和密码查询用户
*
* @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));
}
/**
* 按用户名和密码查询用户
*
* @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;
}