1.用户登录

1.1阿里云短信

1.1.1初体验

  • 官方网站:https://www.aliyun.com/product/sms?spm=5176.19720258.J_8058803260.611.48192c4abPvXEp
  • 阿里云短信-申请
    • 注册阿里云账号
    • 开通短信服务
    • 申请签名
    • 配置模板
    • 示例代码测试
      代码测试
      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
      package cn.itheima.test;

      import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
      import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
      import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
      import com.aliyun.teaopenapi.models.Config;


      public class Sample {

      public static void main(String[] args_) throws Exception {

      //配置阿里云
      Config config = new Config()
      //AccessKey ID
      .setAccessKeyId("ID")
      //AccessKey Secret
      .setAccessKeySecret("Key");
      //访问域名
      config.endpoint = "dysmsapi.aliyuncs.com";

      com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);

      SendSmsRequest sendSmsRequest = new SendSmsRequest()
      .setSignName("ABC商城")
      .setTemplateCode("SMS_467595081")
      .setTemplateParam("{\"code\":\"1234\"}")
      .setPhoneNumbers("手机");

      // 复制代码运行请自行打印 API 的返回值
      SendSmsResponse response = client.sendSms(sendSmsRequest);
      SendSmsResponseBody body = response.getBody();
      }
      }


代码模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args_) throws Exception {

//1、创建配置对象
Config config = new Config()
.setAccessKeyId("")
.setAccessKeySecret("")
.setEndpoint(“dysmsapi.aliyuncs.com”);
//2、创建请求客户端对象
Client client = new Client(config);
//3、设置短信参数
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers("手机号码")
.setSignName("签名")
.setTemplateCode("模板")
.setTemplateParam("模板参数");
//4、发送短信
client.sendSms(sendSmsRequest);}

1.1.2组件抽取

  • 企业开发中,往往将常见工具类封装抽取,以简洁便利的方式供其他工程模块使用。而SpringBoot的自动装配机制可以方便的实现组件抽取。SpringBoot执行流程如下:
    1. 扫描依赖模块中META-INF/spring.factories
    2. 执行装配类中方法
    3. 对象存入容器中
    4. 核心工程注入对象,调用方法使用
验证码的抽取
  • tanhua-autoconfig
    1. 在 tanhua-autoconfig 定义模板对象SmsTemplate
      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
      package com.tanhua.autoconfig.template;

      import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
      import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
      import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
      import com.aliyun.teaopenapi.models.Config;

      public class SmsTemplate {
      public void sendSms(String mobile,String code){
      try {
      //配置阿里云
      Config config = new Config()
      //AccessKey ID
      .setAccessKeyId("id")
      //AccessKey Secret
      .setAccessKeySecret("secret");
      //访问域名
      config.endpoint = "dysmsapi.aliyuncs.com";

      com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);

      SendSmsRequest sendSmsRequest = new SendSmsRequest()
      .setSignName("ABC商城")
      .setTemplateCode("SMS_467595081")
      .setTemplateParam("{\"code\":\""+code+"\"}")
      .setPhoneNumbers(mobile);

      // 复制代码运行请自行打印 API 的返回值
      SendSmsResponse response = client.sendSms(sendSmsRequest);
      SendSmsResponseBody body = response.getBody();
      }catch (Exception e){
      e.printStackTrace();
      }
      }
      }
    2. 在 tanhua-autoconfig 定义配置类TanhuaAutoConfigration
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      package com.tanhua.autoconfig;

      import com.tanhua.server.SmsTemplate;
      import org.springframework.context.annotation.Bean;

      public class TanhuaAutoConfigration {

      @Bean
      public SmsTemplate smsTemplate(){
      return new SmsTemplate();
      }
      }
    3. 在 tanhua-autoconfig 中添加装配文件 META-INF/spring.factories
      1
      2
      org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.tanhua.autoconfig.TanhuaAutoConfiguration
  • tanhua-app-server
    1. 添加tanhua-autoconfig工程的依赖
    2. 添加启动类
    3. 在yaml文件中添加访问名称
    4. 创建SmsTemplateTest进行测试
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package cn.itheima.test;
      import com.tanhua.server.AppServerApplication;
      import com.tanhua.server.SmsTemplate;
      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.test.context.junit4.SpringRunner;
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = AppServerApplication.class)
      public class SmsTemplateTest {

      //注入
      @Autowired
      private SmsTemplate smsTemplate;

      //测试
      @Test
      public void testSendSms(){
      smsTemplate.sendSms("15018398130","4567");
      }
      }

