【版本】
当前版本号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'@'%';
数据库脚本创建
- 参考实验4.1,使用 Flyway 来构建物联网云平台项目的数据库。在
src\main\resources\db\migration
目录下创建以下3个SQL文件。
注意:这里的脚本命名V00X后面接2个下划线符号!
V001__CREATE_USER.sql
- 创建用户表,参考实验2.2V002__CREATE_DEVICE.sql
- 创建设备表,参考实验2.2V003__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
,测试是否能够获取令牌信息。