乐优商城学习笔记二十二-用户注册(一)


title: 乐优商城学习笔记二十二-用户注册(一)
date: 2019-04-23 13:23:27
tags:
- 乐优商城
- java
- springboot
categories:
- 乐优商城


0.学习目标

  • 独立创建用户中心
  • 了解面向接口开发方式
  • 实现数据校验功能
  • 实现短信发送功能
  • 实现注册功能
  • 实现根据用户名和密码查询用户功能

1.创建用户中心

用户搜索到自己心仪的商品,接下来就要去购买,但是购买必须先登录。所以接下来我们编写用户中心,实现用户的登录和注册功能。

用户中心的提供的服务:

  • 用户的注册
  • 用户登录
  • 用户个人信息管理
  • 用户地址管理
  • 用户收藏管理
  • 我的订单
  • 优惠券管理

这里我们暂时先实现基本的:注册和登录功能,其它功能大家可以自行补充完整。

因为用户中心的服务其它微服务也会调用,因此这里我们做聚合:

  • ly-user:父工程,包含2个子工程:
    • ly-user-interface:实体及接口
    • ly-user-service:业务和服务

1.1.创建父module

创建

乐优商城学习笔记二十二-用户注册(一)_第1张图片
image

位置:

乐优商城学习笔记二十二-用户注册(一)_第2张图片
image

1.2.创建ly-user-interface

创建module:

乐优商城学习笔记二十二-用户注册(一)_第3张图片
image
乐优商城学习笔记二十二-用户注册(一)_第4张图片
image

pom:



    
        ly-user
        com.leyou.service
        1.0.0-SNAPSHOT
    
    4.0.0

    com.leyou.service
    ly-user-interface
    1.0.0-SNAPSHOT

1.3.创建ly-user-service

创建module

乐优商城学习笔记二十二-用户注册(一)_第5张图片
image
乐优商城学习笔记二十二-用户注册(一)_第6张图片
image

pom



    
        ly-user
        com.leyou.service
        1.0.0-SNAPSHOT
    
    4.0.0

    com.leyou.service
    ly-user-service
    1.0.0-SNAPSHOT

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
        
        
        
            tk.mybatis
            mapper-spring-boot-starter
        
        
        
            mysql
            mysql-connector-java
        
        
            com.leyou.service
            ly-user-interface
            ${leyou.latest.version}
        
    

启动类

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

配置:

server:
  port: 8085
spring:
  application:
    name: user-service
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/heima
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
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.item.pojo

父工程ly-user的pom:



    
        leyou
        com.leyou.parent
        1.0.0-SNAPSHOT
    
    4.0.0

    com.leyou.service
    ly-user
    1.0.0-SNAPSHOT
    
        ly-user-interface
        ly-user-service
    
    pom

1.4.添加网关路由

我们修改ly-api-gateway,添加路由规则,对ly-user-service进行路由:

乐优商城学习笔记二十二-用户注册(一)_第7张图片
image

2.后台功能准备

2.1.接口文档

整个用户中心的开发,我们将模拟公司内面向接口的开发。

乐优商城学习笔记二十二-用户注册(一)_第8张图片
image

我们将根据文档直接编写后台功能,不关心页面实现。

2.2.数据结构

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='用户表';

数据结构比较简单,因为根据用户名查询的频率较高,所以我们给用户名创建了索引

2.3.基本代码

实体类

@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;// 密码的盐值
}

注意:为了安全考虑。这里对password和salt添加了注解@JsonIgnore,这样在json序列化时,就不会把password和salt返回。

mapper

public interface UserMapper extends Mapper {
}

Service

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
}

3.数据验证功能

3.1.接口说明:

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

接口路径

GET /check/{data}/{type}

参数说明:

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

返回结果:

返回布尔类型结果:

  • true:可用
  • false:不可用

状态码:

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

3.2.controller

因为有了接口,我们可以不关心页面,所有需要的东西都一清二楚:

  • 请求方式:GET
  • 请求路径:/check/{param}/{type}
  • 请求参数:param,type
  • 返回结果:true或false
/**
  * 校验数据是否可用
  * @param data
  * @param type
  * @return
  */
@GetMapping("check/{data}/{type}")
public ResponseEntity checkUserData(@PathVariable("data") String data, @PathVariable(value = "type", defaultValue="1") Integer type) {
    Boolean boo = this.userService.checkData(data, type);
    if (boo == null) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
    }
    return ResponseEntity.ok(boo);
}

