Part 5 - 物联网云平台数据管理开发实战

2022-03-31
8分钟阅读时长

【版本】

当前版本号v20230208

版本修改说明
v20230208优化了代码,把 thymeleaf 框架换为 beetl
v20220413修正登录的密码错误
v20220331初始化

【实验名称】5.1 构建物联网云平台项目(iot-cloud)

【实验目的】

  • 掌握数据库的建立和使用 Flyway 管理数据库脚本
  • 掌握使用 SpringBoot 框架搭建项目
  • 掌握使用 SpringBoot、MyBatis 框架开发项目

【实验环境】

  • IDEA
  • Maven 3.6
  • MariaDB 10.4
  • JDK 8

【实验说明】

  1. 结合实验2.2设计的表,实现从使用 Flyway 命令来管理数据库脚本。

Flyway是一个管理数据库脚本的框架,可以与Maven集成,快速地构建数据库。

  1. iot-cloud是物联网云平台项目。使用 SpringBoot 集成了 SSM 框架和 Beetl 框架。

【实验效果】

  1. 启动IoTCloudApplication,访问http://localhost:8098/,可以看到以下界面

【实验步骤】

  1. 解压iot-cloud.zip,这是物联网云平台项目,也是一个 Maven 项目。

  2. 使用IDEA 打开此项目。

  3. 参考实验3.1步骤5,设置iot-cloud项目的 Maven 安装路径和 settings.xml 文件路径。

创建数据库和数据库用户

  1. 创建物联网云平台项目的 MariaDB 数据库(iotcloud)和用户,请使用数据库客户端执行以下SQL语句。
创建数据库 iotcloud
CREATE DATABASE iotcloud DEFAULT character set UTF8mb4 collate utf8mb4_bin;
-- 创建iotcloud@localhost 用户,密码为 CkA4NAbqHCz@t
CREATE user 'iotcloud'@'localhost' IDENTIFIED BY 'CkA4NAbqHCz@t';
-- 创建iotcloud@% 用户,密码为 CkA4NAbqHCz@t
CREATE user 'iotcloud'@'%' IDENTIFIED BY 'CkA4NAbqHCz@t';
-- 授权 iotcloud 的所有表,以及所有权限给这2个用户
GRANT ALL ON iotcloud.* TO 'iotcloud'@'localhost';
GRANT ALL ON iotcloud.* TO 'iotcloud'@'%';

数据库脚本创建

  1. 参考实验4.1,使用 Flyway 来构建物联网云平台项目的数据库。在src\main\resources\db\migration目录下创建以下3个SQL文件。
注意:这里的脚本命名V00X后面接2个下划线符号!
  • V001__CREATE_USER.sql - 创建用户表,参考实验2.2
  • V002__CREATE_DEVICE.sql - 创建设备表,参考实验2.2
  • V003__INIT_DATA.sql - 初始化数据。
INSERT INTO `user` (`user_id`, `pwd`, `user_name`, `user_secret`) VALUES ('zhangsan', '123456', '张三', 'JOGP9IEQNBOEOPRTJ');
INSERT INTO `device` (`iot_id`, `dev_name`, `user_id`, `dev_type`, `status`, `dev_secret`, `description`,`create_time`) VALUES ('jt982tghj9r8g', 'Lock01', 'zhangsan', 'lock', 'enabled', 'jgiofjgdfiogj', '智能锁1号',now());

Flyway 执行 SQL 脚本

  1. 参考实验4.1步骤7,配置和运行 Flyway 迁移任务,构建iotcloud数据库,构建成功的数据库应该包含以下表。
device
user
flyway_schema_history
  1. 启动IoTCloudApplication,访问http://localhost:8098/,可以看到以下界面

  2. 输入用户名zhangsan和密码123456测试是否能够登录成功,并跳转到设备列表界面。

【实验名称】5.2 物联网云平台实现用户登录功能

【实验目的】

  • 掌握使用 Spring、SpringMVC、MyBatis、Beetl 框架开发项目

【实验环境】

  • IDEA
  • Maven 3.6
  • MariaDB 10.4
  • JDK 8

【实验说明】

  1. 本实验承接实验5.1,在此基础上对登录功能进行修改,实现能够从数据库读取用户密码信息进行匹配,只有匹配成功情况下才能成功登录。

【实验效果】

  1. 输入用户名zhangsan和密码123456,登录成功。