改进代码(消息以硬编码形式写入)
  1. tanhua-app-server工程加入短信配置
    1
    2
    3
    4
    5
    6
    tanhua:
    sms:
    signName: 物流云商
    templateCode: SMS_106590012
    accessKey: key
    secret: secret
  2. 在tanhua-autoconfig创建配置信息类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.tanhua.autoconfig.properties;

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    @Data
    @ConfigurationProperties(prefix = "tanhua.sms")
    public class Smsproperties {
    private String signName;
    private String templateCode;
    private String accessKey;
    private String secret;
    }

  3. 修改SmsTemplate方法
    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
    package com.tanhua.autoconfig.template;

    import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
    import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
    import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
    import com.aliyun.teaopenapi.models.Config;
    import com.tanhua.autoconfig.properties.Smsproperties;

    public class SmsTemplate {

    private Smsproperties properties;
    private SmsTemplate(Smsproperties properties){
    this.properties = properties;
    }

    public void sendSms(String mobile,String code){

    try {
    //配置阿里云
    Config config = new Config()
    //AccessKey ID
    .setAccessKeyId(properties.getAccessKey())
    //AccessKey Secret
    .setAccessKeySecret(properties.getSecret());
    //访问域名
    config.endpoint = "dysmsapi.aliyuncs.com";

    com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);

    SendSmsRequest sendSmsRequest = new SendSmsRequest()
    .setSignName(properties.getSignName())
    .setTemplateCode(properties.getTemplateCode())
    .setTemplateParam("{\"code\":\""+code+"\"}")
    .setPhoneNumbers(mobile);

    // 复制代码运行请自行打印 API 的返回值
    SendSmsResponse response = client.sendSms(sendSmsRequest);
    SendSmsResponseBody body = response.getBody();
    }catch (Exception e){
    e.printStackTrace();
    }
    }
    }

  4. 修改TanhuaAutoConfiguration方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.tanhua.autoconfig;

    import com.tanhua.autoconfig.properties.Smsproperties;
    import com.tanhua.autoconfig.template.SmsTemplate;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;

    @EnableConfigurationProperties({
    Smsproperties.class
    })
    public class TanhuaAutoConfiguration {

    @Bean
    public SmsTemplate smsTemplate(Smsproperties properties){
    return new SmsTemplate(properties);
    }
    }

1.2获取验证码

步骤概括
  • 因为验证码直接用map来接值,所以不用实体类
  1. 定义LoginController
  2. 实现UserService —-直接调用自动配置类里面的API方法
  3. 配置yaml文件

1.2.1流程分析

  • 客户端发送请求
  • 服务端调用第三方组件发送验证码
  • 验证码发送成功,存入redis
  • 响应客户端,客户端跳转到输入验证码页面
    code

1.2.2接口定义

1
2
3
4
接口路径:/user/login
请求方式:Post
body参数:phone
响应结果:null
  • LoginController —- APP(消费者)
    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
    package com.tanhua.Controller;
    import com.tanhua.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import java.util.Map;

    @RestController
    @RequestMapping("/user")
    public class LoginController {

    @Autowired
    private UserService userService;

    /**
    * 获取登录验证码
    * 请求参数:phone (Map)
    * 响应:void
    */
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody Map map){
    String phone = String.valueOf(map.get("phone"));
    userService.sendMsg(phone);
    return ResponseEntity.ok(null);//正常返回状态码200
    }
    }

    1.2.3接口实现

  • 配置yaml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    server:
    port: 18080
    spring:
    application:
    name: tanhua-app-server
    redis:
    port: 6379
    host: 192.168.136.160
    cloud:
    nacos:
    discovery:
    server-addr: 192.168.136.160:8848

    tanhua:
    sms:
    signName: ABC商城
    templateCode: SMS_467595081
    accessKey: key
    secret: secret
  • UserService —- APP(消费者)
    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.tanhua.service;
    import com.tanhua.SmsTemplate;
    import org.apache.commons.lang.RandomStringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import java.time.Duration;

    @Service
    public class UserService {

    @Autowired
    private SmsTemplate smsTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
    * 发送短信验证码
    * @param phone
    */
    public void sendMsg(String phone){
    //1.随机生成6位数字
    String code = RandomStringUtils.randomNumeric(6);
    //2、调用template对象,发送手机短信
    smsTemplate.sendSms(phone,code);
    //3、将验证码存入到redis
    redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
    }
    }

    1.2.4测试结果

    code

1.3验证码校验

