3.1 数据生产模块
此情此景,对于该模块的业务,即数据生产过程,一般并不会让你来进行操作,数据生产是一套完整且严密的体系,这样可以保证数据的安全性。
但是如果涉及到项目的一体化方案的设计(数据的产生、存储、分析、展示),则必须清楚每一个环节是如何处理的,包括其中每个环境可能隐藏的问题, 数据结构,数据内容可能出现的问题。
3.1.1 数据结构
想象一次完整通话一般会产生哪些数据:
主要信息:主叫手机号, 被叫手机号, 开始建立通话的时间(通话时间戳), 通话时长
其他一些信息: 主叫位置(主叫所在基站), 被叫位置(被叫所在基站) ...
这些数据都应该是用户在打电话的时候生成的.
对于本项目我们使用 Java 代码来生成上述的
3.1.2 编写代码
创建 Java 集合类存放模拟的电话号码和联系人;
随机选取两个手机号码当作“主叫”与“被叫”(注意判断两个手机号不能重复),产出
call1
与call2
字段数据;创建随机生成通话建立时间的方法,可指定随机范围,最后生成通话建立时间,产出
date_time
字段数据随机生成一个通话时长,单位:秒,产出
duration
字段数据将产出的一条数据拼接封装到一个字符串
使用 IO 操作将产出的一条通话数据写入到本地文件中
创建 module: data-producer
类: com.atguigu.telecom.dataproducer.Data
这个类存储我们的电话号码和姓名
package com.atguigu.telecom.dataproducer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Data {
/**
* 电话号码列表
*/
public final static List<String> phones;
/**
* 存储联系人: 电话号码->姓名
*/
public final static Map<String, String> contacts;
/**
* 初始化一些我们需要的联系人信息
*/
static {
phones = new ArrayList<>();
contacts = new HashMap<>();
phones.add("15369468720");
phones.add("19920860202");
phones.add("18411925860");
phones.add("14473548449");
phones.add("18749966182");
phones.add("19379884788");
phones.add("19335715448");
phones.add("18503558939");
phones.add("13407209608");
phones.add("15596505995");
phones.add("17519874292");
phones.add("15178485516");
phones.add("19877232369");
phones.add("18706287692");
phones.add("18944239644");
phones.add("17325302007");
phones.add("18839074540");
phones.add("19879419704");
phones.add("16480981069");
phones.add("18674257265");
phones.add("18302820904");
phones.add("15133295266");
phones.add("17868457605");
phones.add("15490732767");
phones.add("15064972307");
contacts.put("15369468720", "李雁");
contacts.put("19920860202", "卫艺");
contacts.put("18411925860", "仰莉");
contacts.put("14473548449", "陶欣悦");
contacts.put("18749966182", "施梅梅");
contacts.put("19379884788", "金虹霖");
contacts.put("19335715448", "魏明艳");
contacts.put("18503558939", "华贞");
contacts.put("13407209608", "华啟倩");
contacts.put("15596505995", "仲采绿");
contacts.put("17519874292", "卫丹");
contacts.put("15178485516", "戚丽红");
contacts.put("19877232369", "何翠柔");
contacts.put("18706287692", "钱溶艳");
contacts.put("18944239644", "钱琳");
contacts.put("17325302007", "缪静欣");
contacts.put("18839074540", "焦秋菊");
contacts.put("19879419704", "吕访琴");
contacts.put("16480981069", "沈丹");
contacts.put("18674257265", "褚美丽");
contacts.put("18302820904", "孙怡");
contacts.put("15133295266", "许婵");
contacts.put("17868457605", "曹红恋");
contacts.put("15490732767", "吕柔");
contacts.put("15064972307", "冯怜云");
}
}
类: com.atguigu.telecom.dataproducer.bean.CallRecord
JavaBean类, 表示生产的每条记录对象.
并且在构造函数内计算出来了需要的数据.
package com.atguigu.telecom.dataproducer.bean;
import com.atguigu.telcom.util.Util;
import com.atguigu.telecom.dataproducer.Data;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* 表示一条通话记录
*/
public class CallRecord {
/**
* 电话号码1
*/
private String call1;
/**
* 电话号码2
*/
private String call2;
/**
* 通话建立建立时间
*/
private long startTime;
/**
* 通话时长
*/
private String duration;
/**
* 通话类型:
* 站在电话号码1的角度是主叫还是被叫
* <p>
* 0 : 表示主叫
* 1: 表示被叫
*/
private int flag = 0;
/**
* 时间格式化对象
*/
private SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
/**
* 随机生成:call1, call2, 通话建立时间, 通话时长, flag = 0 默认call1是主叫
*/
public CallRecord() {
//1. 生成call1和call2. 还要保证call1和call2不相等
String[] calls = produceCalls();
setCall1(calls[0]);
setCall2(calls[1]);
//2. 随机生成通话家建立时间(时间戳)
setStartTime(produceRandomTime());
//3. 随机生成通话时长(至少5s)
setDuration(produceRandomDuration());
//4. flag 设置为0
setFlag(0);
}
/**
* 生成随机的时长(s)
* 为了后期数据的分析方便, 我们格式化通话时长的字符串的长度为4位
* <p>
* 比如: 12 -> "0012"
* 140 -> '014'
* 1500 -> '1500'
*
* @return
*/
private String produceRandomDuration() {
int duration = Util.randomInt(5, 6000);
DecimalFormat format = new DecimalFormat("0000");
return format.format(duration);
}
/**
* 生成随机的时间戳
*
* @return
*/
private long produceRandomTime() {
// 为了业务需求, 我们只生成2018-1-1 到 2020-1-1之间的任意时间戳
Calendar from = Calendar.getInstance();
from.set(2018, 0, 1, 0, 0, 0);
Calendar to = Calendar.getInstance();
to.set(2020, 0, 1, 0, 0, 0);
return Util.randomLong(from.getTimeInMillis(), to.getTimeInMillis());
}
/**
* 随机生成两个电话
*
* @return
*/
private String[] produceCalls() {
List<String> phones = Data.phones;
String[] calls = new String[2];
// 随机生成两个电话号码: 生成两个不等的下标, 然后从集合中取出电话号码
int index1 = Util.randomInt(0, phones.size() - 1);
int index2;
while (true) {
index2 = Util.randomInt(0, phones.size() - 1);
if (index1 != index2) break;
}
calls[0] = phones.get(index1);
calls[1] = phones.get(index2);
return calls;
}
public String getCall1() {
return call1;
}
public void setCall1(String call1) {
this.call1 = call1;
}
public String getCall2() {
return call2;
}
public void setCall2(String call2) {
this.call2 = call2;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public String getDuration() {
return duration;
}
public void setDuration(String duration) {
this.duration = duration;
}
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
/**
* 返回的就是将来每条记录的格式
* call1,startTime,call2,duration,flag
* <p>
* 输出字符串的时候, 我们把startTime 换成 2018-02-03 10:22:10 这样的格式
*
* @return
*/
@Override
public String toString() {
return this.call1 + ","
+ dateFormatter.format(new Date(this.startTime)) + ","
+ this.call2 + ","
+ this.duration + ","
+ this.flag;
}
}
类: com.atguigu.telecom.dataproducer.ProductLog
主类
package com.atguigu.telecom.dataproducer;
import com.atguigu.telecom.dataproducer.bean.CallRecord;
import java.io.*;
public class ProductLog {
/**
* 把日志(通话记录)写入本地磁盘
* <p>
* 一条通话记录, 我们向磁盘中写入一次
* <p>
* 通过循环的方式, 持续的写入到文件中.
*/
public void writLogs() throws IOException, InterruptedException {
String filePath = "/opt/module/telecom/calls.csv";
FileWriter writer = new FileWriter(filePath);
while (true) {
writer.write(produceOneLog() + "\n");
writer.flush();
Thread.sleep(1000);
}
}
/**
* 随机产生一条通话记录
* <p>
* 通话记录的字符串表示形式
*/
public String produceOneLog() {
CallRecord record = new CallRecord();
return record.toString();
}
public static void main(String[] args) throws IOException, InterruptedException {
ProductLog productLog = new ProductLog();
productLog.writLogs();
}
}
工具类Util
package com.atguigu.telcom.util;
import sun.applet.Main;
import java.util.Random;
public class Util {
/**
* 随机数生成器对象
*/
private static Random r = new Random();
/**
* 生成一个大于等于form, 小于等于to的随机int整数 [from, to]
* from 必须小于等于 to
*
* @param from
* @param to
*/
public static int randomInt(int from, int to) {
if (from > to) throw new RuntimeException("参数错误, " + from + " 大于" + to + "要求from必须小于等于to");
return r.nextInt(to - from + 1) + from;
}
public static long randomLong(long from, long to) {
if (from > to) throw new RuntimeException("参数错误, " + from + " 大于" + to + "要求from必须小于等于to");
return Math.abs(r.nextLong()) % (to - from + 1) + from;
}
}
注意:
- 为了在其他项目中使用方便, 可以把工具类放入在专门的子项目中.
3.1.3 打包到 Linux 进行测试
创建目录/opt/module/telecom
把本项目下用到的文件都放在此目录下.
把打包好的文件copy到该目录下.
执行 jar 包文件下的主类:
java -Djava.ext.dirs=/opt/module/telecom -cp producer.jar com.atguigu.telecom.dataproducer
.ProductLog
说明:
如果依赖了其他的子项目则需要添加参数:
-Djava.ext.dirs=/opt/module/telecom
, 如果没有则不需要添加如果有依赖其他的子项目, 也需要把其他的子项目打包放在
-Djava.ext.dirs
指定的目录下.-cp
等价于-classpath
3.1.4 编写 shell 脚本
为了以后生成数据方便, 则可以把上面的命令放在脚本内.
cd /opt/module/telecom
vim producer.sh
# 上面的指令放入文件中
java -Djava.ext.dirs=/opt/module/telecom -cp producer.jar com.atguigu.telecom.dataproducer
.ProductLog
# 修改文件权限
chmod 777 producer.sh
# 执行脚本
./producer.sh