【实验步骤】

  1. 修改LoginController.signIn方法,删除硬编码相关代码,实现从数据库查询用户信息。请同学们自己完成。

提示:可以调用 UserService 的方法获取用户信息

  1. 修改src\main\java\iot\cloud\platform\cloud\mapper\UserMapper.xml,实现 SQL 语句。请同学们自己完成。

【实验名称】5.3 物联网云平台实现设备列表查询功能

【实验目的】

  • 掌握使用 Spring、SpringMVC、MyBatis 框架开发项目

【实验环境】

  • IDEA
  • Maven 3.6
  • MariaDB 10.4
  • JDK 8

【实验说明】

  1. 本实验承接实验5.2,在此基础上对用户设备列表功能进行修改,实现能够从数据库读取当前用户的设备列表。

【实验效果】

  1. 用户登录以后显示当前用户的设备列表信息。

【实验步骤】

  1. 修改DeviceController.getDeviceList方法,删除硬编码相关代码,实现从数据库查询用户设备列表信息。请同学们自己完成。

提示:可以调用 DeviceService 的方法获取用户设备信息

  1. 修改src\main\java\iot\cloud\platform\cloud\mapper\DeviceMapper.xml,实现 SQL 语句。请同学们自己完成。

【实验名称】5.4 物联网云平台实现公开API——获取访问令牌

【实验目的】

  • 掌握使用 Spring、SpringMVC、MyBatis 框架开发项目
  • 掌握使用 Swagger 框架编写公开API

【实验环境】

  • IDEA
  • Maven 3.6
  • MariaDB 10.4
  • JDK 8

【实验说明】

  1. 本实验承接实验5.3,在此基础上增加实现公开API功能,这里仅实现一个获取访问令牌功能。
参数名称描述
user_id用户ID
secret用户密钥
  • 示例发送代码:
http://localhost:8098/token?user_id=zhangsan&secret=jgiofjgdfiogj
  • 获取令牌成功回应消息:
{
  "errcode": "0",
  "errmsg": "获取令牌成功",
  "data": {
    "userId": "zhangsan",
    "token": "7e75e015cf66ad0942df0413eb9d1a4c12342c61bd42bca3f70b2cd6c315d682",
    "expiredTime": "2022-04-03T23:10:52.010+00:00",
    "expiredTs": 1649027452010
  }
}

【实验步骤】

Token 表创建

  1. src\main\resources\db\migration新增一个 SQL 脚本V004__CREATE_TOKEN.sql,创建一个token表用于保存用户访问令牌。
create table `token`(
	`token` VARCHAR(128) NOT NULL COMMENT '访问令牌',
    `user_id` VARCHAR(32) NOT NULL COMMENT '用户id',
	`expired_time` DATETIME NOT NULL COMMENT '令牌超时时间',
	`expired_ts` BIGINT NOT NULL COMMENT '令牌超时时间戳',
	UNIQUE KEY (`token`) USING BTREE
)
COMMENT='用户令牌'
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
ROW_FORMAT=DYNAMIC
;

TokenEntity

  1. 根据Token表创建一个TokenEntity类,用于装载Token表数据。其他属性部分、getter和setter请同学们自行完成。
public class TokenEntity {
  private Long expiredTs;

  public boolean expired(){
    long now=new Date().getTime();
    return expiredTs<now;
  }
  
  //请补充缺失的属性、getter 和 setter。
}

pom.xml

  1. pom.xml内新增swagger相关包。
        <!-- swagger 包开始 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>
        <!-- swagger 包结束 -->

TokenMapper

  1. 新增TokenMapper类。
package iot.cloud.platform.cloud.mapper;

import iot.cloud.platform.cloud.entity.TokenEntity;

public interface TokenMapper {
  /**
   * 根据 Token 获取 Token信息
   * @param token
   * @return
   */
  TokenEntity getToken(String token);

  /**
   * 根据用户ID获取 Token信息
   * @param userId
   * @return
   */
  TokenEntity getTokenByUserId(String userId);

  /**
   * 保存 Token
   * @param token
   * @return
   */
  boolean saveToken(TokenEntity token);
  
  /**
   * 更新用户 Token
   * @param token
   * @return
   */
  boolean updateToken(TokenEntity token);
}