步骤概括
  1. 服务提供者
    • 创建实体类 —-model模块
    • 创建mapper接口 —-dubbo-db模块
    • 配置API —-dubbo-interface模块
    • 实现API —-dubbo-db模块
    • 创建启动类
    • 配置yaml
    • 单元测试
  2. 定义LoginController —-app模块
  3. 完善UserService —-app模块

1.3.1 JWT

JWT介绍
  • JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。
    • JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C
    • A由JWT头部信息header加密得到
    • B由JWT用到的身份验证信息json数据加密得到
    • C由A和B加密得到,是校验部分
  • 鉴权流程
    code
JWT入门案例
  1. 导入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    </dependency>
  2. 编写测试用例
  • testCreateToken
    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
    49
    50
    51
    52
    53
    54
    package cn.itheima.test;

    import io.jsonwebtoken.*;
    import org.junit.jupiter.api.Test;

    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;

    public class CreateTokenTest {

    @Test
    public void testCreateToken() {
    //生成token
    //1、准备数据
    Map map = new HashMap();
    map.put("id",1);
    map.put("mobile","13800138000");
    //2、使用JWT的工具类生成token
    long now = System.currentTimeMillis();
    String token = Jwts.builder()
    .signWith(SignatureAlgorithm.HS512, "itcast") //指定加密算法
    .setClaims(map) //写入数据
    .setExpiration(new Date(now + 30000)) //失效时间
    .compact();
    System.out.println(token);
    }

    //解析token

    /**
    * SignatureException : token不合法
    * ExpiredJwtException:token已过期
    */
    @Test
    public void testParseToken() {
    String token = "eyJhbGciOiJIUzUxMiJ9.eyJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE3MTY3ODk4NDV9.97ZmACuCs9JIsihHHvmWigRTKWRlCxkEyQHYIuvTzDW-zPj5Md9KUsJZvAcIYl2LSVonyie2D66cv_IUB0HH6Q";
    try {
    Claims claims = Jwts.parser()
    .setSigningKey("itcast")
    .parseClaimsJws(token)
    .getBody();
    Object id = claims.get("id");
    Object mobile = claims.get("mobile");
    System.out.println(id + "--" + mobile);
    }catch (ExpiredJwtException e) {
    System.out.println("token已过期");
    }catch (SignatureException e) {
    System.out.println("token不合法");
    }

    }
    }

1.3.2需求分析

  • 如果用户存在直接进入主页,不存在则进入注册页面
  • tb-user表
    code
  • 业务流程
    code

    1.3.3Dubbo服务提供者开发

  1. tanhua-model配置实体类 —-model模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.tanhua.model.domain;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.io.Serializable;
    import java.util.Date;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {
    private Long id;
    private String mobile;
    private String password;
    private Date created;
    private Date updated;
    }

  2. 创建数据库层Mapper接口 —-dubbo-db模块
    1
    2
    3
    4
    5
    package com.tanhua.dubbo.mappers;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    public interface UserMapper extends BaseMapper<User> {
    }

  3. 配置API接口和实现类
    • dubbo-interface模块
      1
      2
      3
      4
      5
      6
      7
      8
      package com.tanhua.dubbo.api;
      import com.tanhua.dubbo.domain.User;
      public interface UserApi {

      //根据手机查询用户
      User findByMobile(String mobile);
      }

    • dubbo-db模块
      1
      2
      3
      4
      5
      6
      7
      @Override
      public User findByMobile(String mobile) {
      QueryWrapper<User> queryWrapper = new QueryWrapper<>();
      queryWrapper.eq("mobile",mobile);
      return userMapper.selectOne(queryWrapper);
      }

  4. 创建引导类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.tanhua.dubbo.api;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.tanhua.dubbo.mapper.UserMapper;
    import com.tanhua.dubbo.domain.User;
    import org.apache.dubbo.config.annotation.DubboService;
    import org.springframework.beans.factory.annotation.Autowired;

    @DubboService
    public class UserApiImpl implements UserApi{

    @Autowired
    private UserMapper userMapper;

    //根据手机号码查询
    @Override
    public User findByMobile(String mobile) {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("mobile",mobile);
    return userMapper.selectOne(queryWrapper);
    }
    }

  5. 添加application.yaml配置
    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
    server:
    port: 18081
    spring:
    application:
    name: tanhua-dubbo-db
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/tanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
    username: root
    password: 123456
    cloud:
    nacos:
    discovery:
    server-addr: 192.168.136.160:8848

    dubbo:
    protocol:
    name: dubbo
    port: 20881
    registry:
    address: spring-cloud://localhost
    scan:
    base-packages: com.tanhua.dubbo.api #dubbo中包扫描

    mybatis-plus:
    global-config:
    db-config:
    table-prefix: tb_ # 表名前缀
    id-type: auto # id策略为自增长
  6. 单元测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package cn.itheima.test;
    import com.tanhua.AppServerApplication;
    import com.tanhua.dubbo.api.UserApi;
    import com.tanhua.dubbo.domain.User;
    import org.apache.dubbo.config.annotation.DubboReference;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = AppServerApplication.class)
    public class UserApiTest {

    @DubboReference
    private UserApi userApi;

    @Test
    public void testFindByMobile(){
    User user = userApi.findByMobile("13800138000");
    System.out.println(user);
    }
    }

