要完成这样的功能,不是很复杂,就是有点多,不想每个都写一个。
而因为他们是其实有规则的,比如根据总的如申请购买数量统计近7日的数据,然后根据不同的产品同样统计近7日的数据。而且产品只有产品1,产品2,产品3,产品4,这四种。所以想根据泛型来完成,因为他们的类型不是完全一样,上面的数量用的是Long类型,而下面是的金额Double(对于金额没有用BigDecimal,因为用这个类型,显示不出来),所以初始化的时候我废了一番功夫。
文中只会显示主要的代码和结果,而且都是我修改过的,不是原本的代码。
由于这个是竖着展示的,所以我也是按着竖着的方式写的velocity模板引擎。以前这种的,我会写很复杂的循环来完成这样的功能,但是由于我总是不善于保存代码,每次都是新写一遍,我觉得太费劲了,所以想到和横着写的方式一样,竖着把这个写好,只是这样觉得实体不是很好定义。velocity模板的语法很类似html,只是所有的数据,需要先用set放置到变量中,然后才能使用。
<html>
<body>
<h1>数据如下</h1>
<div>
<table cellspacing="16">
#set($record = $result.record)
#if ($record)
#set($productDetailList = $record.productDetailList)
#if ($productDetailList)
#set($countList = $productDetailList[0].totalCountList)
#if ($countList)
<tr align="left">
<td align="left">日期</td>
#foreach ($stat in $countList)
<td align="left">$stat.date</td>
#end
</tr>
#end
#foreach ($product in $productDetailList)
#set($countList = $product.totalCountList)
#if ($countList)
<tr align="left">
<td align="left">$product.title</td>
#foreach ($stat in $countList)
<td align="left">$stat.count</td>
#end
</tr>
#end
#set($countList = $product.productOneCountList)
#if ($countList)
<tr align="right">
<td>产品1$product.title</td>
#foreach ($stat in $countList)
<td align="left">$stat.count</td>
#end
</tr>
#end
#set($countList = $product.productTwoCountList)
#if ($countList)
<tr align="right">
<td>产品2$product.title</td>
#foreach ($stat in $countList)
<td align="left">$stat.count</td>
#end
</tr>
#end
#set($countList = $product.productThreeCountList)
#if ($countList)
<tr align="right">
<td>产品3$product.title</td>
#foreach ($stat in $countList)
<td align="left">$stat.count</td>
#end
</tr>
#end
#set($countList = $product.productFourCountList)
#if ($countList)
<tr align="right">
<td>产品4$product.title</td>
#foreach ($stat in $countList)
<td align="left">$stat.count</td>
#end
</tr>
#end
#end
#end
#end
</table>
<br><br>
</div>
</body>
</html>
对应的结构就是这样,从map中取出record,也就是所有的记录,该record中只有productDetailList产品详情列表信息,他原本还有其他属性的,但是这里不需要被我删了。而产品详情List,每个都是一个实体,该实体又是由5个List和一个title组成。title就是用于在界面上显示名字而已。其中每个List,都是近7日的数据,包含日期和数据。
产品详情中的实体代码
/**
* 产品详情VO
*
*
* @author
*/
public class ProductDetailVO<T> {
/**
* 标题
*/
private String title;
/**
* 总的数据详情
*/
private List<DateCountPO<T>> totalCountList;
/**
* 产品1的数据详情
*/
private List<DateCountPO<T>> productOneCountList;
/**
* 产品2的数据详情
*/
private List<DateCountPO<T>> productTwoCountList;
/**
* 产品3的数据详情
*/
private List<DateCountPO<T>> productThreeCountList;
/**
* 产品4的数据详情
*/
private List<DateCountPO<T>> productFourCountList;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<DateCountPO<T>> getTotalCountList() {
return totalCountList;
}
public void setTotalCountList(List<DateCountPO<T>> totalCountList) {
this.totalCountList = totalCountList;
}
}
DateCountPO也是泛型类
/**
* 日期和数量
*
* @author
*/
public class DateCountPO<T> {
/**
* 日期
*/
private String date;
/**
* 数量
*/
private T count;
public DateCountPO() {}
public DateCountPO(String date, T count) {
this.date = date;
this.count = count;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public T getCount() {
return count;
}
public void setCount(T count) {
this.count = count;
}
}
SummaryDayVO就是只有
productDetailList一个属性;
public class SummaryDayVO {
/**
* 各产品信息详情
*/
private List<ProductDetailVO> productDetailList;
public List<ProductDetailVO> getProductDetailList() {
return productDetailList;
}
public void setProductDetailList(List<ProductDetailVO> productDetailList) {
this.productDetailList = productDetailList;
}
}
获取日报的代码就是这样,
先是根据开始和结束日期获取7日中所有的日期,这样假使GoupBy之后发现数据库中没有当天的记录,我用这个日期来控制,没有记录的默认为0或者0.0;
然后就是total和ByProduct的SQL分别从数据库中获取对应的数据,然后用splitProductDetail把这数据库中的数据拆分成我需要的产品详情实体。最后放到SummaryDayVO实体中,这就是我们需要的结果了。
/**
* 获取日报中需要的数据;
* @param startDate
* @param endDate
* @return
*/
private SummaryDayVO queryDailyReportData(Date startDate, Date endDate) {
//开始到结束日期的每一天的列表,用于补齐数据
List<Date> dateList = Dates.getBetweenDates(startDate,endDate,false);
SummaryDayVO dayVO = new SummaryDayVO();
//所有产品
List<ProductDetailVO> productDetailVOList = Lists.newArrayList();
//申请购买
List<Map<String,Object>> orderCheck = mapper.queryApplyByTotal(startDate,endDate);
//申请购买(按产品划分)
List<Map<String,Object>> orderCheckByProduct = mapper.queryApplyByProduct(startDate,endDate);
//申请购买产品
ProductDetailVO<Long> orderCheckProductDetailVO = splitProductDetail(orderCheck,orderCheckByProduct,dateList,"申请购买","count",0l);
productDetailVOList.add(orderCheckProductDetailVO);
//成功订单
List<Map<String,Object>> orderSuccessInfo = mapper.queryOrderSuccessInfo(startDate,endDate);
//成功订单(按产品)
List<Map<String,Object>> orderSuccessInfoByProduct = mapper.queryOrderSuccessInfoByProduct(startDate,endDate);
//成功单数,成功金额
ProductDetailVO<Long> orderSuccessCountProductDetailVO = splitProductDetail(orderSuccessInfo,orderSuccessInfoByProduct,dateList,"成功单数","count",0l);
ProductDetailVO<Double> orderSuccessAmountProductDetailVO = splitProductDetail(orderSuccessInfo,orderSuccessInfoByProduct,dateList,"成功金额","amount",0.0);
productDetailVOList.add(orderSuccessCountProductDetailVO);
productDetailVOList.add(orderSuccessAmountProductDetailVO);
dayVO.setProductDetailList(productDetailVOList);
//记录条数
LOGGER.info("get data end");
//0填充缺失数据
return fullWithZero(dayVO, Dates.getBetweenDates(startDate,endDate,false));
}
而从数据库中获取需要的数据,如
queryOrderSuccessInfo,queryO
rderSuccessInfoByProduct,如成功订单和成功订单分产品,根据更新时间获取每天的成功单数和成功金额数。其中,product 产品1,2,3,4是在SQL中已经定义好的,而且返回的count就是数量,amount就是金额也是定义好的。
set @startDate := '2018-01-12';
set @endDate := '2018-01-17';
-- 总的
SELECT
DATE_FORMAT( dt.upadte_time, '%Y-%m-%d' ) AS date,
count( 1 ) AS count,
ROUND(sum( amount ) / 100,2) AS amount
FROM
database1.table1 dt
WHERE
1 = 1
AND dt.upadte_time >= @startDate
AND dt.upadte_time < @endDate
GROUP BY
1;
-- 分产品的
SELECT
DATE_FORMAT( dt.upadte_time, '%Y-%m-%d' ) AS date,
CASE
WHEN product_code = 1 THEN 'productOne'
WHEN product_code = 2 THEN 'productTwo'
WHEN product_code = 3 THEN 'productThree'
ELSE 'productFour'
END AS product,
count( 1 ) AS count,
ROUND(sum( amount ) / 100,2) AS amount
FROM
database1.table1 dt
WHERE
1 = 1
AND dt.upadte_time >= @startDate
AND dt.upadte_time < @endDate
GROUP BY
1;
splitProductDetail,就是拆分产品详情,拆分成我需要的实体。其中title是传递过来的,然后filed字段也是提前指定的,指定我从totalInfo和infoByProduct这两个List<Map>到底取那个key。而由于T是不能初始化的,我又有Long和Double两种不同的类型,所以最终决定初始化的值也是传过来的,这样我只用定义T initCount就行。产品数据的时候,若没有查到则设置为初始化的值。也就是说,这就是说我在用0填充缺失的数据。
/**
* 将totalInfo和infoByProduct,处理成以这个ProductDetailVO,
* @param totalInfo 总的信息
* @param infoByProduct 根据产品的分的信息
* @param dateList 所有日期列表,每天一个
* @param title 标题
* @param filed 字段(从数据库中需要读取的数据)
* @param initCount 初始化值(因为T我不知道如何初始化,所以传一个初始化的值过来)
* @param <T> 泛型
* @return productDetailVO
*/
private <T extends Number> ProductDetailVO<T> splitProductDetail(List<Map<String, Object>> totalInfo,List<Map<String, Object>> infoByProduct, List<Date> dateList,String title, String filed,T initCount) {
//最终的结果
ProductDetailVO<T> resultProductVO = new ProductDetailVO<>();
//设置好中文标题
resultProductVO.setTitle(title);
List<DateCountPO<T>> totalCountList = Lists.newArrayList();
List<DateCountPO<T>> productOneCountList = Lists.newArrayList();
List<DateCountPO<T>> productTwoCountList = Lists.newArrayList();
List<DateCountPO<T>> productThreeCountList = Lists.newArrayList();
List<DateCountPO<T>> productFourCountList = Lists.newArrayList();
//遍历所有天数,防止为空的数据的存在
for (Date sevenDate : dateList) {
String date = Dates.formatDate(sevenDate);
DateCountPO<T> totalCount = new DateCountPO<>(date,initCount);
DateCountPO<T> productOneCount = new DateCountPO<>(date,initCount);
DateCountPO<T> productTwoCount = new DateCountPO<>(date,initCount);
DateCountPO<T> productThreeCount = new DateCountPO<>(date,initCount);
DateCountPO<T> productFourCount = new DateCountPO<>(date,initCount);
//获取总的
Map<String, Object> total = totalInfo.stream().filter(o -> o.get("date").toString().equals(date)).findFirst().orElse(null);
//获取各个产品,productOne,productTwo,productThree,productFour的值,没有则为null;
Map<String, Object> productOne = infoByProduct.stream().filter(o -> o.get("date").toString().equals(date) && o.get("product").toString().equals("productOne")).findFirst().orElse(null);
Map<String, Object> productTwo = infoByProduct.stream().filter(o -> o.get("date").toString().equals(date) && o.get("product").toString().equals("productTwo")).findFirst().orElse(null);
Map<String, Object> productThree = infoByProduct.stream().filter(o -> o.get("date").toString().equals(date) && o.get("product").toString().equals("productThree")).findFirst().orElse(null);
Map<String, Object> productFour = infoByProduct.stream().filter(o -> o.get("date").toString().equals(date) && o.get("product").toString().equals("productFour")).findFirst().orElse(null);
//为空,则用声明时的默认值;
if(total != null) {
totalCount.setCount((total.get(filed) != null) ? (T)total.get(filed) : initCount);
}
//获取字段
if(productOne != null) {
productOneCount.setCount((productOne.get(filed) != null) ? (T)productOne.get(filed) : initCount);
}
if(productTwo != null) {
productTwoCount.setCount(productTwo.get(filed) != null ? (T)productTwo.get(filed) : initCount);
}
if(productThree != null) {
productThreeCount.setCount(productThree.get(filed) != null ? (T)productThree.get(filed) : initCount);
}
if(productFour != null) {
productFourCount.setCount(productFour.get(filed) != null ? (T)productFour.get(filed) : initCount);
}
totalCountList.add(totalCount);
productOneCountList.add(productOneCount);
productTwoCountList.add(productTwoCount);
productThreeCountList.add(productThreeCount);
productFourCountList.add(productFourCount);
}
resultProductVO.setTotalCountList(totalCountList);
resultProductVO.setProductOneCountList(productOneCountList);
resultProductVO.setProductTwoCountList(productTwoCountList);
resultProductVO.setProductThreeCountList(productThreeCountList);
resultProductVO.setProductFourCountList(productFourCountList);
return resultProductVO;
}
其中Dates.getBetweenDates(startDate,endDate,false)的getBetweenDates这个静态方法就是获取两个日期之间所有的日期,如2017-01-21到2017-01-24,则最终返回2017-01-21,
2017-01-22,2017-01-23,2017-01-24的List集合。若选择不包含结束日期(false),则去掉2017-01-24,只有21,22,23这3天;
/**
* 获取两个日期之间的日期,包含开始,可以控制是否包含结束日期
* 若两个日期只差一天,或者就是当天,则是保留开始日期
* @param start 开始日期
* @param end 结束日期
* @param isContainEndDate 是否包含结束时间
* @return 日期集合
*/
public static List<Date> getBetweenDates(Date start, Date end,boolean isContainEndDate) {
List<Date> result = new ArrayList<>();
//若开始比结束要晚,则返回空list;
if(start.after(end)) {
return Lists.newArrayList();
}
//先添加开始日期;
result.add(start);
//存储临时的开始和结束日期
Calendar tempStart = Calendar.getInstance();
tempStart.setTime(start);
Calendar tempEnd = Calendar.getInstance();
tempEnd.setTime(end);
//若开始日期比结束日期早,则依次添加;
//若相等,则算了;
while (tempStart.before(tempEnd)) {
//开始日期+1;
tempStart.add(Calendar.DAY_OF_YEAR, 1);
result.add(tempStart.getTime());
}
//若不包含结束日期,且记录多余2条,则去掉结束日期
if(!isContainEndDate && result.size() > 1) {
result.remove(result.size()-1);
}
return result;
}
以上就是我用泛型部分的相关代码了,核心部分就是splitProductDetail这里了,最开始我是写了3,4个这样的方法,代码内容都差不多,就是最终返回的是一个实体还是两个实体的区别(可以看到申请购买是只有count的,只有一个实体,而成功订单是有count和amount的,是有两个实体的),以及amount和count是0.0和0的区别。但是就是这些的区别导致我一开始不知道如何将他们合成一个方法。后来写完之后,学习了一下泛型的使用,就试着这么用了,在泛型的使用上,我一直试图给T初始化,后来发现实在不行,于是决定还是传参数,最终写出了这个还算通用的方法,虽然参数有些多。
勉励之~