TokenMapper.xml

  1. src\main\resources\iot\cloud\platform\cloud\mapper新增TokenMapper.xml,根据TokenMapper.java的注释提示完成 SQL 语句编写。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="iot.cloud.platform.cloud.mapper.TokenMapper">
    <select id="getTokenByUserId" resultType="iot.cloud.platform.cloud.entity.TokenEntity">
        <!-- 请完成SQL语句 -->
    </select>

    <select id="getToken" resultType="iot.cloud.platform.cloud.entity.TokenEntity">
        <!-- 请完成SQL语句 -->
    </select>

    <insert id="saveToken" parameterType="iot.cloud.platform.cloud.entity.TokenEntity">
        <!-- 请完成SQL语句 -->
    </insert>
	
	<update id="updateToken" parameterType="iot.cloud.platform.cloud.entity.TokenEntity">
        <!-- 请完成SQL语句 -->
    </update>
</mapper>

TokenService 和 TokenServiceImpl

  1. 新增TokenServiceTokenServiceImpl类。
package iot.cloud.platform.cloud.service;

import iot.cloud.platform.cloud.entity.TokenEntity;

public interface TokenService {
  /**
   * 如果是有效的Token 返回true
   * @param token
   * @return
   */
  boolean isValidToken(String token);

  /**
   * 根据用户ID 从数据库获取Token
   * @param userId
   * @return
   */
  TokenEntity getTokenByUserId(String userId);

  /**
   * 生成一个Token,保存到数据库,并返回
   * @param userId
   * @return
   */
  TokenEntity generateToken(String userId);

  /**
   * 根据Token 获取所有 Token 信息
   * @param token
   * @return
   */
  TokenEntity getToken(String token);
  
}
  • TokenServiceImpl
package iot.cloud.platform.cloud.service.impl;

import iot.cloud.platform.cloud.entity.TokenEntity;
import iot.cloud.platform.cloud.mapper.TokenMapper;
import iot.cloud.platform.cloud.mapper.UserMapper;
import iot.cloud.platform.cloud.service.TokenService;
import iot.cloud.platform.cloud.utils.IDUtils;
import iot.cloud.platform.cloud.utils.MsgDigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class TokenServiceImpl implements TokenService {

  private long tokenValidPeriod=1000*60*60*8;

  @Autowired
  private TokenMapper tokenMapper;

  @Autowired
  private UserMapper userService;

  @Override
  public boolean isValidToken(String token) {
    TokenEntity te=getToken(token);
    return te!=null && !te.expired();
  }

  @Override
  public TokenEntity getTokenByUserId(String userId) {
    return tokenMapper.getTokenByUserId(userId);
  }

  @Override
  public TokenEntity generateToken(String userId) {
    TokenEntity token=new TokenEntity();
    long expiredTs=new Date().getTime()+tokenValidPeriod;
    Date expiredTime=new Date(expiredTs);
    token.setUserId(userId);
    token.setExpiredTs(expiredTs);
    token.setExpiredTime(expiredTime);
    token.setToken(IDUtils.genUniqueId());
    if(getTokenByUserId(userId)==null) {
      tokenMapper.saveToken(token);
    }else{
      tokenMapper.updateToken(token);
    }
    return token;
  }

  @Override
  public TokenEntity getToken(String token) {
    return tokenMapper.getToken(token);
  }


}

UserService 和 UserServiceImpl

  1. UserServiceUserServiceImpl新增verifySecret方法,用于校验用户ID和用户密钥是否匹配,匹配返回true。
  • UserService
package iot.cloud.platform.cloud.service;

import iot.cloud.platform.cloud.entity.UserEntity;

public interface UserService {
  UserEntity getUserById(String userId);
  UserEntity getUserByIdOrName(String idOrName);
  boolean verifySecret(String userId,String secret);
}
  • UserServiceImpl
package iot.cloud.platform.cloud.service.impl;

import iot.cloud.platform.cloud.entity.UserEntity;
import iot.cloud.platform.cloud.mapper.UserMapper;
import iot.cloud.platform.cloud.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

  @Autowired
  private UserMapper userMapper;

  @Override
  public UserEntity getUserById(String userId) {
    return userMapper.getUserById(userId);
  }

  @Override
  public UserEntity getUserByIdOrName(String idOrName) {
    return userMapper.getUserByIdOrName(idOrName);
  }

  @Override
  public boolean verifySecret(String userId, String secret) {
    //TODO:请同学们自行完成此方法
  }
}

SwaggerConfig

  1. 新增SwaggerConfig
