此博客用于个人学习,来源于网上,对知识点进行一个整理。
用户搜索到自己心仪的商品,接下来就要去购买,但是购买必须先登录。所以接下来我们编写用户中心,实现用户的登录和注册功能。
用户中心的提供的服务:
这里我们暂时先实现基本的: 注册和登录功能,因为用户中心的服务其它微服务也会调用,因此这里我们做聚合。
leyou-user:父工程,包含2个子工程:
创建 module:leyou-user,采用 maven 工程的方式创建。
在 leyou-user 下,创建 module:leyou-user-interface。
pom 文件:
<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>leyou-userartifactId>
<groupId>com.leyou.usergroupId>
<version>1.0.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.leyou.usergroupId>
<artifactId>leyou-user-interfaceartifactId>
<version>1.0.0-SNAPSHOTversion>
project>
在 leyou-user 下,创建 module:leyou-user-service。
pom 文件:
<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>leyou-userartifactId>
<groupId>com.leyou.usergroupId>
<version>1.0.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.leyou.usergroupId>
<artifactId>leyou-user-serviceartifactId>
<version>1.0.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapper-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.leyou.usergroupId>
<artifactId>leyou-user-interfaceartifactId>
<version>1.0.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
启动类:
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.leyou.user.mapper")
public class LeyouUserApplication {
public static void main(String[] args) {
SpringApplication.run(LeyouUserApplication.class, args);
}
}
配置:
server:
port: 8085
spring:
application:
name: user-service
datasource:
url: jdbc:mysql://127.0.0.1:3306/leyou
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
redis:
host: 192.168.56.101
rabbitmq:
host: 192.168.56.101
virtual-host: /leyou
username: leyou
password: leyou
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 15
mybatis:
type-aliases-package: com.leyou.user.pojo
修改 leyou-gateway,添加路由规则,对 leyou-user-service 进行路由:
user-service: /user/** #用户微服务
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(32) NOT NULL COMMENT '密码,加密存储',
`phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
`created` datetime NOT NULL COMMENT '创建时间',
`salt` varchar(32) NOT NULL COMMENT '密码加密的salt值',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8 COMMENT='用户表';
因为根据用户名查询的频率较高,所以我们给用户名创建了索引。
1)实体类:
@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;// 用户名
@Length(min = 4,max = 30,message = "密码必须在4-30位之间")
@JsonIgnore //对象序列化为json字符串时,忽略该属性
private String password;// 密码
@Pattern(regexp = "^1[356789]\\d{9}$",message = "手机号不合法")
private String phone;// 电话
private Date created;// 创建时间
@JsonIgnore
private String salt;// 密码的盐值
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
为了安全考虑,这里对 password 和 salt 添加了注解 @JsonIgnore,这样在 json 序列化时,就不会把 password 和 salt 返回。
2)mapper:
public interface UserMapper extends Mapper<User> {
}
3)service:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
}
4)controller:
@Controller
public class UserController {
@Autowired
private UserService userService;
}
实现用户数据的校验,主要包括对:手机号、用户名的唯一性校验。
接口路径:GET /check/{data}/{type}。
参数说明:
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
data | 要校验的数据 | 是 | String | 无 |
type | 要校验的数据类型:1,用户名;2,手机; | 否 | Integer | 1 |
返回结果:
返回布尔类型结果:
状态码:
/**
* 校验数据是否可用
* @param data
* @param type
* @return
*/
@GetMapping("/check/{data}/{type}")
public ResponseEntity<Boolean> checkUser(@PathVariable("data")String data,@PathVariable("type")Integer type){
Boolean bool = this.userService.checkUser(data,type);
if (bool == null){
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(bool);
}
/**
* 校验数据是否可用
* @param data
* @param type
* @return
*/
public Boolean checkUser(String data, Integer type) {
User record = new User();
if (type == 1){
record.setUsername(data);
}else if (type == 2){
record.setPhone(data);
}else {
return null;
}
return this.userMapper.selectCount(record) == 0;
}
因为系统中不止注册一个地方需要短信发送,因此我们将短信发送抽取为微服务: leyou-sms-service ,凡是需要的地方都可以使用。
另外,因为短信发送 API 调用时长的不确定性,为了提高程序的响应速度,短信发送我们都将采用异步发送方式,即:
1)创建 module:leyou-sms,采用 maven 工程的方式创建。
2)pom:
<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>leyouartifactId>
<groupId>com.leyou.parentgroupId>
<version>1.0.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.leyou.smsgroupId>
<artifactId>leyou-sms-serviceartifactId>
<version>1.0.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-coreartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-dysmsapiartifactId>
<version>1.0.0version>
dependency>
dependencies>
project>
3)编写启动类:
@SpringBootApplication
public class LeyouSmsApplication {
public static void main(String[] args) {
SpringApplication.run(LeyouSmsApplication.class);
}
}
4)编写 application.yml:
server:
port: 8086
spring:
application:
name: sms-service
rabbitmq:
host: 192.168.56.101
virtual-host: /leyou
username: leyou
password: leyou
1)属性抽取:
首先把一些常量抽取到 application.yml 中:
leyou:
sms:
accessKeyId: JWffwFJIwada # 你自己的accessKeyId
accessKeySecret: aySRliswq8fe7rF9gQyy1Izz4MQ # 你自己的AccessKeySecret
signName: 乐优商城 # 签名名称
verifyCodeTemplate: SMS_133976814 # 模板名称
然后注入到属性类中:
@ConfigurationProperties(prefix = "leyou.sms")
public class SmsProperties {
String accessKeyId;
String accessKeySecret;
String signName;
String verifyCodeTemplate;
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
public String getSignName() {
return signName;
}
public void setSignName(String signName) {
this.signName = signName;
}
public String getVerifyCodeTemplate() {
return verifyCodeTemplate;
}
public void setVerifyCodeTemplate(String verifyCodeTemplate) {
this.verifyCodeTemplate = verifyCodeTemplate;
}
}
2)工具类:
将阿里提供的 demo 进行简化和抽取。封装成一个工具类:
@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;
}
}
编写消息监听器,当接收到消息后,我们发送短信。
@Component
public class SmsListener {
@Autowired
private SmsProperties smsProperties;
@Autowired
private SmsUtils smsUtils;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "LEYOU.SMS.QUEUE",durable = "true"),
exchange = @Exchange(value = "leyou.sms.exchange",ignoreDeclarationExceptions = "true",type = ExchangeTypes.TOPIC),
key = {"verifycode.sms"}
))
public void sendSms(Map<String,String> msg) throws ClientException {
if (CollectionUtils.isEmpty(msg)){
return;
}
String phone = msg.get("phone");
String code = msg.get("code");
if (StringUtils.isBlank(phone) && StringUtils.isBlank(code)){
this.smsUtils.sendSms(phone,code,this.smsProperties.getSignName(),this.smsProperties.getVerifyCodeTemplate());
}
}
}
消息体是一个 Map,里面有两个属性: