基于SpringMVC +前台页面基于bootstrap 的 echats+jquery.slider 堆积图

实现要点

1、基于jquery.slider 动态设置series,数据基于前台动态渲染。

2、legend的动态创建。


效果图:




JSP页面源码:

<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%long version = java.lang.System.currentTimeMillis();%>
<!DOCTYPE html>

<!--[if IE 8]> <html lang="en" class="ie8 no-js"> <![endif]-->

<!--[if IE 9]> <html lang="en" class="ie9 no-js"> <![endif]-->

<!--[if !IE]><!--> <html lang="en" class="no-js"> <!--<![endif]-->

<!-- BEGIN HEAD -->
<head>
    <meta charset="utf-8" />
    <title>APP统计  | 神州专车</title>
    <link href="${baseContextPath}static/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/bootstrap-responsive.min.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/style-metro.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/style.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/style-responsive.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/uniform.default.css" rel="stylesheet" type="text/css"/>
    <!-- END GLOBAL MANDATORY STYLES -->

    <!-- BEGIN PAGE LEVEL STYLES -->
    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/css/select2_metro.css" />
    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/css/chosen.css" />
    <link href="${baseContextPath}static/css/jquery.gritter.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/fullcalendar.css" rel="stylesheet" type="text/css"/>
    <link href="${baseContextPath}static/css/jqvmap.css" rel="stylesheet" type="text/css" ${baseContextPath}static="screen"/>
    <link href="${baseContextPath}static/css/jquery.easy-pie-chart.css" rel="stylesheet" type="text/css" ${baseContextPath}static="screen"/>
    <!-- END PAGE LEVEL STYLES -->
    <link rel="shortcut icon" href="${baseContextPath}static/image/ucar/favicon.ico" />

    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/css/datepicker.css" />
    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/css/daterangepicker.css" />
    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/css/datetimepicker.css" />
    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/css/timepicker.css" />
    <link rel="stylesheet" type="text/css" href="${baseContextPath}static/plugins/bootstrap-slider4.4.0/bootstrap-slider.min.css" />
    <script type="text/javascript">var BATH_PATH='${baseContextPath}';</script>
    <style type="text/css">
    /*以下样式重写slider的样式*/
    .slider-selection{background:#e48d0c;}
	.slider-track{background-image: linear-gradient(to bottom,#BABABA 0,#BABABA 100%);}
	.slider-handle{width:10px;}
    </style>
</head>

<!-- END HEAD -->

<!-- BEGIN BODY -->

<body>
<!-- search layout begin -->
<div class="row-fluid">
    <div class="span12">
        <!-- BEGIN VALIDATION STATES-->
        <div class="portlet box blue">
            <div class="portlet-title">
                <div class="caption"><i class="icon-reorder"></i>查询条件</div>
            </div>
            <div class="portlet-body form">
                <!-- BEGIN FORM-->
                <form id="searchForm" action="${baseContextPath}retentionAnalysis/loadForRetention" class="form-horizontal">
                    <input id="appVersionList" name="appVersionList" value="${appVersion}" type="hidden"/>
                    <input id="dataType" name="dataType" value="${empty searchObj.dataType ? 'JDZ' : searchObj.dataType}" type="hidden"/>
                    <input id="timeType" name="timeType" value="${empty searchObj.timeType ? 'day' : searchObj.timeType}" type="hidden"/>
                    <input id="from" name="from" value="${empty searchObj.from ? 0 : searchObj.from}" type="hidden"/>
                    <input id="to" name="to" value="${empty searchObj.to ? 31 : searchObj.to}" type="hidden"/>
                    <!--/row-->
                    <div class="row-fluid">
                        <div class="span4 ">
                            <div class="control-group">
                                <label class="control-label">开始时间</label>
                                <div class="controls">
                                    <input id="beginTime" name="beginTime" size="16" type="text" value="${searchObj.beginTime }" readonly class="m-wrap span12 form_dateday">
                                </div>
                            </div>
                        </div>
                        <!--/span-->
                        <div class="span4 ">
                            <div class="control-group">
                                <label class="control-label">结束时间</label>
                                <div class="controls">
                                    <input id="endTime" name="endTime" size="16" type="text" value="${searchObj.endTime }" readonly class="m-wrap span12 form_dateday">
                                </div>
                            </div>
                        </div>
                        <!--/span-->
                    </div>
                    <!--/row-->
                    <div class="row-fluid">
                        <div class="span4 ">
                            <div class="control-group">
                                <label class="control-label">客户端类型<span class="required"></span></label>
                                <div class="controls select2-wrapper">
                                    <select id="form_2_select1" class="span12" name="appType" οnchange="clkAppType()">
                                        <option value=""></option>
                                        <option value="D_android" <c:if test="${searchObj.appType=='D_android'}">selected="selected" </c:if> >神州专车-司机端</option>
                                        <option value="M_iOS" <c:if test="${searchObj.appType=='M_iOS'}">selected="selected" </c:if> >神州专车-IOS</option>
                                        <option value="M_android" <c:if test="${searchObj.appType=='M_android'}">selected="selected" </c:if> >神州专车-android</option>
                                    </select>
                                </div>
                            </div>
                        </div>
                        <div class="span4 ">
                            <div class="control-group">
                                <label class="control-label">APP版本<span class="required"></span></label>
                                <div class="controls select2-wrapper">
                                    <select id="form_2_select2" class="span12" name="appVersion">
                                        <option value=""></option>
                                        <c:forEach var="version" items="${appVersion}" varStatus="status">
                                            <option value="${version }" <c:if test="${searchObj.appVersion==version}">selected="selected" </c:if> >${version }</option>
                                        </c:forEach>
                                    </select>
                                </div>
                            </div>
                        </div>
                        <div class="span4 ">
                            <div class="control-group">
                                <label class="control-label">下载渠道<span class="required"></span></label>
                                <div class="controls select2-wrapper">
                                    <select id="form_2_select3" class="span12" name="appChannel">
                                        <option value=""></option>
                                        <c:forEach var="channel" items="${appChannel }" varStatus="status">
                                            <option value="${channel }"  <c:if test="${searchObj.appChannel==channel}">selected="selected" </c:if>>${channel }</option>
                                        </c:forEach>
                                    </select>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="form-actions">
                        <button id="btnQuery" type="button" class="btn blue"><i class="icon-ok"></i>查询</button>
	                    <input id="slideBarCheckbox" type="checkbox"/>用户成分分析: <input id="slideBar" type="text" value=""/> <b id="slideBarTips">当天至7天前新增</b>
                    </div>
                </form>
                <!-- END FORM-->
            </div>
        </div>
        <!-- END VALIDATION STATES-->
    </div>
</div>
<!-- search layout end -->

<!-- charts layout begin -->
<div class="row-fluid">
    <div class="span12 ">
        <div class="portlet box blue tabbable">
            <div class="portlet-title">
                <div class="caption"><i class="icon-reorder"></i>用户新鲜度</div>
            </div>
            <div class="portlet-body">
                <div class="tabbable portlet-tabs">
                    <ul class="nav nav-tabs">
                        <li><a eleType="tab" href="#portlet_tab1" data-toggle="tab" id="tab_ratio" dataType="BFB">百分比</a></li>
                        <li class="active"><a eleType="tab" href="#portlet_tab2" data-toggle="tab" id="tab_num" dataType="JDZ">绝对值</a></li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane active" id="portlet_tab1">
                            <div id="chart_div" style="width:100%;height:500px;"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- charts layout end -->

<script src="${baseContextPath}static/js/jquery-1.10.1.min.js" type="text/javascript"></script>
<script src="${baseContextPath}static/js/jquery-migrate-1.2.1.min.js" type="text/javascript"></script>
<!-- IMPORTANT! Load jquery-ui-1.10.1.custom.min.js before bootstrap.min.js to fix bootstrap tooltip conflict with jquery ui tooltip -->
<!-- <script src="${baseContextPath}static/js/jquery-ui-1.10.1.custom.min.js" type="text/javascript"></script> -->
<script src="${baseContextPath}static/js/bootstrap.min.js" type="text/javascript"></script>
<!--[if lt IE 9]>
<script src="${baseContextPath}static/js/excanvas.min.js"></script>
<script src="${baseContextPath}static/js/respond.min.js"></script>
<![endif]-->
<script src="${baseContextPath}static/js/jquery.slimscroll.min.js" type="text/javascript"></script>
<script src="${baseContextPath}static/js/jquery.blockui.min.js" type="text/javascript"></script>
<script src="${baseContextPath}static/js/jquery.cookie.min.js" type="text/javascript"></script>
<script src="${baseContextPath}static/js/jquery.uniform.min.js" type="text/javascript" ></script>
<!-- END CORE PLUGINS -->
<!-- BEGIN PAGE LEVEL PLUGINS -->
<script type="text/javascript" src="${baseContextPath}static/js/chosen.jquery.min.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/wysihtml5-0.3.0.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/bootstrap-wysihtml5.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/jquery.tagsinput.min.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/jquery.toggle.buttons.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/bootstrap-datepicker.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/bootstrap-datetimepicker.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/clockface.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/date.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/daterangepicker.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/bootstrap-colorpicker.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/bootstrap-timepicker.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/jquery.inputmask.bundle.min.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/jquery.input-ip-address-control-1.0.min.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/jquery.multi-select.js"></script>
<script src="${baseContextPath}static/js/bootstrap-modal.js" type="text/javascript" ></script>
<script src="${baseContextPath}static/js/bootstrap-modalmanager.js" type="text/javascript" ></script>
<script type="text/javascript" src="${baseContextPath}static/js/select2.min.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/jquery.dataTables.js"></script>
<script type="text/javascript" src="${baseContextPath}static/js/DT_bootstrap.js"></script>
<!-- END PAGE LEVEL PLUGINS -->
<script type="text/javascript" src="${baseContextPath}static/js/jquery.validate.min.js"></script>
<!-- BEGIN PAGE LEVEL SCRIPTS -->
<script src="${baseContextPath}static/js/app.js"></script>
<script src="${baseContextPath}static/js/table-managed.js"></script>
<script src="${baseContextPath}static/js/form-validation.js"></script>
<script src="${baseContextPath}static/selfJs/common.js" type="text/javascript"></script>
<!-- END PAGE LEVEL SCRIPTS -->
<script src="${baseContextPath}static/selfJs/datepicker.js" type="text/javascript"></script>
<script src="${baseContextPath}static/echarts3/echarts.min.js"></script>
<script src="${baseContextPath}static/plugins/bootstrap-slider4.4.0/bootstrap-slider.min.js"></script>
<script type="text/javascript">
jQuery(document).ready(function() {
    // initiate layout and plugins
    App.init();
    FormValidation.init();
});
</script>
<script type="text/javascript" src="${baseContextPath}static/import/retention/freshness.js?v=<%=version%>"></script>
</body>
</html>

freshness.js源码:

/**
 * 用户新鲜度模块
 * @author 石冬冬
 * @date 2016/12/1
 */
var Freshness = {
	Cts:{
		charts:{
			render:null,
			viewData:null,
			totals:[]
		},
		limit:[0,31],
		rs:null
	},
	/**
	* 初始化
	*/
	init:function(){
		this.initEvts();
		this.initVals();
		this.initSlideBar(false);
		this.initData();
		this.renderCharts();
	},
	/**
	 * 初始化事件
	 */
	initEvts:function(){
		var _this = Freshness;
		$('#btnQuery').off().live('click',function(){
			_this.initData();
			_this.renderCharts();
		});
		//选项卡切换
		$('a[eleType=tab]').off().live('click',function(){
			var dataType = $(this).attr('dataType');
			$('#dataType').val(dataType);
			_this.initData();
			_this.renderCharts();
		});
		
		$('#slideBarCheckbox').off().live('click',function(){
			var checked = $(this).attr('checked');
			var enabled = checked=='checked';
			_this.initSlideBar(enabled);
		});
	},
	/**
	 * 初始化相关元素的值
	 */
	initVals:function(){
		var appVersion = $('#appVersionList').val();
		if(appVersion){
			appVersion = eval('('+appVersion+')');
			loadData(appVersion);
		}
	},
	/**
	 * 初始化标尺滚动条
	 * @param enabled
	 * API:http://demo.htmleaf.com/1502/201502041438/index.html
	 * http://blog.csdn.net/u011127019/article/details/52992654
	 */
	initSlideBar:function(enabled){
		var _this = Freshness;
		//初始化配置项
		$("#slideBar").slider({
			handle:'round',
			min:0,
			max:31,
			step:1,
			value:[0,31],
			tooltip_split:true,
			formatter: function(value) {
				var start = value[0];
				var end = value[1];
				if(start==0){
					start = '当天';
				}else{
					start+='天前';
				}
				if(end==31){
					end = '31天+前';
				}else{
					end+='天前';
				}
				var tips = start+'至'+end+'新增';
				if(value && $.isArray(value) && value.length==2){
					$('#slideBarTips').text(tips);
				}
				return value;
			}
		});
		//监听滑动事件
		$("#slideBar").on("change", function() {
			var value = this.value.split(",");
			if(value){
				_this.Cts.limit=value;
				_this.renderCharts();//刷新加载echarts
			}
		});
		//根据可用多选框设置控件是否可用
		if(enabled){
			$('#slideBarTips').show();
			$("#slideBar").slider("enable");
		}else{
			$('#slideBarTips').hide();
			$("#slideBar").slider("setValue",[0,31]);
			$("#slideBar").slider("disable");
			_this.Cts.limit=[0,31];
			_this.renderCharts();//刷新加载echarts
		}
	},
	/**
	 * 初始化报表
	 * @param args 对象
	 * @param xAxisData x坐标数组
	 */
	initCharts:function(args,xAxisData){
		var dataType = $('#dataType').val();
		var _yAxis = [
            {
                type : 'value',
                boundaryGap : false
            }
        ];
		if(dataType=='BFB'){
			_yAxis = [
	            {
	                type : 'value',
	                max:'100',
	                axisLabel: {
	                    show: true,
	                    interval: 'auto',
	                    formatter: '{value} %'
	                },
	                boundaryGap : false
	            }
	        ];
		}
		var _this = Freshness;
		var series = args.seriesData;
        var legendData = args.legendData;
        var myChart = echarts.init(document.getElementById("chart_div"), 'line');
        this.Cts.charts.render = myChart;
        var option = {
            title : {
            },
            tooltip : {
                trigger: 'axis',
                formatter:function (params,ticket,callback) {
                	var tips = '';
                	if(dataType=='JDZ'){
                		var seriesName = params.seriesName;
                		var value = params.value;
                		var name = params.name;
                		var total = _this.findTotal(name);
                		var ratio = 0;
                		if(total!=0){
                			ratio = ((value / total)*100).toFixed(2);
                		}
                		tips = name + '<br/><br/>在' + seriesName + '新增(占当日活跃比):<br/>'+ value + '('+ratio+'%)' + '<br/><br/>当日全部活跃:'+total;
                	}
                	if(dataType=='BFB'){
                		var seriesName = params.seriesName;
                		var name = params.name;
                		var total = _this.findTotal(name);
                		var value = params.value;
                		value = value/100*total;
                		var ratio = 0;
                		if(total!=0){
                			ratio = ((value / total)*100).toFixed(2);
                		}
                		tips = name + '<br/><br/>在' + seriesName + '新增(占当日活跃比):<br/>'+ value + '('+ratio+'%)' + '<br/><br/>当日全部活跃:'+total;
                	}
                	return tips;
                }
            },
            legend: {
            	show:false,
            	width:'80%',
            	padding: [5,100,5,100], 
                itemGap: 10,   
                data : legendData
            },
            dataZoom:{},
            toolbox: {
                show : true,
                feature : {
                    mark : {show: false},
                    dataView : {show: true, readOnly: false},
                    magicType: {show: true, type: ['line','bar','stack', 'tiled']},
                    restore : {show: true},
                    saveAsImage : {show: true}
                }
            },
            calculable : true,
            xAxis : [{
                type:'category',
                interval:0,
                boundaryGap:false,
                data:xAxisData
            }],
            yAxis : _yAxis,
            series : series
        };
        myChart.setOption(option);
	},
	/**
	 * 根据series的datas构建seriesData和legendData
	 * @param datas
	 */
	initSeries:function(datas){
		var _this = Freshness;
		var limit = _this.Cts.limit;
		console.log('limit=%s',JSON.stringify(limit));
		var from = Number(limit[0]) ;
		var to = Number(limit[1]);
		var legendData = [];
		var series = [];
		for(var i=to;i>=from;i--){
			var name = i+'天前';
			if(i==0){
				name = '当天';
			}
			if(i==31){
				name = '30+天前';
			}
			legendData.push(name);
			var vo = {
	            name: name,
	            type: 'line',
	            stack:'1',
	            tooltip : {
	                show : true,
	                trigger: 'item'
	            },
	            areaStyle: {normal: {}},
	            data: datas[i]
		    };
			series.push(vo);
		}
		return {
			seriesData:series,
			legendData:legendData
		};
	},
	/**
	 * 初始化数据
	 */
	initData:function(){
		var params = $('#searchForm').serialize(); 
		var _this = Freshness;
		$.ajax({
       		type : "post",
       		url : BATH_PATH + "retentionAnalysis/loadForFreshness",
       		data: params,
       		dataType : "json",
       		async : false,
       		error : function(xhr, status, err) {
				//alert(err);
       		},
       		success : function(rs) {
       			_this.Cts.rs = rs;
       		}
   		});
	},
	/**
	 * 根据容量构建二维数组 
	 * @returns {Array} 二维数组
	 */
	initArrays:function(){
		var capacity = 31;
		var arrays = [];
		for(var i = 0;i<=capacity;i++){
			arrays.push([]);
		}
		return arrays;
	},
	/**
	 * 根据xAxisName 查询对应的 活跃总数
	 * @param xAxisName
	 */
	findTotal:function(xAxisName){
		var totals = this.Cts.charts.totals;
		var total = 0;
		if(totals){
			$.each(totals,function(index){
				//console.log('key=%s,val=%s',totals[index].key,val);
				if(totals[index].key == xAxisName){
					total = totals[index].total;
					return;
				}
			});
		}
		return total;
	},
	/**
	 * 渲染Charts
	 * @param rs
	 */
	renderCharts:function(){
		var _this = Freshness;
		var rs = _this.Cts.rs;
		var dataType = $('#dataType').val();
		var xAxisData = [];
		var datas = this.initArrays();
		var totals = [];
		if(rs){
			var data = rs.dataMap;
			//======================绝对值=============================
			if(dataType=='JDZ'){
				$.each(data,function(key,vo){
					xAxisData.push(key);
					totals.push({key:key,total:vo.total});
					//console.log('key=%s,total=%s',key,vo.total);
					$.each(vo.groupMap,function(index,val){
						datas[index].push(val);
					});
				});
				//console.info("arrays=%s",JSON.stringify(datas));
			}
			//======================百分比=============================
			if(dataType=='BFB'){
				$.each(data,function(key,vo){
					xAxisData.push(key);
					var total = vo.total;
					totals.push({key:key,total:total});
					//console.log('key=%s,total=%s',key,vo.total);
					$.each(vo.groupMap,function(index,val){
						var percent = (val==0)?0:((val/total)*100).toFixed(2);
						datas[index].push(percent);
					});
				});
			}
			
		}
		//console.log('totals=%s',JSON.stringify(totals));
		_this.Cts.charts.totals = totals;
		//console.log('totals=%s',JSON.stringify(_this.Cts.charts.totals));
		var args = _this.initSeries(datas);
		_this.initCharts(args,xAxisData);
	}
};
Freshness.init();

Controller

package com.ucar.appcount.web.controller;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.ucar.appcount.common.Common;
import com.ucar.appcount.common.vo.SearchObject;
import com.ucar.appcount.service.AdminManagerServiceNew;
import com.ucar.appcount.service.RetentionAnalysisService;
import com.zuche.framework.utils.StringUtils;
/**
 * 留存分析
 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
 * @date 2016-11-28 下午6:53:41
 */
@Controller
@RequestMapping("retentionAnalysis")
public class RetentionAnalysisController {
	private static final Logger logger = Logger.getLogger(RetentionAnalysisController.class);
	@Autowired
	private AdminManagerServiceNew adminManagerServiceNew;
	@Autowired
	private RetentionAnalysisService retentionAnalysisService;
	
	
	private final String PAGE_PREFIX = "retention/";
	/**
	 * 留存用户|页面
	 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
	 * @date 2016-11-28 下午7:01:07
	 * @param model
	 * @param searchObj
	 * @return
	 */
	@RequestMapping("retention")
	public String retention(Model model,SearchObject searchObj){
		try {
			List<String> appChannel = adminManagerServiceNew.getAppChannel();
	        List<String> appVersion = adminManagerServiceNew.getAppVersion();
	        String timeType = searchObj.getTimeType();
	        String columnTitleSuffix = "天后";
	        if(StringUtils.isEmpty(timeType)){
	        	timeType = Common.DATATYPE.DAY;
	        }
	        initSearchObj(searchObj);
	        if(Common.DATATYPE.DAY.equals(timeType)){
	        	model.addAttribute("columns", RetentionAnalysisService.ReportTitle.DAILY);
	        	columnTitleSuffix = "天后";
			}
			if(Common.DATATYPE.WEEK.equals(timeType)){
				model.addAttribute("columns", RetentionAnalysisService.ReportTitle.WEEK);
				columnTitleSuffix = "周后";
			}
			if(Common.DATATYPE.MONTH.equals(timeType)){
				model.addAttribute("columns", RetentionAnalysisService.ReportTitle.MONTH);
				columnTitleSuffix = "月后";
			}
			model.addAttribute("columnTitleSuffix", columnTitleSuffix);
	        model.addAttribute("searchObj", searchObj);
	        model.addAttribute("appChannel",appChannel);
	        model.addAttribute("appVersion", appVersion);
		} catch (Exception e) {
			logger.error("留存用户初始化异常",e);
		}
		return PAGE_PREFIX.concat("retention");
	}
	/**
	 * 用户新鲜度
	 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
	 * @date 2016-12-1 下午2:00:31
	 * @param model
	 * @param searchObj
	 * @return
	 */
	@RequestMapping("freshness")
	public String freshness(Model model,SearchObject searchObj){
		try {
			List<String> appChannel = adminManagerServiceNew.getAppChannel();
	        List<String> appVersion = adminManagerServiceNew.getAppVersion();
	        String timeType = searchObj.getTimeType();
	        if(StringUtils.isEmpty(timeType)){
	        	timeType = Common.DATATYPE.DAY;
	        }
	        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
	        //开始时间为空则取三个月前
	        if(StringUtils.isBlank(searchObj.getBeginTime())){
	            Calendar calendar=Calendar.getInstance();
	            calendar.add(Calendar.MONTH,-1);
	            searchObj.setBeginTime(sdf.format(calendar.getTime()));
	        }
	        if(StringUtils.isBlank(searchObj.getEndTime())){
	        	Calendar calendar=Calendar.getInstance();
	            calendar.add(Calendar.DAY_OF_YEAR,-1);
	            searchObj.setEndTime(sdf.format(calendar.getTime()));
	        }
	        searchObj.setDataType("JDZ");//默认 绝对值
	        model.addAttribute("searchObj", searchObj);
	        model.addAttribute("appChannel",appChannel);
	        model.addAttribute("appVersion", appVersion);
		} catch (Exception e) {
			logger.error("留存用户初始化异常",e);
		}
		return PAGE_PREFIX.concat("freshness");
	}
	
	/**
	 * 留存用户
	 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
	 * @date 2016-11-29 上午9:53:00
	 * @param searchObj
	 * @return
	 */
	@RequestMapping("loadForRetention")
    @ResponseBody
    public Object loadForRetention(SearchObject searchObj){
        Map<String,Object> dataMap = new HashMap<String,Object>();
        if(!StringUtils.isBlank(searchObj.getTimeType())){
        	Map<String, Map<Integer,Double>> result =  this.retentionAnalysisService.retention(searchObj);
            dataMap.put("data", result);
        }
        return dataMap;
    }
	
	/**
	 * 用户新鲜度
	 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
	 * @date 2016-11-29 上午9:53:00
	 * @param searchObj
	 * @return
	 */
	@RequestMapping("loadForFreshness")
    @ResponseBody
    public Object loadForFreshness(SearchObject searchObj){
        return this.retentionAnalysisService.freshness(searchObj);
    }

    @RequestMapping("activeDegree")
    public String toActiveDegree(Model model,SearchObject searchObj){
        List<String> appChannel = adminManagerServiceNew.getAppChannel();
        List<String> appVersion = adminManagerServiceNew.getAppVersion();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        //开始时间为空则取三个月前
        if(StringUtils.isBlank(searchObj.getBeginTime())){
            Calendar calendar=Calendar.getInstance();
            calendar.add(Calendar.MONTH,-1);
            searchObj.setBeginTime(sdf.format(calendar.getTime()));
        }
        if(StringUtils.isBlank(searchObj.getEndTime())){
            Calendar calendar=Calendar.getInstance();
            //calendar.add(Calendar.DAY_OF_YEAR,-1);
            searchObj.setEndTime(sdf.format(calendar.getTime()));
        }
        model.addAttribute("searchObj", searchObj);
        model.addAttribute("appChannel",appChannel);
        model.addAttribute("appVersion", appVersion);
        return PAGE_PREFIX+"/activeDegree";
    }

    @RequestMapping("loadActiveDegree")
    @ResponseBody
    public Object loadActiveDegree(SearchObject searchObj){
        initSearchObj(searchObj);
        Map<String,Map<Integer,Integer>> map=new HashMap<String, Map<Integer, Integer>>();
        try {
            map=retentionAnalysisService.getActiveDegree(searchObj);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return map;
    }
    @RequestMapping("loadActive15Degree")
    @ResponseBody
    public Object loadActive15Degree(SearchObject searchObj){
        initSearchObj(searchObj);
        Map<String,Map<Integer,Integer>> map=new HashMap<String, Map<Integer, Integer>>();
        try {
            map=retentionAnalysisService.getActive15Degree(searchObj);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return map;
    }
	
	private void initSearchObj(SearchObject seachObj) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        //开始时间为空则取三个月前
        if(StringUtils.isBlank(seachObj.getBeginTime())){
            Calendar calendar=Calendar.getInstance();
            calendar.add(Calendar.WEEK_OF_YEAR,-1);
            seachObj.setBeginTime(sdf.format(calendar.getTime()));
        }
        if(StringUtils.isBlank(seachObj.getEndTime())){
        	Calendar calendar=Calendar.getInstance();
            calendar.add(Calendar.DAY_OF_YEAR,-1);
            seachObj.setEndTime(sdf.format(calendar.getTime()));
        }
        
    }
}

Service

package com.ucar.appcount.service.impl;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.ucar.appcount.common.Common;
import com.ucar.appcount.common.CommonUtils;
import com.ucar.appcount.common.DateFormatUtil;
import com.ucar.appcount.common.vo.RetentionFreshnessVo;
import com.ucar.appcount.common.vo.SearchObject;
import com.ucar.appcount.component.SearchComponent;
import com.ucar.appcount.hbase.service.DevActive15DegreeTabServiceNew;
import com.ucar.appcount.hbase.service.DevActiveDegreeTabServiceNew;
import com.ucar.appcount.hbase.service.impl.BaseOperator;
import com.ucar.appcount.service.DevUserServiceNew;
import com.ucar.appcount.service.RetentionAnalysisDailyTabService;
import com.ucar.appcount.service.RetentionAnalysisMonthTabService;
import com.ucar.appcount.service.RetentionAnalysisService;
import com.ucar.appcount.service.RetentionAnalysisWeekTabService;
import com.ucar.appcount.service.RetentionFreshnessDailyTabService;
import com.zuche.framework.nosql.hbase098.vo.Column;
import com.zuche.framework.nosql.hbase098.vo.RowVo;
import com.zuche.framework.utils.StringUtils;
@Service
public class RetentionAnalysisServiceImpl extends BaseOperator implements RetentionAnalysisService {
	private static final Logger logger = LoggerFactory.getLogger(RetentionAnalysisServiceImpl.class);
	@Autowired
	private SearchComponent searchComponent;
	@Autowired
	private RetentionAnalysisDailyTabService retentionAnalysisDailyTabService;
	@Autowired
	private RetentionAnalysisWeekTabService retentionAnalysisWeekTabService;
	@Autowired
	private RetentionAnalysisMonthTabService retentionAnalysisMonthTabService;
	@Autowired
	private RetentionFreshnessDailyTabService retentionFreshnessDailyTabService;
	@Autowired
	private DevActiveDegreeTabServiceNew activeDegreeTabService;
    @Autowired
	private DevActive15DegreeTabServiceNew active15DegreeTabService;
	@Autowired
	private DevUserServiceNew devUserServiceNew;
	
	@Override
	public List<RowVo> queryRowVoList(SearchObject searchObj,String filterList){
		List<RowVo> rs = Collections.emptyList();
		String timeType= searchObj.getTimeType();
		if(Common.DATATYPE.DAY.equals(timeType)){
			rs = this.retentionAnalysisDailyTabService.queryRetentionRows(searchObj, filterList);
		}
		if(Common.DATATYPE.WEEK.equals(timeType)){
			rs = this.retentionAnalysisWeekTabService.queryRetentionRows(searchObj, filterList);
		}
		if(Common.DATATYPE.MONTH.equals(timeType)){
			rs = this.retentionAnalysisMonthTabService.queryRetentionRows(searchObj, filterList);
		}
		return rs;
	}
	@Override
	public Map<String, Map<Integer, Double>> retention(SearchObject searchObj) {
		Map<String, Map<Integer, Double>> dataMap = new LinkedHashMap<String, Map<Integer,Double>>();
		try {
			String timeType= searchObj.getTimeType();
			final boolean byDay = Common.DATATYPE.DAY.equals(timeType);
			final boolean byWeek = Common.DATATYPE.WEEK.equals(timeType);
			final boolean byMonth = Common.DATATYPE.MONTH.equals(timeType);
			/*****************************************************************/
			/**
			 * 如果是按月统计:对开始时间 和 结束时间 逻辑控制
			 * 开始时间如果非开始时间当月的第一天,需要重置为下个月的第一天。
			 * 结束时间如果非结束时间当月的第一条,需要重置为上个月的第一天。
			 */
			/*****************************************************************/
			RetentionHandler.resetSearchTimeForByMonth(byMonth,searchObj);
			if(byMonth){
				Date start = DateFormatUtil.convertToDate(searchObj.getBeginTime(), DateFormatUtil.FORMAT_DATE_DISPLAY);
		    	Date end = DateFormatUtil.convertToDate(searchObj.getEndTime(), DateFormatUtil.FORMAT_DATE_DISPLAY);
		    	if(start.getTime()!=end.getTime()&&!end.after(start)){
		    		return dataMap;
		    	}
			}
			//1、构建查询结果Map
			this.searchComponent.initDataMapForRetention(dataMap,searchObj);
			String filterList = this.searchComponent.initFilterList(searchObj);
			//2、获取新增用户Map
			Map<String,Integer> userAddMap = this.devUserServiceNew.getNewUserNum(searchObj);
			//2.1、按时间装载新增用户数
			Set<Entry<String, Map<Integer, Double>>> entrySet = dataMap.entrySet();
			for(Entry<String, Map<Integer, Double>> entry : entrySet){
				String dataKey = entry.getKey();
				if(userAddMap.containsKey(dataKey)){
					Map<Integer, Double> columnMap = entry.getValue();
					Double addUsers = Double.valueOf(userAddMap.get(dataKey).doubleValue());
					columnMap.put(0, addUsers);
				}
			}
			/*****************************************************************/
			/**
			 * rowkey特殊规则说明:新增日期#APP类型#APP版本#下载渠道#统计日期
			 * 按天:(20161125#M_android#600340#360shoujizhushou#20161128)
			 * 按周:(20161128#M_android#600340#360shoujizhushou#20161130)
			 * 按月:(20161101#M_android#600340#360shoujizhushou#20161201)
			 */
			/*****************************************************************/
			//3、获取留存用户
			List<RowVo> rs = this.queryRowVoList(searchObj, filterList);
			for(RowVo row : rs){
				String key = row.getRowKey();//新增日期
				String rowKey = row.getRowKey();
				String tjrq = rowKey.substring(rowKey.lastIndexOf("#")+1);
	            key = key.substring(0,key.indexOf("#",0));
	            key = CommonUtils.handlerTime(key, timeType);
	            tjrq = CommonUtils.handlerTime(tjrq, timeType);//统计日期
	            if(byWeek){//按周统计,把日期都转换成这个星期的第一天
	                key = DateFormatUtil.getFirstDayOfWeek(key);
	                key = key + "~" + DateFormatUtil.getLastDayOfWeek(key);
	            }
	            int days = 0;//天数
	            if(byDay||byWeek){
	            	days=Long.valueOf(DateFormatUtil.getTwoDateDifference(key,tjrq)).intValue();
	            }else{
	            	String s=key.concat("-01");
	            	String e=tjrq.concat("-01");
	            	days=Long.valueOf(DateFormatUtil.getTwoDateDifference(s,e)).intValue();
	            }
	            List<Column> columns = row.getColumns();
	            Integer count = Integer.valueOf(getValue(columns,Bytes.toBytes("statistical"), Bytes.toBytes("count")));
	            if(dataMap.containsKey(key)){
	            	Map<Integer, Double> columnMap = dataMap.get(key);
	            	int columnIndex = RetentionHandler.getColumnIndexForRetention(timeType, days);//获取时间类型(天/周/月)对列索引
	            	Double retention = columnMap.get(columnIndex);
	            	logger.error("留存用户载入:retention={},columnMap={}",retention,columnMap);
	            	if(null!=columnMap && null!=retention){
	            		columnMap.put(columnIndex, count+retention);
	            	}
	            	dataMap.put(key, columnMap);
	            }
			}
			logger.error("留存用户加载数据:dataMap={}",JSON.toJSONString(dataMap));
		} catch (Exception e) {
			logger.error("留存用户加载数据异常:dataMap={}",JSON.toJSONString(dataMap),e);
		}
		return dataMap;
	}
	
	@Override
	public Map<String, Object> freshness(SearchObject searchObj){
		Map<String, Object> resultMap = new HashMap<String, Object>();
		try {
			Map<String, RetentionFreshnessVo> dataMap = new LinkedHashMap<String, RetentionFreshnessVo>();
			String timeType = searchObj.getTimeType();
			Integer from = searchObj.getFrom();
			Integer to = searchObj.getTo();
			String filterList = this.searchComponent.initFilterList(searchObj);
			//2、获取用户新鲜度
			List<RowVo> rs = this.retentionFreshnessDailyTabService.queryFreshnessRows(searchObj, filterList);
			for(RowVo row : rs){
				String key = row.getRowKey();//登录日期
				String rowKey = row.getRowKey();
				String xzrq = rowKey.substring(rowKey.lastIndexOf("#")+1);
	            key = key.substring(0,key.indexOf("#",0));
	            key = CommonUtils.handlerTime(key, timeType);
	            xzrq = CommonUtils.handlerTime(xzrq, timeType);//新增日期
	            List<Column> columns = row.getColumns();
	            Integer count = Integer.valueOf(getValue(columns,Bytes.toBytes("statistical"), Bytes.toBytes("count")));
	            if(dataMap.containsKey(key)){
	            	RetentionFreshnessVo freshnessVo = dataMap.get(key);
	            	int orig = freshnessVo.getTotal();
	            	freshnessVo.setTotal(orig + count);
	            	int duration = RetentionHandler.getColumnIndexForFreshness(key, xzrq,to);
	            	if(duration<from){
	            		continue;
	            	}
	            	Map<Integer,Integer> groupMap = freshnessVo.getGroupMap();
	            	groupMap.put(duration, count + groupMap.get(duration));
	            	dataMap.put(key, freshnessVo);
	            }else{
	            	RetentionFreshnessVo freshnessVo = new RetentionFreshnessVo();
		   			Map<Integer, Integer> groupMap = new LinkedHashMap<Integer, Integer>();
		   			for(int d=to;d>=from;d--){
		   				 groupMap.put(d, 0);
		   			}
		   			freshnessVo.setGroupMap(groupMap);
		   			dataMap.put(key, freshnessVo);
	            }
			}
			logger.error("用户新鲜度加载数据:dataMap={}",JSON.toJSONString(dataMap));
			resultMap.put("dataMap", dataMap);
		} catch (Exception e) {
			logger.error("用户新鲜度加载数据异常:dataMap={}",JSON.toJSONString(resultMap),e);
		}
		return resultMap;
	}

    /**
     * 获取用户活跃度数据
     *
     * @param searchObj
     * @return
     */
    @Override
    public Map<String, Map<Integer, Integer>> getActiveDegree(SearchObject searchObj) throws ParseException {
        Map<String, Map<Integer, Integer>> map=new LinkedHashMap<String, Map<Integer, Integer>>();
        //20161113#M_android#600221#360shoujizhushou#2
        String filterList = ".*";
        if(!StringUtils.isEmpty(searchObj.getAppType())){
            filterList += "#"+searchObj.getAppType();
        }else{
            filterList += "#.*";
        }
        if(!StringUtils.isEmpty(searchObj.getAppVersion())){
            filterList += "#"+searchObj.getAppVersion();
        }else{
            filterList += "#.*";
        }
        if(!StringUtils.isEmpty(searchObj.getAppChannel())){
            filterList += "#"+searchObj.getAppChannel()+".*";
        }else{
            filterList += "#.*";
        }

        //20160828#M_android#600171#SGCM 01#1
        searchObj.setTimeType(Common.DATATYPE.DAY);
        //结束时间默认加一天
        SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dateFormat.parse(searchObj.getEndTime()));
        calendar.add(Calendar.DATE, 1);
        searchObj.setEndTime(dateFormat.format(calendar.getTime()));
        List<RowVo> activeList = activeDegreeTabService.getActiveDegree(searchObj, filterList);
        for(RowVo r : activeList){
            String key = r.getRowKey();
            Integer days=Integer.valueOf(key.substring(key.lastIndexOf("#") + 1));
            key = key.substring(0, key.indexOf("#", 0));
            key = CommonUtils.handlerTime(key, searchObj.getTimeType());

            List<Column> columns = r.getColumns();
            Integer count = Integer.valueOf(getValue(columns,Bytes.toBytes("statistical"), Bytes.toBytes("count")));
            if(map.containsKey(key)){
                if(map.get(key).containsKey(days)) {
                    map.get(key).put(days, map.get(key).get(days)+count);
                }else{
                    map.get(key).put(days,count);
                }
            }else{
                Map<Integer,Integer> tempMap=new HashMap<Integer, Integer>();
                tempMap.put(days,count);
                map.put(key,tempMap);
            }
        }

        return map;
    }

    /**
     * 获取用户活跃度数据
     *
     * @param searchObj
     * @return
     */
    @Override
    public Map<String, Map<Integer, Integer>> getActive15Degree(SearchObject searchObj) throws ParseException {
        Map<String, Map<Integer, Integer>> map=new LinkedHashMap<String, Map<Integer, Integer>>();
        //20161113#M_android#600221#360shoujizhushou#2
        String filterList = ".*";
        if(!StringUtils.isEmpty(searchObj.getAppType())){
            filterList += "#"+searchObj.getAppType();
        }else{
            filterList += "#.*";
        }
        if(!StringUtils.isEmpty(searchObj.getAppVersion())){
            filterList += "#"+searchObj.getAppVersion();
        }else{
            filterList += "#.*";
        }
        if(!StringUtils.isEmpty(searchObj.getAppChannel())){
            filterList += "#"+searchObj.getAppChannel()+".*";
        }else{
            filterList += "#.*";
        }

        //20160828#M_android#600171#SGCM 01#1
        searchObj.setTimeType(Common.DATATYPE.DAY);
        //结束时间默认加一天
        SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(dateFormat.parse(searchObj.getEndTime()));
        calendar.add(Calendar.DATE,1);
        searchObj.setEndTime(dateFormat.format(calendar.getTime()));
        List<RowVo> activeList = active15DegreeTabService.getActiveDegree(searchObj, filterList);
        for(RowVo r : activeList){
            String key = r.getRowKey();
            key = key.substring(0, key.indexOf("#", 0));
            key = CommonUtils.handlerTime(key, searchObj.getTimeType());

            Integer days=Integer.valueOf(key.substring(key.lastIndexOf("#") + 1));

            List<Column> columns = r.getColumns();
            Integer count = Integer.valueOf(getValue(columns,Bytes.toBytes("statistical"), Bytes.toBytes("count")));
            if(map.containsKey(key)){
                if(map.get(key).containsKey(days)) {
                    map.get(key).put(days, map.get(key).get(days)+count);
                }else{
                    map.get(key).put(days,count);
                }
            }else{
                Map<Integer,Integer> tempMap=new HashMap<Integer, Integer>();
                tempMap.put(days,count);
                map.put(key,tempMap);
            }
        }

        return map;
    }

    /**
	 * 留存用户数据处理类
	 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
	 * @date 2016-11-30 上午9:54:17
	 */
	public static class RetentionHandler{
		/**
		 * 根据时间类型(日|周|月)获取对应列头对应的索引
		 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
		 * @date 2016-11-29 下午3:48:35
		 * @param timeType 时间类型
		 * @param days 差异天数
		 * @return
		 */
		public static int getColumnIndexForRetention(String timeType,int days){
			int columnIndex = 0;
			if(days==0){
				return -1;
			}
			if(Common.DATATYPE.DAY.equals(timeType)){
				if(days==0){
					return -1;
				}
				if(days<7){
					columnIndex = ReportTitle.DAILY[days-1];
				}else if(days==14){
					columnIndex = ReportTitle.DAILY[7];
				}else if(days==30){
					columnIndex = ReportTitle.DAILY[8];
				}
			}
			if(Common.DATATYPE.WEEK.equals(timeType)){
				int week = (days/7)-1;
				if(week<0){
					return -1;
				}
				columnIndex = ReportTitle.WEEK[week];
			}
			if(Common.DATATYPE.MONTH.equals(timeType)){
				int month = (days/30)-1;
				if(month<0){
					return -1;
				}
				columnIndex = ReportTitle.MONTH[month];
			}
			return columnIndex;
		}
		/**
		 * 用户新鲜度|获取登录日期与新增日期的差异天数
		 * @author 石冬冬-Heil Hilter(dd.shi02@zuche.com)
		 * @date 2016-12-1 下午3:01:57
		 * @param dlrq 登录日期
		 * @param xzrq 新增日期
		 * @return
		 */
		public static int getColumnIndexForFreshness(String dlrq,String xzrq,Integer to){
			int duration = 0;
			duration = Long.valueOf(DateFormatUtil.getTwoDateDifference(xzrq,dlrq)).intValue();
			if(duration>=to){
				return to;
			}
			return duration;
		}
		/*****************************************************************/
		/**
		 * 如果是按月统计:对开始时间 和 结束时间 逻辑控制
		 * 开始时间如果非开始时间当月的第一天,需要重置为下个月的第一天。
		 * 结束时间如果非结束时间当月的第一条,需要重置为上个月的第一天。
		 */
		/*****************************************************************/
		public static void resetSearchTimeForByMonth(boolean byMonth,SearchObject searchObj){
			if(!byMonth){
				return;
			}
			String beginTime = searchObj.getBeginTime();
			String endTime = searchObj.getEndTime();
			if(!StringUtils.isEmpty(beginTime)){
				Calendar calendar = Calendar.getInstance();
				calendar.setTime(DateFormatUtil.convertToDate(beginTime,DateFormatUtil.FORMAT_DATE_DISPLAY));
				int today = calendar.get(Calendar.DAY_OF_MONTH);
				if(today!=1){
					calendar.add(Calendar.MONTH, 1);
					calendar.set(Calendar.DAY_OF_MONTH, 1);
					String newTime = DateFormatUtil.convertDateToString(calendar.getTime(), DateFormatUtil.FORMAT_DATE_DISPLAY);
					searchObj.setBeginTime(newTime);
				}
			}
			if(!StringUtils.isEmpty(endTime)){
				Calendar calendar = Calendar.getInstance();
				calendar.setTime(DateFormatUtil.convertToDate(endTime,DateFormatUtil.FORMAT_DATE_DISPLAY));
				int today = calendar.get(Calendar.DAY_OF_MONTH);
				boolean reset = false;
				//下面有些情况却要确认。。。。
				if(today!=1){
					calendar.add(Calendar.MONTH, -1);
					calendar.set(Calendar.DAY_OF_MONTH, 1);
					reset = true;
				}else{
					int month = calendar.get(Calendar.MONTH);
					Calendar nowCalendar = Calendar.getInstance();
					int currentMonth = nowCalendar.get(Calendar.MONTH);
					//非本月第一天时,
					if(month==currentMonth){
						calendar.add(Calendar.MONTH, -1);
						reset = true;
					}
				}
				if(reset){
					String newTime = DateFormatUtil.convertDateToString(calendar.getTime(), DateFormatUtil.FORMAT_DATE_DISPLAY);
					searchObj.setEndTime(newTime);
				}
			}
		}
		
		public static void main(String[] args) {
			//System.out.println(getColumnIndex(Common.DATATYPE.WEEK, 7));
			System.out.println(RetentionAnalysisService.ReportTitle.MONTH_COLS);
		}
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值