1.3.4接口定义

1
2
3
4
5
6
7
8
接口路径:/user/loginVerification
请求方式:POST
body参数:phone,verificationCode
响应结果:
{
"token":"xxx",
"isNew":true //是否为新用户
}
  • LoginController
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 检验登录
    */
    @PostMapping("/loginVerification")
    public ResponseEntity loginVerification(@RequestBody Map map) {
    //1、调用map集合获取请求参数
    String phone = String.valueOf(map.get("phone"));
    String code = String.valueOf(map.get("verificationCode"));
    //2、调用userService完成用户登录
    Map retMap = userService.loginVerification(phone,code);
    //3、构造返回
    return ResponseEntity.ok(retMap);
    }

1.3.5接口实现

  • 实体类大量属性重复—-把公共的属性提取出来,然后继承
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.tanhua.dubbo.domain;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends BasePojo{
    private Long id;
    private String mobile;
    private String password;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.tanhua.dubbo.domain;

    import com.baomidou.mybatisplus.annotation.FieldFill;
    import com.baomidou.mybatisplus.annotation.TableField;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    @Data
    public abstract class BasePojo implements Serializable {

    @TableField(fill = FieldFill.INSERT) //自动填充
    private Date created;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updated;

    }
  • 保存或者更新数据库,频繁手动设置时间—-一次性使用@TableField配置自动填充属性字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Data
    public abstract class BasePojo implements Serializable {

    @TableField(fill = FieldFill.INSERT) //自动填充
    private Date created;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updated;

    }
    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.tanhua.dubbo.handler;
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    import java.util.Date;

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
    Object created = getFieldValByName("created", metaObject);
    if (null == created) {
    //字段为空,可以进行填充
    setFieldValByName("created", new Date(), metaObject);
    }

    Object updated = getFieldValByName("updated", metaObject);
    if (null == updated) {
    //字段为空,可以进行填充
    setFieldValByName("updated", new Date(), metaObject);
    }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
    //更新数据时,直接更新字段
    setFieldValByName("updated", new Date(), metaObject);
    }
    }
  • JWt工具类

    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
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.tanhua.commons.utils;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.apache.commons.lang3.StringUtils;

    import java.util.Date;
    import java.util.Map;

    public class JwtUtils {

    // TOKEN的有效期1小时(S)
    private static final int TOKEN_TIME_OUT = 3_600;

    // 加密KEY
    private static final String TOKEN_SECRET = "itcast";


    // 生成Token
    public static String getToken(Map params){
    long currentTime = System.currentTimeMillis();
    return Jwts.builder()
    .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式
    .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
    .addClaims(params)
    .compact();
    }


    /**
    * 获取Token中的claims信息
    */
    private static Claims getClaims(String token) {
    return Jwts.parser()
    .setSigningKey(TOKEN_SECRET)
    .parseClaimsJws(token).getBody();
    }


    /**
    * 是否有效 true-有效,false-失效
    */
    public static boolean verifyToken(String token) {

    if(StringUtils.isEmpty(token)) {
    return false;
    }

    try {
    Claims claims = Jwts.parser()
    .setSigningKey("itcast")
    .parseClaimsJws(token)
    .getBody();
    }catch (Exception e) {
    return false;
    }

    return true;
    }
    }
  • UserApi —-interface模块

    1
    2
    //保存用户,返回用户id
    Long save(User user);
  • UserApiImpl —-db模块

    1
    2
    3
    4
    5
    6
    //保存用户id
    @Override
    public Long save(User user) {
    userMapper.insert(user);
    return user.getId();
    }
  • UserService

    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
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    package com.tanhua.service;


    import com.tanhua.commons.utils.JwtUtils;
    import com.tanhua.dubbo.api.SmsTemplate;
    import com.tanhua.dubbo.api.UserApi;
    import com.tanhua.dubbo.domain.User;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.dubbo.config.annotation.DubboReference;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;

    import java.time.Duration;
    import java.util.HashMap;
    import java.util.Map;

    @Service
    public class UserService {

    @Autowired
    private SmsTemplate template;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @DubboReference
    private UserApi userApi;


    /**
    * 发送短信验证码
    * @param phone
    */
    public void sendMsg(String phone) {
    //1、随机生成6位数字
    //String code = RandomStringUtils.randomNumeric(6);
    String code = "123456";
    //2、调用template对象,发送手机短信
    //template.sendSms(phone,code);
    //3、将验证码存入到redis
    redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
    }


    /**
    * 验证登录
    * @param phone
    * @param code
    */
    public Map loginVerification(String phone, String code) {
    //1、从redis中获取下发的验证码
    String redisCode = redisTemplate.opsForValue().get("CHECK_CODE_" + phone);
    //2、对验证码进行校验(验证码是否存在,是否和输入的验证码一致)
    if(StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
    //验证码无效
    throw new RuntimeException("验证码错误");
    }
    //3、删除redis中的验证码
    redisTemplate.delete("CHECK_CODE_" + phone);
    //4、通过手机号码查询用户
    User user = userApi.findByMobile(phone);
    boolean isNew = false;
    //5、如果用户不存在,创建用户保存到数据库中
    if(user == null) {
    user = new User();
    user.setMobile(phone);
    user.setPassword(DigestUtils.md5Hex("123456"));
    Long userId = userApi.save(user);
    user.setId(userId);
    isNew = true;
    }
    //6、通过JWT生成token(存入id和手机号码)
    Map tokenMap = new HashMap();
    tokenMap.put("id",user.getId());
    tokenMap.put("mobile",phone);
    String token = JwtUtils.getToken(tokenMap);
    //7、构造返回值
    Map retMap = new HashMap();
    retMap.put("token",token);
    retMap.put("isNew",isNew);

    return retMap;
    }
    }

