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'@'%';
flush privileges;
复制

数据库脚本创建

  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,测试是否能够获取令牌信息。

扫码或长按识别访问