写业务当中感觉最开始就是新建实体类和表,然后看需求,然后分析怎么去CUID操作,然后就开始去写SQL语句,使用Mybatis可以比较灵活的去解决ORM。这次挑战是对小时粒度的处理,解决方案是采用了部分的重构,提取方法和清晰的数据结构定义。
- 首先看一张表的结构:
describe [tableName];
已经存在表中的数据如下:
按照两个粒度,分别是日和小时。
日就是统计每天的人口数,返回1-24小时的人数,虽然大数据组可以提供24小时的数据,但是为了保证测试,我在业务里添加了该时间可能存在null的情况默认添0.因此返回给前端的总是24个横坐标。
order by 字段1,字段2.这样先按日排序在小时排序,这样方便处理。
select * from t_mon_data_pass_person_hour WHERE area_id = 7000 and date_time >= '2014-01-01' and date_time <= '2015-09-01' order by date_time,hour_time;
首先获取参数startTime,endTime,areaId和粒度:
//获取参数
String areaId = ParamUtil.getFilteredParameter(request, "areaId", 0, "7000");
String startTime = ParamUtil.getFilteredParameter(request, "startDate", 0, "2013-01-01");
String endTime = ParamUtil.getFilteredParameter(request, "endDate", 0, "2016-01-01");
Integer kpiCycle = Integer.valueOf(ParamUtil.getFilteredParameter(request, "kpiCycle", 0, "3"));
由于从timestamp时间戳的格式不便于处理,我需要去定义个我想要的格式,去转换,使用到了SimpleDateFormat类:
//定义查询map
HashMap<String,String> queryMap = new HashMap<String,String>();
queryMap.put("areaId", areaId);
queryMap.put("startTime", startTime);
queryMap.put("endTime", endTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//定义格式
然后判断是否为精确到时:
if(kpiCycle == Constants.VISITOR_COUNT_UNIT_HOUR)
这里dao层接口的方法定义了两个:
getHourPassPersonList(queryMap) getDayPassPersonList(queryMap)
第一个会查询出所有的记录,第二个会采用聚合函数,合并同一天的人口数。
对应sqlmap分别为:
<select id="getHourPassPersonList" parameterType="java.util.Map" resultMap="BaseResultMap">
select * from t_mon_data_pass_person_hour
<where>
<if test="areaId != null">
area_id = #{areaId}
</if>
<if test="startTime != null">
<![CDATA[and date_time >= #{startTime}]]>
</if>
<if test="endTime != null">
<![CDATA[and date_time <= #{endTime}]]>
</if>
</where>
order by date_time,hour_time
</select>
注意时间和小时的排序。
<select id="getDayPassPersonList" parameterType="java.util.Map" resultMap="BaseResultMap">
select date_time,sum(population) as population
from t_mon_data_pass_person_hour
<where>
<if test="areaId != null">
area_id = #{areaId}
</if>
<if test="startTime != null">
<![CDATA[and date_time >= #{startTime}]]>
</if>
<if test="endTime != null">
<![CDATA[and date_time <= #{endTime}]]>
</if>
group by date_time
order by date_time
</where>
</select>
再回到action里:
我们获取所有记录并得到所有日期。
List<PassPerson> passPersonList = passPersonManager.getHourPassPersonList(queryMap);
List<Date> daysList = getDateTimeList(passPersonList);
但是因为表里可能存在重复的日期数,因此有必要提取不重复的顺序排列的日期,我这里的getDateTimeList就是这个方法,如下定义:
//取出所有日期
private List<Date> getDateTimeList(List<PassPerson> list)
{
List<Date> dateList = new ArrayList<Date>();
for(PassPerson p : list)
{
if(!dateList.contains(p.getDateTime()))
{
dateList.add(p.getDateTime());
}
}
return dateList;
}
图标是显示多天24时,key-value分别存储日期和对应的人数。
//定义结果
Map<String,List<Integer>> hourGraphMap = new TreeMap<String,List<Integer>>();
接下来去遍历所有日期,然后将对应日期所对应的24小时人数放到一个列表里,如果改小时没记录(测试可能用到,实际业务中会用记录为0),会用0去补齐:
for(Date date : daysList)
{
List<Integer> hoursList = new ArrayList<Integer>();
int time = 1;//初始化时间
for(PassPerson p : passPersonList)
{
if(sdf.format(p.getDateTime()).equals(sdf.format(date)))
{
//如果该小时存在记录
if(time == p.getHourTime())
{
hoursList.add(p.getPopulation());
}
//如果没时间记录,填0
else
{
for(int i = time;i < p.getHourTime();i ++)
{
hoursList.add(0);
}
hoursList.add(p.getPopulation());
time = p.getHourTime() + 1;
}
}
}
//0补齐
while(time < 24)
{
hoursList.add(0);
time ++;
}
//格式转换
String datetime = sdf.format(date);
hourGraphMap.put(datetime, hoursList);
}
然后还需要获取小时中的最大人数:
//小时最大人数峰值
Integer hourMaxPopulation = getMaxPopulation(passPersonList);
因为在日的粒度下也需要使用,因此方法采用了重构。
//获得查出列表峰值
private Integer getMaxPopulation(List<PassPerson> list)
{
int max = 0;
for(PassPerson p : list)
{
max = p.getPopulation() > max ? p.getPopulation() : max;
}
return max;
}
峰值的时间可能是多个,因此定义列表去保存:
//获得最大峰值时的格式:yyyy-MM-dd H时
List<String> maxPopulationTimeList = new ArrayList<String>();
for(PassPerson p : passPersonList)
{
if(p.getPopulation().equals(hourMaxPopulation))
{
Date date = p.getDateTime();
//格式转换
String dateOfString = sdf.format(date);
maxPopulationTimeList.add(dateOfString + " " + p.getHourTime().toString()+"时");
}
}
后面的最小值,总人数,平均人口都采用了重构去解决,这样在日的粒度下去直接调用方法,简化了代码。在求平均的时候注意类型转换。
//平均每小时的人口
Integer avgPopulationOfHour = 0;
if(sumPopulation != 0)
{
Float f = (float)sumPopulation;
Float f2 = (float)getDaysNumber(passPersonList) * 24;
avgPopulationOfHour = (int) (f / f2);
}
最后测试用例:
pass-person!display.action?areaId=7000&startDate=2014-01-01&endDate=2015-10-01&kpiCycle=0
返回:
{"hourMaxPopulation":400,"minPopulationTime":["2015-08-01 3时"],"maxPopulationTime":["2015-09-02 4时"],"avgPopulationOfHour":8,"hourMinPopulation":22,"topPopulation":400,"hourGraphMap":{"2015-08-01":[0,200,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"2015-08-02":[0,0,0,0,0,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"2015-09-01":[200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"2015-09-02":[0,0,0,400,33,0,0,0,0,0,0,44,0,0,0,0,0,0,0,0,0,0,0,0],"2015-09-10":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,123]}}
最开始tomcat一直报错无法注入,后来发现是服务层的注解没写。填上注解问题解决。