1.3.6前后端联调

  • 修改模拟器里面的API地址
  • http://172.16.1.2:18080
    • 172.16.1为模拟器的ip地址前3为
    • 2 固定
    • 18080 为端口号

1.4完善个人信息

1.4.1流程分析

  • 首次登录时(手机号码不存在),需要创建用户存入数据库中
  • 客户端检测首次登录需要完善用户信息
    • 填写用户基本信息
    • 上传用户头像(需要人脸认证)
      code

      1.4.2图片存储方案

  • 单一服务器存储
    1. 优点:开发便捷,成本低
    2. 缺点:扩容困难
  • 分布式文件系统
    1. 优点:容易实现扩容
    2. 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS)
  • 使用第三方的存储服务

    1. 优点:开发简单,拥有强大功能,免维护
    2. 缺点:付费
  • 阿里云OSS

  • 阿里云对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
  • 官方地址:https://www.aliyun.com/product/oss
  • 使用案例

    • bucket: tanhua0001
    • 需要自己配置id 和 secret

      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
      package cn.itheima.test;
      import com.aliyun.oss.OSS;
      import com.aliyun.oss.OSSClientBuilder;
      import org.junit.Test;
      import java.io.ByteArrayInputStream;
      import java.io.File;
      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.UUID;

      public class OssTest {
      /**案例:
      * 将资料中的default_cover_2.webp上传到阿里云OSS
      * 存放位置: /yyyy/MM/dd/xxx.webp
      */

      @Test
      public void OssTest() throws FileNotFoundException {
      //1.配置图片路径
      String path = "C:\\Users\\19849\\Desktop\\新建文件夹\\default_cover_2.webp";
      //2.构造FileInputStream
      FileInputStream fileInputStream = new FileInputStream(new File(path));
      //3.拼写图片路径
      String filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date())
      +"/"+ UUID.randomUUID().toString() + path.substring(path.lastIndexOf("."));

      // Endpoint以华南(广州)为例,其它Region请按实际情况填写。
      String endpoint = "oss-cn-guangzhou.aliyuncs.com";
      //阿里云的id和secret
      String accessKeyId = "id";
      String accessKeySecret = "key";
      // 创建OSSClient实例。
      OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
      //填写Bucket名称和Object完整路径,Object完整路径中不能包含Bucket名称。
      ossClient.putObject("tanhua0001",filename,fileInputStream);
      //关闭OSSClient
      ossClient.shutdown();
      //前面的字符串在oss图片查看里面找到
      String url = "https://tanhua0001.oss-cn-guangzhou.aliyuncs.com/" + filename;
      System.out.println(url);

      }
      }

