【版本】
当前版本号v20230208
| 版本 | 修改说明 | 
|---|---|
| v20230208 | 优化了代码,把 thymeleaf 框架换为 beetl | 
| v20220413 | 修正登录的密码错误 | 
| v20220331 | 初始化 | 
任务5.1 构建物联网云平台项目(iot-cloud)
【任务目的】
- 掌握数据库的建立和使用 Flyway 管理数据库脚本
- 掌握使用 SpringBoot 框架搭建项目
- 掌握使用 SpringBoot、MyBatis 框架开发项目
【任务环境】
- IDEA
- Maven 3.6
- MariaDB 10.4
- JDK 8
【任务说明】
- 结合任务2.2设计的表,实现从使用 Flyway 命令来管理数据库脚本。
Flyway是一个管理数据库脚本的框架,可以与
Maven集成,快速地构建数据库。
- iot-cloud是物联网云平台项目。使用 SpringBoot 集成了 SSM 框架和 Beetl 框架。
【任务效果】
- 启动IoTCloudApplication,访问http://localhost:8098/,可以看到以下界面 
【任务步骤】
- 解压 - iot-cloud.zip,这是物联网云平台项目,也是一个 Maven 项目。
- 使用IDEA 打开此项目。  
- 参考任务3.1步骤5,设置 - iot-cloud项目的 Maven 安装路径和 settings.xml 文件路径。
创建数据库和数据库用户
- 创建物联网云平台项目的 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'@'%';
flush privileges;
数据库脚本创建
- 参考任务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 脚本
- 参考任务4.1步骤7,配置和运行 Flyway 迁移任务,构建iotcloud数据库,构建成功的数据库应该包含以下表。
device
user
flyway_schema_history
- 启动 - IoTCloudApplication,访问http://localhost:8098/,可以看到以下界面 
- 输入用户名 - zhangsan和密码- 123456测试是否能够登录成功,并跳转到设备列表界面。 
任务5.2 物联网云平台实现用户登录功能
【任务目的】
- 掌握使用 Spring、SpringMVC、MyBatis、Beetl 框架开发项目
【任务环境】
- IDEA
- Maven 3.6
- MariaDB 10.4
- JDK 8
【任务说明】
- 本任务承接任务5.1,在此基础上对登录功能进行修改,实现能够从数据库读取用户密码信息进行匹配,只有匹配成功情况下才能成功登录。
【任务效果】
- 输入用户名zhangsan和密码123456,登录成功。   
【任务步骤】
- 修改LoginController.signIn方法,删除硬编码相关代码,实现从数据库查询用户信息。请同学们自己完成。
提示:可以调用 UserService 的方法获取用户信息
- 修改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
【任务说明】
- 本任务承接任务5.2,在此基础上对用户设备列表功能进行修改,实现能够从数据库读取当前用户的设备列表。
【任务效果】
- 用户登录以后显示当前用户的设备列表信息。
 
【任务步骤】
- 修改DeviceController.getDeviceList方法,删除硬编码相关代码,实现从数据库查询用户设备列表信息。请同学们自己完成。
提示:可以调用 DeviceService 的方法获取用户设备信息
- 修改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
【任务说明】
- 本任务承接任务5.3,在此基础上增加实现公开API功能,这里仅实现一个获取访问令牌功能。
- 获取访问令牌功能 URL:http://localhost:8098/token
| 参数名称 | 描述 | 
|---|---|
| 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 表创建
- 在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
- 根据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
- 在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
- 新增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
- 在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
- 新增TokenService和TokenServiceImpl类。
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
- UserService和- UserServiceImpl新增- 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
- 新增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
- 新增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
- 新增工具类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();
  }
}
- 访问http://localhost:8098/swagger-ui.html,输入用户ID和用户密钥,点击Try it out,测试是否能够获取令牌信息。   