package iot.cloud.platform.cloud.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                // 指定构建api文档的详细信息的方法:apiInfo()
                .apiInfo(apiInfo())
                .select()
                // 指定要生成api接口的包路径
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                //使用了 @ApiOperation 注解的方法生成api接口文档
                //.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                //可以根据url路径设置哪些请求加入文档,忽略哪些请求
                .build();
    }

    /**
     * 设置api文档的详细信息
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                // 标题
                .title("物联网云平台开放API")
                // 接口描述
                .description("物联网云平台开放API")
                // 版本信息
                .version("1.0")
                // 构建
                .build();
    }
}

TokenController

  1. 新增TokenController类,补充缺失的代码。
package iot.cloud.platform.cloud.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import iot.cloud.platform.cloud.entity.TokenEntity;
import iot.cloud.platform.cloud.service.TokenService;
import iot.cloud.platform.cloud.service.UserService;
import iot.cloud.platform.cloud.vo.ResMsg;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@Api("平台访问令牌接口")
public class TokenController {

  @Autowired
  private TokenService tokenService;

  @Autowired
  private UserService userService;

  @GetMapping("/token")
  @ResponseBody
  @ApiOperation("/获取用户访问令牌")
  @ApiImplicitParams ({
    @ApiImplicitParam(name = "user_id",value = "用户ID",required = true,paramType = "query",dataType = "string"),
    @ApiImplicitParam(name = "secret",value = "用户密钥",required = true,paramType = "query",dataType = "string")
  })
  public ResMsg token(@RequestParam("user_id") String userId,@RequestParam("secret") String secret){
    ResMsg msg=new ResMsg();
    if(userId!=null || secret!=null){
      if(){//校验userId和secret
        TokenEntity token=;//根据userId 获取 TokenEntity
        if(token!=null) {
          if (token.expired()) {
            token=;//如果令牌过期,重新生成一个
            msg.setErrcode("0");
            msg.setErrmsg("获取令牌成功");
            msg.setData(token);
          } else {
            msg.setErrcode("0");
            msg.setErrmsg("获取令牌成功");
            msg.setData(token);
          }
        }else{
          token=;//如果令牌过期,重新生成一个
          msg.setErrcode("0");
          msg.setErrmsg("获取令牌成功");
          msg.setData(token);
        }
      }
    }else{
      msg.setErrcode("1003");
      msg.setErrmsg("用户不存在或secret不匹配");
    }
    return msg;
  }
}

工具类 IDUtils 和 MsgDigestUtils

  1. 新增工具类IDUtils 用于生成唯一ID,MsgDigestUtils 用于加密信息。
  • IDUtils
package iot.cloud.platform.cloud.utils;

import org.apache.commons.lang3.RandomStringUtils;

import java.util.Date;

public class IDUtils {

  public static String genUniqueId(){
    return MsgDigestUtils.encodeSHA256(RandomStringUtils.randomAlphanumeric(16)+new Date().getTime());
  }

}
  • MsgDigestUtils
package iot.cloud.platform.cloud.utils;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MsgDigestUtils {
  /**
   * 密码加密
   * @param pwd 明文密码
   * @param salt 盐
   * @return
   */
  public static String pwdEncrypt(String pwd,String salt){
    final int loop=3;
    String pwdEnc=pwd;
    for(int i=0;i<loop;i++){
      pwdEnc=encodeSHA256(pwdEnc+salt);
    }
    return pwdEnc;
  }

  /**
   * SHA256 加密
   * @param str 明文
   * @return 密文
   */
  public static String encodeSHA256(String str){
    MessageDigest messageDigest;
    String encdeStr = "";
    try {
      messageDigest = MessageDigest.getInstance("SHA-256");
      byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
      encdeStr = byte2Hex(hash);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
    return encdeStr;
  }

  private static String byte2Hex(byte[] bytes){
    StringBuffer stringBuffer = new StringBuffer();
    String temp = null;
    for (int i=0;i<bytes.length;i++){
      temp = Integer.toHexString(bytes[i] & 0xFF);
      if (temp.length()==1){
        //1得到一位的进行补0操作
        stringBuffer.append("0");
      }
      stringBuffer.append(temp);
    }
    return stringBuffer.toString();
  }

}
  1. 访问http://localhost:8098/swagger-ui.html,输入用户ID用户密钥,点击Try it out,测试是否能够获取令牌信息。