把案例写入自动配置里面
  1. 创建OssProperties —-autoconfig模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.tanhua.autoconfig.properties;

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    @Data
    @ConfigurationProperties(prefix = "tanhua.oss")
    public class OssProperties {
    private String accessKey;
    private String secret;
    private String bucketName;
    private String url; //域名
    private String endpoint;
    }
  2. 创建OssTemplate模板对象 —-autoconfig模块
    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
    package com.tanhua.autoconfig.template;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.tanhua.autoconfig.properties.OssProperties;
    import java.io.InputStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.UUID;

    public class OssTemplate {
    private OssProperties properties;
    public OssTemplate(OssProperties properties){
    this.properties = properties;
    }

    /**
    * 文件上传
    * 1.文件名称
    * 2.输入流
    */
    public String upload(String filename, InputStream is){
    //拼写图片路径
    filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date())
    +"/"+ UUID.randomUUID().toString() + filename.substring(filename.lastIndexOf("."));

    // Endpoint以华南(广州)为例,其它Region请按实际情况填写。
    String endpoint = properties.getEndpoint();
    //阿里云的id和secret
    String accessKeyId = properties.getAccessKey();
    String accessKeySecret = properties.getSecret();
    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
    //填写Bucket名称和Object完整路径,Object完整路径中不能包含Bucket名称。
    ossClient.putObject(properties.getBucketName(),filename,is);
    //关闭OSSClient
    ossClient.shutdown();
    //前面的字符串在oss图片查看里面找到
    String url = properties.getUrl()+ "/" + filename;
    return url;
    }
    }
  3. TanhuaAutoConfiguration注入容器 —-autoconfig模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.tanhua.autoconfig;
    @EnableConfigurationProperties({
    Smsproperties.class,
    OssProperties.class
    })
    public class TanhuaAutoConfiguration {

    @Bean
    public SmsTemplate smsTemplate(Smsproperties properties){
    return new SmsTemplate(properties);
    }

    @Bean
    public OssTemplate ossTemplate(OssProperties properties){
    return new OssTemplate(properties);
    }
    }

  4. 修改消费者的yaml文件 —-app模块
    1
    2
    3
    4
    5
    6
    7
    tanhua:
    oss:
    accessKey: Key
    secret: secret
    endpoint: oss-cn-guangzhou.aliyuncs.com
    bucketName: tanhua0001
    url: https://tanhua0001.oss-cn-guangzhou.aliyuncs.com
  5. OssTest单元测试 —-app模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = AppServerApplication.class)
    public class OssTest {

    @Autowired
    private OssTemplate ossTemplate;

    @Test
    public void testOssremplateUpload() throws FileNotFoundException {
    String path = "C:\\Users\\19849\\Desktop\\新建文件夹\\default_cover_2.webp";
    FileInputStream fileInputStream = new FileInputStream(new File(path));

    String urlimage = ossTemplate.upload(path, fileInputStream);
    System.out.println(urlimage);

    }

    如果测试失败,可能是由于服务提供者没有启动

1.4.3人脸识别技术

  • 人脸识别(Face Recognition)基于图像或视频中的人脸检测、分析和比对技术,提供对您已获授权前提下的私有数据的人脸检测与属性分析、人脸对比、人脸搜索、活体检测等能力。灵活应用于金融、泛安防、零售等行业场景,满足身份核验、人脸考勤、闸机通行等业务需求
  • 官方地址:https://ai.baidu.com/tech/face/
  • 入门案例
  1. 创建人脸识别应用
  2. 单元测试
    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
    package cn.itheima.test;
    import com.baidu.aip.face.AipFace;
    import org.json.JSONObject;
    import java.util.HashMap;
    public class FaceTest {
    //设置APPID/AK/SK
    public static final String APP_ID = "APPID";
    public static final String API_KEY = "zAqvCgwiNko8l8bRX8bwacr0";
    public static final String SECRET_KEY = "LaUFQsMuBPHoslZ4bMYVnmUOUwz0G2zh";

    public static void main(String[] args) {
    // 初始化一个AipFace
    AipFace client = new AipFace(APP_ID, API_KEY, SECRET_KEY);

    // 可选:设置网络连接参数
    client.setConnectionTimeoutInMillis(2000);
    client.setSocketTimeoutInMillis(60000);

    // 调用接口
    String image = "图片地址";
    String imageType = "URL";

    // 传入可选参数调用接口
    HashMap<String, String> options = new HashMap<String, String>();
    options.put("face_field", "age");
    options.put("max_face_num", "2");
    options.put("face_type", "LIVE");
    options.put("liveness_control", "LOW");

    // 人脸检测
    JSONObject res = client.detect(image, imageType, options);
    System.out.println(res.toString(2));

    Object error_code = res.get("error_Code");
    System.out.println(error_code);
    }
    }
  3. 结果
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "result": null,
    "log_id": 78997790,
    "error_msg": "image download fail",
    "cached": 0,
    "error_code": 222204,
    "timestamp": 1716886348
    }

    第一次可以查询出来
    后面不知道为什么出现image download fail错误

