手把手教你写一个简单的项目---->物联网环境监测系统
程序员文章站
2022-06-09 09:03:59
...
物联网环境监测系统
需要用到的知识
- SVN版本管理工具
- maven项目管理工具
- JDBC、连接池
- IO流
- 线程池
- javaEE的一些基础知识
- log4j日志
- lombok的一些简单操作
项目整体思路
- 使用maven搭建好项目环境,使用聚合关系搭建
environment-parent
项目和它的一些子模块项目environment-common(写一些共用的代码)
、environment-gateway(用来解析数据、把解析后的数据存到容器中发送给服务器)
、environment-server(服务器接收到数据后,把接收到的数据发送给数据库保存起来)
- 下载一个jar比较全的本地仓库
- 处理好各个项目的依赖关系:
environment-common
依赖父项目environment-parent
,environment-gateway
、environment-server
都依赖于environment-common
。- 在
environment-common
项目下创建一个环境的实体类- 在
environment-gateway
中写一个类GatheImpl
进行解析数据,并发送给服务端- 在
environment-server
中写一个类ReviceImpl
用来接收,StoreImpl
类用来给数据库发送数据
创建maven项目,配置pom文件
environment-parent
的pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.briup</groupId>
<artifactId>environment-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<!--父项目的三个子模块-->
<modules>
<module>environment-gateway</module>
<module>environment-server</module>
<module>environment-common</module>
</modules>
<!-- 需要用到的jar的版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.13</junit.version>
<druid.version>1.1.23</druid.version>
<lombok.version>1.18.12</lombok.version>
<ojdbc8.version>19.3.0.0</ojdbc8.version>
<log4j.version>1.2.17</log4j.version>
</properties>
<!-- 对依赖进行管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.oracle.ojdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>${ojdbc8.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
environment-common
的pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.briup</groupId>
<artifactId>environment-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>environment-common</artifactId>
<!--把整个项目所需要的jar全部都依赖进来,供其他两个项目使用,减少了项目jar的冗余-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.ojdbc</groupId>
<artifactId>ojdbc8</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
</dependencies>
</project>
environment-gateway
的pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.briup</groupId>
<artifactId>environment-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>environment-gateway</artifactId>
<!--在这里直接依赖environment-common就可以使用其下的所有jar-->
<dependencies>
<dependency>
<groupId>com.briup</groupId>
<artifactId>environment-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
environment-server
的pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.briup</groupId>
<artifactId>environment-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>environment-server</artifactId>
<!--在这里直接依赖environment-common就可以使用其下的所有jar-->
<dependencies>
<dependency>
<groupId>com.briup</groupId>
<artifactId>environment-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
这个文件radwtmp
就是检测到的环境数据,只写一部分。
100|101|2|16|1|3|5d606f7802|1|1516323596029
1001|101|2|16|1|3|5d606f7802|1|1516323597007
1002|101|2|16|1|3|5d646f7802|1|1516323598382
100|101|2|1280|1|3|031101|1|1516536453850
100|101|2|1280|1|3|031101|1|1516536454877
100|101|2|1280|1|3|031001|1|1516536455808
100|101|2|256|1|3|011b03|1|1516599160680
100|101|2|256|1|3|011b03|1|1516599161579
100|101|2|256|1|3|011c03|1|1516599162522
100|101|2|256|1|3|011b03|1|1516599163528
100|101|2|16|1|3|5d706fac02|1|1516323603959
...
项目开始:
Environment实体类
maven项目配置完开始写我们的environment
实体类。以上环境数据以 | 为分隔符按顺序对应以下实体类的属性(从第二个属性开始)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Environment implements Serializable{
private static final long serialVersionUID = 1L;
//环境数据编号
private long id;
//发送端的id
private String stcId;
//树莓派id
private String systemId;
//实验箱区域id
private String regionId;
//数据类型名:
//二氧化碳、光照强度、温度、湿度
private String dataType;
private String name;
//设置传感器个数
private int sensor;
//数据状态
//3 : 接受数据 , 6 : 发送数据
private byte dataStatus;
//发送来的数据
private double data;
//接收数据的标志
//1 : 成功
private byte reviceStatus;
//传感器采集数据的时间
private Date date;
}
Utiles.class工具类
写一个工具类Utiles.clss
便于后期写代码时候直接调用
//生成一个日志对象
private static final Logger logger = Logger.getLogger(Utiles.class);
/**
* 将数据备份
* @param file 备份文件的目的地
* @param data 备份的数据
* @param append 是否追加
*/
public static void store(File file,Object data,Boolean append) {
try {
if (!file.exists()) {
file.createNewFile();
}
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(file,append));
oos.writeObject(data);
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取数据
* @param file 读取数据的文档
* @param delete 是否删除源文档
* @return
*/
public static Object read(File file,Boolean delete) {
try {
if (file == null || !file.exists()) {
return null;
}
ObjectInputStream ois =
new ObjectInputStream(
new FileInputStream(file));
Object object = ois.readObject();
if (delete) {
file.delete();
}
ois.close();
logger.info("读取成功!");
return object;
}catch (EOFException e) {
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
//读取properties文件
public static Properties load(InputStream is) {
try {
if (Objects.isNull(is)) {
throw new EnvironmentException(ExceptionMessageEnum.PARM_IS_NULL.getMsg());
}
Properties properties = new Properties();
properties.load(is);
return properties;
} catch (Exception e) {
throw new EnvironmentException(e.getMessage());
}
}
//关闭数据库
public static void close(Connection connection,Statement statement,ResultSet resultSet) {
try {
if (connection != null) {
connection.close();
}
if (statement != null) {
statement.close();
}
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
异常信息类
准备一个手动抛出的自定义异常类EnvironmentException
继承自RuntimeException
,使用枚举ExceptionMessageEnum
获取异常信息
//自定义异常类
public class EnvironmentException extends RuntimeException{
private static final long serialVersionUID = 1L;
public EnvironmentException(String msg) {
super(msg);
}
public EnvironmentException() {
}
}
//枚举类
@Getter
@AllArgsConstructor
public enum ExceptionMessageEnum {
PARM_IS_NULL("参数为空");
private String msg;
}
一些配置文件
把要用到的一些文件地址,进行网络通信的东西都存放到properties文件中,方便后期进行修改和维护
SKIP_NAME=src/main/resources/reader.txt
READER_NAME=src/main/resources/radwtmp
STORE_READ=src/main/resources/storeData.txt
HOST=127.0.0.1
POST=10099
DATA_ROLL=src/main/resources/dataroll.txt
log4j日志的一些配置文件
log4j.rootLogger=debug,stdout,D
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%-5p] %l-%m%n
log4j.appender.D = org.apache.log4j.FileAppender
log4j.appender.D.File =src/main/resources/log.txt
log4j.appender.D.Append=true
log4j.appender.D.Threshold = debug
log4j.appender.D.layout =org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r] -[ %p] %m%n
环境数据解析
项目架构基本完成,开始进行数据的解析,创建解析类GatheImpl.class
public class GatheImpl implements IGathe{
//已经读取过的数据将在下次读取时跳过,这个key值对应的文件保存的是已经解析过的数据
private static String SKIP_NAME = null;
//key值对应的保存检测到环境数据的文件
private static String READER_NAME = null;
//key值对应的解析异常,将数据进行备份的文件
private static String STORE_READ = null;
//获取日志对象
private static final Logger logger = Logger.getLogger(GatheImpl.class);
//这里用雪花算法来写环境数据编号,具体不知道的可以百度一下
private static IdWorker idWorker;
//用来存储解析后的数据
private static Collection<Environment> list;
static int j= 0;
static {
idWorker = new IdWorker();
list = new ArrayList<>();
Properties load =
Utiles.load(GatheImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
SKIP_NAME = load.getProperty("SKIP_NAME");
READER_NAME = load.getProperty("READER_NAME");
STORE_READ = load.getProperty("STORE_READ");
}
/*
* 解析发送来的数据
*/
@SuppressWarnings("unused")
@Override
public Collection<Environment> gathe() throws EnvironmentException {
if(list == null) {
logger.warn("参数为空");
throw new EnvironmentException(ExceptionMessageEnum.PARM_IS_NULL.getMsg());
}
try{
//读取检测到环境的数据
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(READER_NAME)));
String string = "";
String[] split = null;
Environment environment = null;
long count = 0;
logger.info("开始读取SKIP文件");
Object read = Utiles.read(new File(SKIP_NAME), true);
//判断SKIP文件是否有数据,有的话加上,用br.skip(count)方法跳过已经保存的数据
if (!Objects.isNull(read)) {
count += (Long) read;
}
br.skip(count);
logger.info("解析器开始解析数据");
while ((string = br.readLine()) != null) {
count += string.length() + 1;
//对数据进行分割
split = string.split("\\|");
GatheImpl.analysis(split);
}
Utiles.store(new File(SKIP_NAME), count, false);
br.close();
logger.info("解析数据完成!");
return list;
} catch (Exception e) {
logger.warn("解析异常,开始备份!");
Utiles.store(new File(STORE_READ), list, false);
}
return list;
}
/*
* 解析一个字符串,返回一个environment对象
*/
private static void analysis(String[] s) {
Environment e = new Environment();
Environment e1 = new Environment();
//设置温度
//设置环境数据编号
e.setId(idWorker.nextId());
for (int i = 0; i < s.length; i++) {
choice(i, e, s);
}
list.add(e);
//设置湿度
if (s[6].length() >= 8) {
e1.setId(idWorker.nextId());
for (int i = 0; i < s.length; i++) {
choice(i, e1, s);
}
Long ll = Long.parseLong(s[6].substring(0, 4), 16);
e1.setDataType("16");
e1.setName("湿度");
e1.setData(((float)ll*0.00190735)-6);
list.add(e1);
}
}
/*
* 对数组初始化赋值
*/
private static void choice(int num,Environment e,String[] s) {
switch (num) {
case 0:
//设置发送端id
e.setStcId(s[0]);
break;
case 1:
//设置树莓派id
e.setSystemId(s[1]);
break;
case 2:
//设置实验箱区域id
e.setRegionId(s[2]);
break;
case 3:
Long l = Long.parseLong(s[6].substring(0, 4), 16);
//设置数据类型名
if ("16".equals(s[3])) {
e.setDataType("16");
e.setName("温度");
e.setData(((float)l*0.00268127)-46.85);
}else if ("256".equals(s[3])) {
e.setName("光照强度");
e.setDataType("256");
e.setData(l);
}else if ("1280".equals(s[3])) {
e.setName("CO2");
e.setDataType("1280");
e.setData(l);
}else {
e.setDataType("null");
}
break;
case 4:
//设置传感器个数
e.setSensor(Integer.valueOf(s[4]));
break;
case 5:
//设置数据状态
e.setDataStatus(Byte.valueOf(s[5]));
break;
case 6:
break;
case 7:
//设置接收数据的标志
e.setReviceStatus(Byte.valueOf(s[7]));
break;
case 8:
//设置传感器采集数据的时间
e.setDate(new java.sql.Date(Long.valueOf(s[8])));
break;
default:
System.err.println("采集错误");
break;
}
}
}
将解析后的数据以list形式发送到服务端
创建Application类
public class Application {
//IP地址
private static String HOST = null;
//端口号
private static String POST = null;
//创建日志对象
private static final Logger logger = Logger.getLogger(Application.class);
static {
Properties load =
Utiles.load(GatheImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
HOST = load.getProperty("HOST");
POST = load.getProperty("POST");
}
/*
* 客户端向服务端发送数据
*/
public static void main(String[] args) {
//使用定时器,定时向服务端发送数据
//创建带有四个线程的定时线程池对象
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
//实现ISender接口中的向服务端发送数据的方法
ISender sender = (t->{
try {
Socket socket = new Socket(HOST, Integer.valueOf(POST));
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
oos.writeObject(t);
oos.flush();
socket.close();
oos.close();
} catch (Exception e) {
logger.warn("数据发送异常");
e.printStackTrace();
}
});
//延迟1秒发送,每5秒发送一次数据
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
IGathe gather = new GatheImpl();
Collection<Environment> gathe = gather.gathe();
logger.info("客户端向服务端开始发送数据");
sender.send(gathe);
System.out.println();
}
}, 1, 5, TimeUnit.SECONDS);
}
}
服务端进行数据的接收
创建ReviceImpl
类
public class ReviceImpl implements IRevice{
//端口号
private static String POST = null;
//创建日志对象
private static final Logger logger = Logger.getLogger(ReviceImpl.class);
//创建serversocket对象
private static ServerSocket ss;
//将接受到的数据存储到list中
private static Collection<Environment> list;
static {
Properties load = Utiles.load(ReviceImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
POST = load.getProperty("POST");
POOL = Executors.newCachedThreadPool();
list = new ArrayList<>();
try {
ss = new ServerSocket(Integer.valueOf(POST));
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 服务端接收数据
*/
@SuppressWarnings("unchecked")
@Override
public Collection<Environment> revice() throws EnvironmentException {
logger.info("服务器连接中...");
try {
Socket socket = ss.accept();
logger.info("服务器连接成功");
logger.info("服务端开始接收数据");
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));
Object obj = ois.readObject();
list = (Collection<Environment>)obj;
logger.info("服务端数据接收完成");
ois.close();
socket.close();
return list;
} catch (Exception e) {
logger.warn("数据接收异常");
e.printStackTrace();
}
return list;
}
}
创建StoreImpl
类,把数据发送到数据库
/*
* 把集合中数据存储到数据库中
*/
public class StoreImpl implements IStore{
//创建日志对象
private static final Logger logger = Logger.getLogger(StoreImpl.class);
//上传数据库异常时,对象文件进行备份
private static String DATA_ROLL = null;
static {
Properties load =
Utiles.load(StoreImpl.class.getClassLoader().getResourceAsStream("properties.properties"));
DATA_ROLL = load.getProperty("DATA_ROLL");
}
@SuppressWarnings({ "null", "unchecked" })
@Override
public void store(Collection<Environment> data) throws EnvironmentException {
//判断要传的数据是否为空
if (data.size() == 0) {
logger.warn("没有数据要发送到数据库");
System.out.println();
return;
}
//读取备份文件,如果有数据,就赋给data集合,重新进行上传数据库
Object read = Utiles.read(new File(DATA_ROLL), true);
if (!Objects.isNull(read)) {
data = (Collection<Environment>) read;
}
Connection conn = null;
//创建一个解析日期的对象,获取日期在当前月份的天数。因为每条数据有对应的日期,在一个月中,每一天的数据要存储到一个表中
SimpleDateFormat day = new SimpleDateFormat("dd");
try {
Properties properties = new Properties();
properties.load(
DruidWebUtils.class.getClassLoader().getResourceAsStream("datasource.properties"));
//连接池。创建DateSource对象,用来获取数据库连接
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
int i = 0;
//获取连接
conn = dataSource.getConnection();
//设置为手动提交
conn.setAutoCommit(false);
//随机定义一个字符串,用来标识一张表(也就是一天的数据)中创建的preparedStatement对象只有一个
String dayStr = "0";
//创建一个字符串,用来存储,数据中日期解析后的天数
String dayString = "";
String sql = null;
Date date = null;
PreparedStatement ps = null;
logger.info("开始向数据库传数据...");
for (Environment environment : data) {
//获取一条数据中的日期
date = environment.getDate();
//获取日期的天数
dayString = day.format(date);
//判断相邻的两条数据是否是同一天,是的话直接存储,否则创建preparedStatement对象
if (!Objects.equals("dayStr", "dayString")) {
//如果天数不相同,则把之前的数据提交数据库后再创建新的对象
if (!Objects.isNull(ps)) {
ps.executeBatch();
ps.clearBatch();
//必须关闭否则会报错
Utiles.close(null, ps, null);
}
//把数据中解析的天数赋值给dayStr,这样下次再遇到相同的天数的数据,则直接跳过创建对象
dayStr = dayString;
//创建对象
sql = "insert into env_tab_"+dayStr+" values(?,?,?,?,?,?,?,?,?,?,?)";
ps = conn.prepareStatement(sql);
}
//对数据中的字段赋值
ps.setLong(1, environment.getId());
ps.setString(2, environment.getStcId());
ps.setString(3, environment.getSystemId());
ps.setString(4, environment.getRegionId());
ps.setString(5, environment.getDataType());
ps.setString(6, environment.getName());
ps.setInt(7, environment.getSensor());
ps.setByte(8, environment.getDataStatus());
ps.setDouble(9, environment.getData());
ps.setByte(10, environment.getReviceStatus());
ps.setDate(11, date);
ps.addBatch();
i++;
//批处理,每500条处理一次
if (i % 500 == 0) {
ps.executeBatch();
ps.clearBatch();
}
}
if (Objects.isNull(ps)) {
logger.warn("没有数据要上传");
System.out.println();
}else {
ps.executeBatch();
ps.close();
logger.info("上传完成!");
System.out.println();
}
conn.commit();
} catch (SQLException e) {
Utiles.store(new File(DATA_ROLL), data, false);
try {
conn.rollback();
System.out.println();
return;
} catch (SQLException e1) {
e1.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
Utiles.close(conn, null, null);
}
}
}
调用方法开启服务端接受数据,把数据发送到数据库
创建Application
类
public class Application {
public static void main(String[] args) {
//因为客户端为定时发送,所以服务端一直开启,随时准备接收数据
while (true) {
IRevice iRevice = new ReviceImpl();
Collection<Environment> list = iRevice.revice();
IStore iStore = new StoreImpl();
iStore.store(list);
}
}
}
下一篇: PHP Wap开发环境配置_PHP教程