3.3.Service

public Boolean checkData(String data, Integer type) {
    User record = new User();
    switch (type) {
        case 1:
            record.setUsername(data);
            break;
        case 2:
            record.setPhone(data);
            break;
        default:
            return null;
    }
    return this.userMapper.selectCount(record) == 0;
}

3.4.测试

我们在数据库插入一条假数据:

乐优商城学习笔记二十二-用户注册(一)_第9张图片
image

然后在浏览器调用接口,测试:

乐优商城学习笔记二十二-用户注册(一)_第10张图片
image
乐优商城学习笔记二十二-用户注册(一)_第11张图片
image

4.阿里大于短信服务

4.1.demo

注册页面上有短信发送的按钮,当用户点击发送短信,我们需要生成验证码,发送给用户。我们将使用阿里提供的阿里大于来实现短信发送。

4.2.创建短信微服务

因为系统中不止注册一个地方需要短信发送,因此我们将短信发送抽取为微服务:ly-sms-service,凡是需要的地方都可以使用。

另外,因为短信发送API调用时长的不确定性,为了提高程序的响应速度,短信发送我们都将采用异步发送方式,即:

  • 短信服务监听MQ消息,收到消息后发送短信。
  • 其它服务要发送短信时,通过MQ通知短信微服务。

4.2.1.创建module

乐优商城学习笔记二十二-用户注册(一)_第12张图片
image

4.2.2.pom



    
        leyou
        com.leyou.parent
        1.0.0-SNAPSHOT
    
    4.0.0

    com.leyou.service
    ly-sms-service
    1.0.0-SNAPSHOT

    
        
            com.aliyun
            aliyun-java-sdk-core
            4.1.0
        
        
            com.aliyun
            aliyun-java-sdk-dysmsapi
            1.0.0
        
        
            org.springframework.boot
            spring-boot-starter-amqp
        
                
            org.springframework.boot
            spring-boot-starter-web
        
    

4.2.3.编写启动类

@SpringBootApplication
public class LySmsService {
    public static void main(String[] args) {
        SpringApplication.run(LySmsService.class, args);
    }
}

4.2.4.编写application.yml

server:
  port: 8086
spring:
  application:
    name: sms-service
  rabbitmq:
    host: 192.168.56.101
    username: leyou
    password: leyou
    virtual-host: /leyou

4.3.编写短信工具类

4.3.1.属性抽取

我们首先把一些常量抽取到application.yml中:

ly:
  sms:
    accessKeyId: JWffwFJIwada # 你自己的accessKeyId
    accessKeySecret: aySRliswq8fe7rF9gQyy1Izz4MQ # 你自己的AccessKeySecret
    signName: 乐优商城 # 签名名称
    verifyCodeTemplate: SMS_133976814 # 模板名称

然后注入到属性类中:

@ConfigurationProperties(prefix = "ly.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;
    }
}

4.3.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;
    }
}

属性加载:

@ConfigurationProperties(prefix = "ly.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;
    }
}

4.4.编写消息监听器

接下来,编写消息监听器,当接收到消息后,我们发送短信。

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

    @Autowired
    private SmsUtils smsUtils;

    @Autowired
    private SmsProperties prop;

    @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 msg) throws Exception {
        if (msg == null || msg.size() <= 0) {
            // 放弃处理
            return;
        }
        String phone = msg.get("phone");
        String code = msg.get("code");

        if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) {
            // 放弃处理
            return;
        }
        // 发送消息
        SendSmsResponse resp = this.smsUtils.sendSms(phone, code, 
                                                     prop.getSignName(),
                                                     prop.getVerifyCodeTemplate());
        // 发送失败
      //  throw new RuntimeException();
    }
}

我们注意到,消息体是一个Map,里面有两个属性:

  • phone:电话号码
  • code:短信验证码

4.5.启动

启动项目,然后查看RabbitMQ控制台,发现交换机已经创建:

乐优商城学习笔记二十二-用户注册(一)_第13张图片
image

队列也已经创建:


乐优商城学习笔记二十二-用户注册(一)_第14张图片
image

并且绑定:

乐优商城学习笔记二十二-用户注册(一)_第15张图片
image

你可能感兴趣的:(乐优商城学习笔记二十二-用户注册(一))