把案例写入自动配置里面
  1. AipFaceProperties
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.tanhua.autoconfig.properties;

    import com.baidu.aip.face.AipFace;
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;

    @Data
    @ConfigurationProperties("tanhua.aip")
    public class AipFaceProperties {
    private String appId;
    private String apiKey;
    private String secretKey;

    //AipFace建议单例使用,避免重复获取access_token
    @Bean
    public AipFace aipFace() {
    AipFace client = new AipFace(appId, apiKey, secretKey);
    // 可选:设置网络连接参数
    client.setConnectionTimeoutInMillis(2000);
    client.setSocketTimeoutInMillis(60000);
    return client;
    }
    }
  2. AipFaceTemplate
    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
    package com.tanhua.autoconfig.template;
    import com.baidu.aip.face.AipFace;
    import org.json.JSONObject;
    import org.springframework.beans.factory.annotation.Autowired;
    import java.util.HashMap;

    public class AipFaceTemplate {

    @Autowired
    private AipFace client;

    /**
    * 检测图片中是否包含人脸
    * true:包含
    * false:不包含
    */
    public boolean detect(String imageUrl) {
    // 调用接口
    String imageType = "URL";

    HashMap<String, String> options = new HashMap<String, String>();
    options.put("face_field", "age");
    options.put("max_face_num", "2");
    options.put("face_type", "LIVE");
    options.put("liveness_control", "LOW");

    // 人脸检测
    JSONObject res = client.detect(imageUrl, imageType, options);
    System.out.println(res.toString(2));

    Integer error_code = (Integer) res.get("error_code");

    return error_code == 0;
    }
    }

  3. TanhuaAutoConfiguration
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @EnableConfigurationProperties({
    Smsproperties.class,
    OssProperties.class,
    AipFaceProperties.class
    })
    public class TanhuaAutoConfiguration {
    @Bean
    public SmsTemplate smsTemplate(Smsproperties properties){
    return new SmsTemplate(properties);
    }
    @Bean
    public OssTemplate ossTemplate(OssProperties properties){
    return new OssTemplate(properties);
    }
    @Bean
    public AipFaceTemplate aipFaceTemplate(){
    return new AipFaceTemplate();
    }
    }

    注意:一定要写@EnableConfigurationProperties里面的类,不然会报错

  4. 消费者app里面的yaml
    1
    2
    3
    4
    5
    tanhua:
    aip:
    appId: id
    apiKey: zAqvCgwiNko8l8bRX8bwacr0
    secretKey: LaUFQsMuBPHoslZ4bMYVnmUOUwz0G2zh
  5. 单元测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = AppServerApplication.class)
    public class FaceTest {

    @Autowired
    private AipFaceTemplate template;

    @Test
    public void detectFace() {
    String image = "https://tanhua0001.oss-cn-guangzhou.aliyuncs.com/2024/05/28/54b00c19-2b06-433c-b219-ef66b65959a4.webp";
    boolean detect = template.detect(image);
    }

    由于前面出现image download fail错误,导致现在test代码也无法跑通

2.完善用户信息

2.1保存用户信息

2.1.1 需求分析

  • tb_user_info:用户资料表
    code
    • 用户表和用户信息表是一对一的关系,两者采用主键关联的形式配置
    • 主键关联:用户表主键和用户资料表主键要保持一致(如:用户表id=1,此用户的资料表id=1)

2.1.2 接口定义

1
2
3
4
5
6
7
8
9
10
11
接口路径:/user/loginReginfo
请求方式:POST
请求header:Authorization --- token
Body参数:UserInfo
{
"gender":"man",
"nickname": "播仔",
"birthday": "2006-05-01",
"city": "北京-昌平"
}
响应结果:ResponseEntity: null

2.1.3 接口实现

  1. 配置dubbo提供者环境
    • 创建实体类UserInfo —-domain模块
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      package com.tanhua.dubbo.domain;
      import com.baomidou.mybatisplus.annotation.FieldFill;
      import com.baomidou.mybatisplus.annotation.TableField;
      import lombok.Data;
      import java.io.Serializable;
      import java.util.Date;

      @Data
      public abstract class BasePojo implements Serializable {
      @TableField(fill = FieldFill.INSERT) //自动填充
      private Date created;
      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date updated;
      }
    • 创建UserInfoMapper接口 —-db模块
      1
      2
      public interface UserInfoMapper extends BaseMapper<UserInfo> {
      }
    • 创建UserInfoApi接口 —-interface模块
      1
      2
      3
      4
      5
      public interface UserInfoApi {

      public void sava(UserInfo userInfo);
      public void update(UserInfo userInfo);
      }
    • 实现接口UserInfoImpl —-db模块
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      @DubboService
      public class UserInfoImpl implements UserInfoApi {
      @Autowired
      private UserInfoMapper userInfoMapper;

      @Override
      public void sava(UserInfo userInfo) {
      userInfoMapper.insert(userInfo);
      }

      @Override
      public void update(UserInfo userInfo) {
      userInfoMapper.updateById(userInfo);
      }
      }

  2. 在消费者中创建UserInfoController —-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

    @RestController
    @RequestMapping("/user")
    public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    /**
    * 保存用户信息
    * UserInfo
    * 请求头携带token
    */
    @PostMapping("/loginReginfo")
    public ResponseEntity loginReginfo(@RequestBody UserInfo userInfo,
    @RequestHeader("Authorization") String token){
    //1.判断token是否合法
    boolean verifyToken = JwtUtils.verifyToken(token);
    if (!verifyToken){
    return ResponseEntity.status(401).body(null);
    }
    //2.向userinfo中设置id
    Claims claims = JwtUtils.getClaims(token);
    Integer id = (Integer) claims.get("id");
    userInfo.setId(Long.valueOf(id));
    //3.调用service
    userInfoService.save(userInfo);
    return ResponseEntity.ok(null);
    }

    }

  3. 在消费者中创建UserInfoService —-api模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Service
    public class UserInfoService {

    @DubboReference
    private UserInfoApi userInfoApi;

    public void save(UserInfo userInfo){
    userInfoApi.sava(userInfo);
    }
    }

    2.1.4测试结果

    code

2.2上传用户头像

2.2.1流程分析

  • 用户点击上传头像,把图片上传到阿里云oss,并返回url存入avatar字段里面
    code

2.2.2接口定义

1
2
3
4
5
接口路径:/user/loginReginfo/head
请求方式:POST
请求header:Authorization
请求参数:headPhoto
响应结果:ResponseEntity ---null
  • 文件上传采用POST传递,mvc中经过文件解析器转化为MultipartFile对象处理
  • 后续请求中,请求headr中都有Authorization参数。

2.2.3接口实现

  • UserInfoController —-app模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //上传用户头像
    @PostMapping("/loginReginfo/head")
    public ResponseEntity head(MultipartFile headPhoto,
    @RequestHeader("Authorization") String token) throws IOException {
    //1.判断token是否合法
    boolean verifyToken = JwtUtils.verifyToken(token);
    if (!verifyToken){
    return ResponseEntity.status(401).body(null);
    }
    //2.向userinfo中设置id
    Claims claims = JwtUtils.getClaims(token);
    Integer id = (Integer) claims.get("id");
    //3.调用service
    userInfoService.updateHead(headPhoto,id);
    return ResponseEntity.ok(null);
    }
  • UserInfoService —-app模块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //更新用户头像
    public void updateHead(MultipartFile headPhoto, Integer id) throws IOException {
    //1、将图片上传到阿里云oss
    String imageUrl = ossTemplate.upload(headPhoto.getOriginalFilename(), headPhoto.getInputStream());
    //2、调用百度云判断是否包含人脸
    boolean detect = aipFaceTemplate.detect(imageUrl);
    //2.1 如果不包含人脸,抛出异常
    if(!detect) {
    throw new RuntimeException();
    }else{
    //2.2 包含人脸,调用API更新
    UserInfo userInfo = new UserInfo();
    userInfo.setId(Long.valueOf(id));
    userInfo.setAvatar(imageUrl);
    userInfoApi.update(userInfo);
    }
    }

2.2.4测试结果

  • 由于模拟器没有摄像头权限,所以无法测试
  • 使用postman测试,由于前面的image download fail错误,导致测试失败,但是oss里面可以看到图片