精华内容
下载资源
问答
  • java统计系统工具类

    万次阅读 2019-08-06 15:28:41
    在某些常见的图形、表格统计系统中,往往需要计算同比、环比等等,下面是自己整理的常用统计工具类方法 import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; ...

    在某些常见的图形、表格统计系统中,往往需要计算同比、环比等等,下面是自己整理的常用统计工具类方法

    
    import org.apache.commons.lang.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import javax.servlet.http.HttpServletRequest;
    import java.text.NumberFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.time.LocalDate;
    import java.util.*;
    import java.util.regex.Pattern;
    
    /**
     * 统计项目的工具类
     *
     * @author linmin
     **/
    public class StatisticsUtils {
        /**
         * 判断一个字符串是不是全是数字的正则匹配规则
         */
        private static Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$");
    
        /**
         * 日志记录对象
         */
        private static final Logger logger = LoggerFactory.getLogger(StatisticsUtils.class);
    
        private StatisticsUtils() {
    
        }
    
        /**
         * 同比,环比均可用
         * 获取增长率,保留四位小数
         * 增长率=(本月数-上月数)/上月数×100%
         * @param lastCount    上月的数据
         * @param currentCount 本月的数据
         * @return float
         */
        public static String getGrowthRate(double lastCount, double currentCount) {
            double growthRate;
            if (lastCount == 0) {
                growthRate = 1;
            } else {
                growthRate = (float) (currentCount - lastCount) / lastCount;
            }
            //保留4为小数
            return String.format("%.4f", growthRate);
        }
    
        /**
         * 获取占比
         * @param molecular  分子
         * @param denominator 分母
         * @return  返回占比
         */
        public static String getProportion(double molecular, double denominator) {
            String proportion;
            if (denominator == 0) {
                proportion = "";
            } else {
                double pro = molecular / denominator;
                proportion = String.format("%.4f", pro);
            }
    
            return proportion;
        }
    
        /**
         *  把对象转换成双精度数据
         * @param object object
         * @return 双精度数据
         */
        public static double getObjectToDouble(Object object) {
            String str = String.valueOf(object).trim();
            if (StringUtils.isEmpty(str)) {
                return 0;
            }
            if (!isNumber(str)) {
                throw new NumberFormatException("字符串里面不全是数字,不能转换");
            }
            return Double.parseDouble(str);
        }
    
        /**
         *  把对象转换成字符串
         * @param object object
         * @return  String
         */
        public static String getObjectToString(Object object) {
            if(object==null){
                return "";
            }
            return String.valueOf(object).trim();
        }
    
        /**
         * 判断一个字符串是不是全是数字
         * @param string 判断是不是全数字的字符串
         * @return 布尔值
         */
        private static boolean isNumber(String string) {
            if (StringUtils.isEmpty(string)) {
                return false;
            }
            return pattern.matcher(string).matches();
        }
    
    
        /**
         * 获取昨年和今年同比的数据
         * @param thisYearList 今年的数据
         * @param lastYearList 昨年的数据
         * @return 同比数据集合
         */
    
        public static List<Map<String, Object>> getYearOnYearList(List<Map<String, Object>> thisYearList, List<Map<String, Object>> lastYearList) {
            //住院总费用同比增长集合
            List<Map<String, Object>> yearOnYearZngeList = new ArrayList<>();
            for (int i = 0; i < thisYearList.size(); i++) {
                //获取今年每月对应的收入map
                Map<String, Object> thisYearMap = thisYearList.get(i);
                //获取去年每月对应的收入map
                Map<String, Object> lastYearMap = lastYearList.get(i);
                Object thisYearCost = thisYearMap.get("thisYearCost");
                Object lastYearCost = lastYearMap.get("lastYearCost");
                double thisYearCount = StatisticsUtils.getObjectToDouble(thisYearCost);
                double lastYearCount = StatisticsUtils.getObjectToDouble(lastYearCost);
                //根据去年和今年的总收入算出对应的增长率
                String growthRate = StatisticsUtils.getGrowthRate(lastYearCount, thisYearCount);
                //时间横坐标 1月、 2月……
                String dateAbscissa = (String) thisYearMap.get("dateAbscissa");
                //每次计算得出的增长率map
                Map<String, Object> yearOnYearMap = new HashMap<>(16);
                //吧每月对应的增长率放入map
                yearOnYearMap.put("dateAbscissa", dateAbscissa);
                yearOnYearMap.put("thisYearCount",thisYearCount);
                yearOnYearMap.put("lastYearCount",lastYearCount);
                yearOnYearMap.put("growthRate", growthRate);
                yearOnYearZngeList.add(yearOnYearMap);
            }
            return yearOnYearZngeList;
        }
    
    
        /**
         * 获取双精度数据的后几位数据
         * @param d     双精度数据
         * @param digit 需要保留小数点后几位
         * @return 返回处理后的字符串对象
         */
        public static String getNumberFormatByDecimalPlaces(double d, int digit) {
            NumberFormat numberInstance = NumberFormat.getNumberInstance();
            numberInstance.setMaximumFractionDigits(digit);
            return numberInstance.format(d);
        }
    
    
    
    
    
        /**
         * 获取上月环比日期
         * @param date 本月日期
         * @param formatPattern  日期的匹配格式
         * @return  获取上月环比日期
         */
       private static String  getLastMouthDate( String date,String formatPattern){
           Date lastMouth=new Date();
           try {
               SimpleDateFormat format = new SimpleDateFormat(formatPattern);
               Date currentDate = format.parse(date);
               Calendar c = Calendar.getInstance();
               c.setTime(currentDate);
               c.add(Calendar.MONTH, -1);
               lastMouth = c.getTime();
    
           } catch (ParseException e) {
               logger.error("获取环比日期异常",e);
           }
           return format(lastMouth,formatPattern);
       }
    
    
        /**
         * 获取去年同比日期
         * @param date 今年对应的日期
         * @param formatPattern  日期的匹配格式
         * @return 获取去年同比日期
         */
        public static String  getLastYearDate( String date,String formatPattern){
            Date lastYear=new Date();
            try {
                SimpleDateFormat format = new SimpleDateFormat(formatPattern);
                Date currentDate = format.parse(date);
                Calendar c = Calendar.getInstance();
                c.setTime(currentDate);
                c.add(Calendar.YEAR, -1);
                lastYear = c.getTime();
            } catch (ParseException e) {
                logger.error("获取同比日期异常",e);
            }
            return format(lastYear,formatPattern);
        }
    
        /**
         * 格式化时间成字符串
         * @param time  需要格式化的时间
         * @param formatPattern  日期的匹配格式
         * @return 返回对应格式的时间字符串
         */
        private static String format(Date time,String formatPattern) {
            SimpleDateFormat sdf = new SimpleDateFormat(formatPattern);
            return sdf.format(time);
        }
    
        /**
         * 获取上期环比搜索条件(移除以前的时间参数值)
         * @param searchConditionalParameters 搜索条件map
         * @param startDate 开始时间
         * @param endDate 结束时间
         */
        public  static  Map<String, Object>  replaceLastMouthConditional( Map<String, Object> searchConditionalParameters,String startDate ,String endDate ){
            searchConditionalParameters.remove(Constant.START_DATE.getDescribe());
            searchConditionalParameters.remove(Constant.END_DATE.getDescribe());
            searchConditionalParameters.put(Constant.START_DATE.getDescribe(),StatisticsUtils.getLastMouthDate(startDate,Constant.DATE_FORMAT_PATTERN.getDescribe()));
            searchConditionalParameters.put(Constant.END_DATE.getDescribe(),StatisticsUtils.getLastMouthDate(endDate,Constant.DATE_FORMAT_PATTERN.getDescribe()));
            return searchConditionalParameters;
        }
        /**
         * 获去年同比搜索条件(移除以前的时间参数值)
         * @param searchConditionalParameters 搜索条件map
         * @param startDate 开始时间
         * @param endDate 结束时间
         */
        public  static  Map<String, Object> replaceLastYearConditional( Map<String, Object> searchConditionalParameters,String startDate ,String endDate ){
            searchConditionalParameters.remove(Constant.START_DATE.getDescribe());
            searchConditionalParameters.remove(Constant.END_DATE.getDescribe());
            searchConditionalParameters.put(Constant.START_DATE.getDescribe(),StatisticsUtils.getLastYearDate(startDate,Constant.DATE_FORMAT_PATTERN.getDescribe()));
            searchConditionalParameters.put(Constant.END_DATE.getDescribe(),StatisticsUtils.getLastYearDate(endDate,Constant.DATE_FORMAT_PATTERN.getDescribe()));
            return searchConditionalParameters;
        }
    
        /**
         * 获取开始时间,如果没有传入时间,则默认当年的1月1日
         * @param request  客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息
         * @return 开始时间字符串
         */
    
        public static  String getStartDate(HttpServletRequest request){
            String startDate = request.getParameter(Constant.START_DATE.getDescribe());
            LocalDate localDate=LocalDate.now();
            if (StringUtils.isEmpty(startDate)){
                startDate=localDate.getYear()+"-01-01";
            }
            return startDate;
        }
    
        /**
         * 获取结束时间,如果没有传入时间,则默认当前时间
         * @param request  客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息
         * @return 结束时间字符串
         */
        public static  String getEndDate(HttpServletRequest request){
            String endDate = request.getParameter(Constant.END_DATE.getDescribe());
            LocalDate localDate=LocalDate.now();
            if (StringUtils.isEmpty(endDate)){
                endDate=localDate.toString();
            }
            return endDate;
        }
    
    
    }
    
    展开全文
  • 「好多粉」微信复制统计系统

    万次阅读 2020-11-28 00:25:16
    微信复制统计系统操作流程 复制统计使用步骤: 1.落地页添加基础js代码 代码位置:账户后台>js代码和教程>基础js代码。 添加位置:添加在网页底部的标签前,如下图:(图片可点击放大查看) 2.设置统计微信号,...

    微信复制统计系统操作流程

    复制统计使用步骤:

    1.落地页添加基础js代码

    代码位置:账户后台>js代码和教程>基础js代码。

    添加位置:添加在网页底部的标签前,如下图:(图片可点击放大查看)
    在这里插入图片描述
    2.设置统计微信号,如下:

    先在落地页管理中开启需要使用的落地页,如下图:
    在这里插入图片描述
    点击设置按钮,进入到落地页统计管理 ,填写统计微信号:
    在这里插入图片描述
    在这里插入图片描述
    3.设置完成,如下:
    在这里插入图片描述
    测试是否能统计成功:

    通过浏览器访问刚刚添加的链接后,后台【实时访客】中就会有一条访问记录;如下图:
    在这里插入图片描述
    复制成功后,变成看到复制记录:
    在这里插入图片描述
    「好多粉」微信复制统计系统操作流程功能教程已全部讲解

    展开全文
  • C语言小程序-学生成绩统计系统

    万次阅读 多人点赞 2019-05-30 13:12:45
    C语言小程序-学生成绩统计系统 #刚入门的小白,写的一个C语言作业,参考了 另一位博主的代码(https://blog.csdn.net/qq_36503589/article/details/53106983) 但是我自信的认为我的代码更“好看”一点,见笑了,请...

    C语言小程序-学生成绩统计系统

    参考了

    另一位博主的代码(https://blog.csdn.net/qq_36503589/article/details/53106983)
    我后来写的成绩排序版本链接:https://blog.csdn.net/qq_43617268/article/details/103491760

    1. 功能介绍:本程序可以输入50人以内的成绩,包括英语,C语言,高数,体育成绩。
    2. 可以计算并输出学生的平均分,学科的平均分。
    3. 可以循环查询并修改学生成绩。
      这是我的测试情况
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    #include<stdio.h>
    #include<string.h>
    struct grade_list{										//定义结构体 
    	char name[10];										//姓名 
    	float c_len;										//C语言成绩 
    	float math;											//高数 
    	float eng;											//英语 
    	float PE;											//体育 
    };
    void in_put(struct grade_list grades[], int n);			//声明输入函数
    void average(struct grade_list grades[], int n);		//声明平均值函数
    void average_subject(struct grade_list grades[], int n);//声明课程平均值函数
    void find(struct grade_list grades[], int n);			//声明查询函数 
    void change(struct grade_list grades[], int n);			//声明修改成绩函数
     
    int main()
    {
    	struct grade_list grades[50];
    	int choices,i,n;
    	printf("成绩统计系统正在启动...\n"); 
    	printf("程序正在运行...\n");
    	do{
    		rerun:
    		printf("\n");
    		printf("\t************************************\n");
    		printf("\t*    ===========================   *\n");
    		printf("\t*          学生成绩统计系统        *\n");
    		printf("\t*    ===========================   *\n");
    		printf("\t*                                  *\n");
    		printf("\t*     输入学生成绩---------1       *\n");
    		printf("\t*     查看学生平均成绩-----2       *\n");
    		printf("\t*     查看课程平均值-------3       *\n");
    		printf("\t*     查找学生成绩---------4       *\n");
    		printf("\t*     修改学生成绩---------5       *\n");
    		printf("\t*     退出系统-------------0       *\n");
    		printf("\t*                                  *\n");
    		printf("\t************************************\n");
    		printf("\t请选择功能:");
    		scanf("%d", &choices);
    		switch(choices)
    		{
    			case 1:
    				printf("执行成绩输入程序...\n\n");
    				printf("\t请输入学生人数(1~50):");
    				scanf("%d",&n);
    				in_put(grades,n);
    				break;
    			case 2:
    				printf("执行学生平均成绩计算程序...\n\n");
    				average(grades,n);
    				break;
    			case 3:
    				printf("执行学科平均成绩计算程序...\n\n");
    				average_subject(grades,n);
    				break;
    			case 4:
    				printf("执行成绩查询程序...\n\n");
    				find(grades, n);
    				break;
    			case 5:
    				printf("执行成绩修改程序...\n\n");
    				change(grades, n);
    				break; 
    			case 0:
    				printf("\t||系统已关闭,感谢您的使用!||");
    				break;
    			default :
    				printf("\t非法输入!!!");
    				goto rerun; 
    		}
    	}while(choices>0&&choices<6);
    	
    	return 0;
     } 
    
    void in_put(struct grade_list grades[], int n)				//定义输入函数 
    {
    	int i;
    	for(i=0;i<n;i++)
    	{
    		printf("\t请输入第%d名同学的姓名:", i+1);
    		scanf("%s", grades[i].name);
    		printf("\t请输入第%d名同学的C语言成绩:", i+1);
    		scanf("%f", &grades[i].c_len);
    		printf("\t请输入第%d名同学的高数成绩:", i+1);
    		scanf("%f", &grades[i].math);
    		printf("\t请输入第%d名同学的英语成绩:", i+1);
    		scanf("%f", &grades[i].eng);
    		printf("\t请输入第%d名同学的体育成绩:", i+1);
    		scanf("%f", &grades[i].PE);
    	}
    	printf("\t*成绩录入成功*\n");
    }
    
    void average(struct grade_list grades[], int n)			//定义计算学生平均值 
    {
    	int i;
    	float result;
    	for(i=0;i<n;i++)
    	{
    		result=(grades[i].c_len+grades[i].math+grades[i].eng
    		+grades[i].PE)/4;
    		printf("\t%s的平均成绩为%.2f\n",grades[i].name, result);	
    	}
    }
    
    void average_subject(struct grade_list grades[], int n)	//定义计算学科平均分 
    {
    	printf("\tC语言\t高数\t英语\t体育\n");
    	int i;
    	float result=0.0;
    	for(i=0;i<n;i++)
    	{
    		result+=grades[i].c_len;
    	}
    	printf("\t%.2f", result/n);
    	result=0;
    	for(i=0;i<n;i++)
    	{
    		result+=grades[i].math;
    	}
    	printf("\t%.2f", result/n);
    	result=0;
    	for(i=0;i<n;i++)
    	{
    		result+=grades[i].eng;
    	}
    	printf("\t%.2f", result/n);
    	result=0;
    	for(i=0;i<n;i++)
    	{
    		result+=grades[i].PE;
    	}
    	printf("\t%.2f\n", result/n);
    }
    
    void find(struct grade_list grades[], int n)			//定义循环成绩查询函数 
    {
    	int i,trigger;
    	char in_name[10];
    	
    	do{
    		printf("\t请输入要查询的学生姓名:");
    		scanf("%s", in_name);
    		int chack=0;
    		for(i=0;i<n;i++)
    		{
    			if(strcmp(in_name,grades[i].name)==0)
    			{
    				printf("\t%s的成绩为:\n\n",grades[i].name);
    				printf("\tC语言\t高数\t英语\t体育\n");
    				printf("\t%.2f\t%.2f\t%.2f\t%.2f\n\n", grades[i].c_len,grades[i].math,grades[i].eng,grades[i].PE); 
    				chack=1;
    			}
    					 
    		}
    		if(chack==0) printf("\t*查无此人*\n\n"); 
    		printf("\t继续查询请输入1,否则输入0返回功能选择界面:");
    		scanf("%d", &trigger);
    	}while(trigger==1);
    } 
    
    void change(struct grade_list grades[], int n)			//定义成绩修改函数 
    {
    	int i,trigger;
    	char ch_name[10];
    	
    	do{
    		int chack=0; 
    		printf("\t请输入要修改成绩的学生姓名:");
    		scanf("%s", ch_name);
    		for(i=0;i<n;i++)
    		{
    			if(strcmp(ch_name,grades[i].name)==0)
    			{
    				printf("\t请输入该同学修改后的成绩:\n\tC语言:");
    				scanf("%f", &grades[i].c_len);
    				printf("\t高数:");
    				scanf("%f", &grades[i].math);
    				printf("\t英语:");
    				scanf("%f", &grades[i].eng);
    				printf("\t体育:");
    				scanf("%f", &grades[i].PE);
    				chack=1;
    				printf("\t*修改成功*\n");
    			}		 
    		}
    		if(chack==0) printf("\t*查无此人*\n\n");
    		printf("\t继续修改请输入1,否则输入0返回功能选择界面:");
    		scanf("%d", &trigger);
    	}while(trigger==1);
    }
    
    展开全文
  •   打造一款符合自己公司需求的用户行为统计系统,相信是很多运营人员的梦想,也是开发人员对技术的的执着追求。下面我为大家分一享下自己为公司打造的用户行为统计系统。   用户行为统计(User Behavior ...

      打造一款符合自己公司需求的用户行为统计系统,相信是很多运营人员的梦想,也是开发人员对技术的的执着追求。下面我为大家分一享下自己为公司打造的用户行为统计系统。
      用户行为统计(User Behavior Statistics, UBS)一直是移动互联网产品中必不可少的环节,也俗称埋点。对于产品经理,运营人员来说,埋点当然是越多,覆盖范围越广越好。废话废话就不多少了,这里我主要利用了AOP面向切片编程的思想来解决这个问题的。参考博客:参考博客地址首先声明,我这里并没有完全照搬别人博客,这里主要是顺着别人博客思路去走,走进死胡同,然后返璞归真,用自己的思路去实现的。之所以把别人的思路写下来讨论,就是为了说明思考的过程有时也很重要。

    用户行为统计统计什么?

      我们常常说用户行为统计,那么用户行为统计主要统什计么呢,在我看来主要分为两类:1,页面统计:PV2,事件统计:Event

    页面统计:PV

      页面统计就是就在用户进入某个页面的时候,进记行录保存;在用户离开某个页面的时候进行保存记录。在当适的时候将保存的数据发送给后台服务器。实现代码如下:

    [UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){
                [self JKhandlePV:data status:JKUBSPV_ENTER];
            } error:nil];
    
            [UIViewController aspect_hookSelector:@selector(viewDidDisappear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){
                [self JKhandlePV:data status:JKUBSPV_LEAVE];
            } error:nil];
    

    很多博客贴出这样的代码以为就解决了问题,其实忽略了很大的一个问题,这样简粗单暴的去处理,会发现项中目所的有UIViewCnotroller的这两个方法viewDidAppear:viewDidDisappear:都被会hook,造了成额外的性能开销,非常的不好。所以我边这进行了处理只针对要统的计页面进行hook操作。具现体实如下:

    + (void)configPV{
        for (NSString *vcName in [[JKUBS shareInstance].configureData[JKUBSPVKey] allKeys]) {
    
            Class target = NSClassFromString(vcName);
            [target aspect_hookSelector:@selector(viewDidAppear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){
                [self JKhandlePV:data status:JKUBSPV_ENTER];
            } error:nil];
    
            [target aspect_hookSelector:@selector(viewDidDisappear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){
                [self JKhandlePV:data status:JKUBSPV_LEAVE];
            } error:nil];
        }
    
    
    }

    事件统计:Event

      事件统计主要是在用户触发事件时进行记录保存,然后在合适的时候将记的录数据发送给后台服务器进行处理。按照文章开头参考博客所说,简单将件事分成了UIButotn,UIControl,UIGestureRecognizer以及点击UITableView单元格cell触发的事件,点击UICollectionView单元格cell触发的事件。
      按照这个思路我首先对UIButton,UIControl触发的事件进行处理:

    + (void)configUIControlEvent{
    
        [UIControl aspect_hookSelector:@selector(sendAction:to:forEvent:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id<JKUBSAspectInfo> data){
            [self JKHandleEvent:data];
        } error:nil];
    
    }
    

    这个实现起来相对容易些,相信大家都有实现过。

      对UIGestureRecognizer触发的事件进行处理,比较麻烦 首先UIGestureRecognizer是一个类簇,我们触发事件时的tap,LongPress,swipe,pan等手势发送事件是并不是发送事件的真正的类,我这边通过打断点的形式找到了发送事件的真正的类是:UIGestureRecognizerTarget 发送事件的私有方法是:_sendActionWithGestureRecognizer: 然后我就通过hook操作对手势触发的事件进行了处理:

    + (void)configGestureRecognizerEvent{
        Class UIGestureRecognizerTarget =NSClassFromString(@"UIGestureRecognizerTarget");
        [UIGestureRecognizerTarget aspect_hookSelector:@selector(_sendActionWithGestureRecognizer:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id<JKUBSAspectInfo> data){
            [self JKHandleEvent:data];
        } error:nil];
    
    
    }

    对手势触发的事件进行统计虽然困难,但还是实现了。
      对于点击UITableView单元格cell触发的事件,点击UICollectionView单元格cell触发的事件。我这边以点击UITableView单元格cell触发的事件为例进行说明。假设JKBViewController实现了UITableView 的代理方法tableView:didSelectRowAtIndexPath: 那么我的实现如下:

    + (void)configureDelegateEvent{
    
        [JKBViewController aspect_hookSelector:@selector(tableView:didSelectRowAtIndexPath:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id<JKUBSAspectInfo> data){
            [self JKHandleEvent:data];
        } error:nil];
    
    }
    

    通过这个实现我们能够做到对点击UITableView单元格cell触发的事件进行统计,但是顺着参博考客作者的思路这一步一步做下来,做到这里我内心有种不的妙感觉。

    走进死胡同

    以下是参考的博客作者在开发的过程中遇到的问题

    1,并不是所有的事件都是有继承自UIControl的空间来发出的,比如:手势,点击Cell。
    2,并不是所有的按钮点击了之后就立马需要埋点上传?可能在按钮的响应方法中经过了层层的if(){ } else{ }最后才需要埋点。
    4,对于代理方法该怎样处理?
    5,如果很多个按钮对应着一个事件该怎样处理?
    

    其针实对第1点,我边这虽然梳理了很多类型的事件,但是仍然有很多没有被统计上,比如摇一摇触发的事件,计步器触发的事件,tabBar点击触发的事件等,还很有多我可能没到想的事件,我现发如果按照作者的意图,按照事件触发的类型去一个一个的进行hook操作的话,工作两蛮大,而且还是会有遗漏的。尤是其涉及到有方些法苹果没有开放给开发者,我们进行处理的话比较麻烦。开员发人估被计要累死啊。
    针对第2点,按作照者的意图,会现发点击之后里面还有层层的判断,如何绕过层层的判断呢?这个我会在接下来详细阐述。
    针对第4点,我在上面已经实现过了。
    针对第5点,在现实的情况中确实存在者不同的页面中,甚至相同的页面中不同的按钮对应着同一个事件这样的问题。如果按照参考博客作者的思路确实处理起来很是麻烦。

    返璞归真

      针对上面出现的困境,我在想有没有更好的办法去解决呢。首先想到我们统计用户操的作事件,并是不为了统计用户点击了某个按钮,或者进行了某个手势操作,调了用某个代理方法。而为是了统计用户进行这个操作的目的是什么,是为了购物,还是为了分享等。所以我就打破参考博客作者的思路,不再对按钮,手势,单元格选中等事件进行hook,而是对用户的目的事件触发的方法进行hook,事件就是事件,没有来源之分。也就是hook就提示的事件,中间层层的逻辑判断,我不需要考虑,我只考虑hook的目的事件。举例个子,用户要行进分享- (void)goShare;,我不关心用是户否点击了按钮,或者tap手势触发了方法,或者单元格被中选,我只关心分享的方法- (void)goShare;有没有被调用,被调用的时候我是否可以进记行录操作。另外唯一确定一个方法,除了selector,还要有相关的target(方法的实现者,或者消息接受者)。针上面第5点,不同按钮对应同一个事件,一般情况下事件相同target不同,我们是能够区别的出来的。当了然也存在同一个页面上的不同按钮触发的同一个事件,这种情况下不是太常见,函数外面包一层,改个别的名字区分一下就好了,不过EnvetID还是要一样的。
      为了更好的方便大家,我这边按自照己的思路写了一个pod库,下面先说一下自己的plist文件文件:
    这里写图片描述
    注意:最近又新添加了对类方法的埋点,在plist表中配置如下,需要在类方法名前添加一个+号,方便识别。具体如下图:
    这里写图片描述
    大家可以看到PV字段下,每一个页面都以可设置页面的名字,还一有些其他的信息。
    Event字段下有EventID,同时呢也允许同一个EventID下有不同的触发事件。
    事件1这一级字段写上具体的事件内容,主要是方便开发人读员阅查找。
    JKVC1点击,JKVC2点击,tap单击,选中tableView单元格这些都是为了标件来明事源,方便开发人员阅读。另外如果事件还需要配置额外的参数,那么可以在EventID同级字段下添加新的内容。
    下看看面来代码吧:
    JKUBS.h

    #import <Foundation/Foundation.h>
    #import "JKUBSAspects.h"
    
    
    extern NSString const *JKUBSPVKey;
    extern NSString const *JKUBSEventKey;
    extern NSString const *JKUBSEventIDKey;
    extern NSString const *JKUBSEventConfigKey;
    extern NSString const *JKUBSSelectorStrKey;
    extern NSString const *JKUBSTargetKey;
    
    
    typedef NS_ENUM(NSInteger, JKUBSPVSTATUS){
        JKUBSPV_ENTER = 0,         //进入页面
        JKUBSPV_LEAVE              //离开页面
    };
    
    @interface JKUBS : NSObject
    
    @property (nonatomic,strong,readonly) NSDictionary *configureData;
    
    
    
    
    /**
     生成单例的方法
    
     @return 单例对象
     */
    + (instancetype)shareInstance;
    
    
    /**
     通过json配置文件导入配置信息
    json配置文件或plist配置文件只导入一个就好了
     @param jsonFilePath json文件沙盒路径
     */
    + (void)configureDataWithJSONFile:(NSString *)jsonFilePath;
    
    
    /**
     通过plist配置文件导入配置信息
    json配置文件或plist配置文件只导入一个就好了
     @param plistFileName plist文件名字(不带后缀名)
     */
    + (void)configureDataWithPlistFile:(NSString *)plistFileName;
    
    
    /**
     处理PV
    这个方法需要开发者重载进行具体的操作
     @param data 页面信息
     @param status 进入或离开页面的状态
     */
    + (void)JKhandlePV:(id<JKUBSAspectInfo>)data status:(JKUBSPVSTATUS)status;
    
    
    /**
     处理事件
    这个方法需要开发者重载进行具体的操作
     @param data 事件信息
     @param eventId 事件ID
     */
    + (void)JKHandleEvent:(id<JKUBSAspectInfo>)data EventID:(NSInteger)eventId;
    
    
    @end
    

    JKUBS.m

    #import "JKUBS.h"
    
    NSString const *JKUBSPVKey = @"PV";
    NSString const *JKUBSEventKey = @"Event";
    NSString const *JKUBSEventIDKey = @"EventID";
    NSString const *JKUBSEventConfigKey = @"EventConfig";
    NSString const *JKUBSSelectorStrKey = @"selectorStr";
    NSString const *JKUBSTargetKey = @"target";
    
    
    @interface JKUBS()
    
    @property (nonatomic,strong,readwrite) NSDictionary *configureData;
    
    @end
    
    @implementation JKUBS
    static JKUBS *_ubs =nil;
    + (instancetype)shareInstance{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _ubs = [JKUBS new];
        });
    
        return _ubs;
    
    }
    
    + (void)configureDataWithJSONFile:(NSString *)jsonFilePath{
        NSData *data = [NSData dataWithContentsOfFile:jsonFilePath];
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
        [JKUBS shareInstance].configureData = dic;
    
        if ([JKUBS shareInstance].configureData) {
            [self setUp];
        }
    }
    
    
    + (void)configureDataWithPlistFile:(NSString *)plistFileName{
        NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:plistFileName ofType:@"plist"]];
        [JKUBS shareInstance].configureData = dic;
    
        if ([JKUBS shareInstance].configureData) {
            [self setUp];
        }
    
    }
    
    
    + (void)setUp{
    
        [self configPV];
        [self configEvents];
    
    }
    
    #pragma mark PVConfig - - - -
    
    + (void)configPV{
        for (NSString *vcName in [[JKUBS shareInstance].configureData[JKUBSPVKey] allKeys]) {
    
            Class target = NSClassFromString(vcName);
            [target aspect_hookSelector:@selector(viewDidAppear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){
                [self JKhandlePV:data status:JKUBSPV_ENTER];
            } error:nil];
    
            [target aspect_hookSelector:@selector(viewDidDisappear:) withOptions:JKUBSAspectPositionAfter usingBlock:^(id data){
                [self JKhandlePV:data status:JKUBSPV_LEAVE];
            } error:nil];
        }
    
    
    }
    
    
    
    + (void)JKhandlePV:(id<JKUBSAspectInfo>)data status:(JKUBSPVSTATUS)status{
    
    
    }
    
    #pragma mark EventConfig - - - -
    
    + (void)configEvents{
    
        NSDictionary *eventsDic = [JKUBS shareInstance].configureData[JKUBSEventKey];
        NSArray *events =[eventsDic allValues];
        for (NSDictionary *dic in events) {
            NSInteger EventID = [dic[JKUBSEventIDKey] integerValue];
            NSArray *eventConfigs = [dic[JKUBSEventConfigKey] allValues];
            for (NSDictionary *eventConfig in eventConfigs) {
                NSString *selectorStr = eventConfig[JKUBSSelectorStrKey];
                NSString *targetClass = eventConfig[JKUBSTargetKey];
                Class target =NSClassFromString(targetClass);
                SEL selector = NSSelectorFromString(selectorStr);
    
                    [target aspect_hookSelector:selector withOptions:JKUBSAspectPositionBefore usingBlock:^(id<JKUBSAspectInfo> data){
                        [self JKHandleEvent:data EventID:EventID];
                    } error:nil];
    
    
            }
        }
    
    
    }
    
    + (void)JKHandleEvent:(id<JKUBSAspectInfo>)data EventID:(NSInteger)eventId{
    
    }
    

    其中有两个方法要重点说一下。

    + (void)JKhandlePV:(id<JKUBSAspectInfo>)data status:(JKUBSPVSTATUS)status;
    + (void)JKHandleEvent:(id<JKUBSAspectInfo>)data EventID:(NSInteger)eventId;

    这两个方法都需要在JKUBS的category进行重载,来做具体的实现。例如页面活动的记录,事件的记录。打造用户行为统计系统,我这边已经完成了AOP思想下的事件采集,具体如何记录,保存,发给送后台,这里就不详细说明了。

    代码下载地址
    使用pod如下:

    pod "JKUBS"

    注意:demo中我对aspects库进行了修改,为了防止名字冲突,我这边统一都加了JKUBS前缀。欢迎大家来找茬,一块交流学习。QQ交流群:

    展开全文
  • 公交车人数统计系统设计

    万次阅读 2016-07-07 20:23:50
    由于公交车每到停靠站点才会统计客流量,而离开站点时需要核对车内总人数,所以公交车人数统计系统具有间断性;另外,无论是在停靠站点还是车体运行时检测乘客目标,以摄像头为参照物,公交车永远是静止不动的(车厢...
  • 篮球赛场数据统计系统数据库设计实例 研究内容(篮球赛场数据统计系统)  1)比赛前对球队、球员各项基本信息的录入功能;  2)比赛时对进球得分、各种进球类型以及裁判评判情况等数据和信息的记录功能;  ...
  • ps:承接上一篇通讯录管理系统的设计,模仿代码设计全国地区新冠感染人数统计系统 该系统主要包含的功能有:1、添加统计数据 2、显示统计数据 3、查找特定数据 4、修改统计数据 5、清空统计数据 查找功能分为三种...
  • 埋点统计系统架构图

    千次阅读 2016-02-25 21:30:35
    埋点统计系统   一、背景  统计系统页面的点击情况,并生成数据分析报表。    二、架构图     三、实现 3.1 页面埋点  &lt;div class="tab-content"&gt; &lt;table&...
  • Cocos Creator 接入全平台统计系统

    千次阅读 2018-05-12 17:25:56
     对于游戏的统计我想大家应该都有这样的需求:1、全平台统计(Web,IOS,Android)2、丰富的维度3、个性化定制 CocosCreator最近推出了自己的统计系统,我觉得他是符合上面那些需求的,但是web统计貌似只能企业才能...
  • 好多粉-微信号复制统计工具,来粉统计,微信号复制统计系统更新落地页微信号自动控制功能,免费使用好多粉加粉统计,微信号复制统计,关键词复制统计工具,微信号在线管理等工具。使用方法:1. 第一步,新建微信号...
  • Piwik——最好用的开源网站统计系统

    千次阅读 2017-06-26 14:04:53
    Piwik——最好用的开源网站统计系统 前段时间一直在寻找开源的网站统计系统,在网上发现了开源的Piwik,它简洁强大,有时一个开源的系统,可以部署在任何PHP+MYSQL上,可以说是最好用的网站统计系统,如果你不想...
  • 流量统计系统架构设计

    千次阅读 2012-06-28 01:52:56
    流量统计系统架构设计: 因为公司有需求,需要重新设计一套流量分析系统,这次设计我融入了很多新型服务器架构软件,例如Nginx,Redis,Python 主架构设计是这样的: NGINX设定为流量统计系统的servlet服务器
  • Android:如何统计系统的启动时间

    千次阅读 2017-07-22 11:37:27
    这里所说的统计系统的启动时间,并不是简单地用秒表和肉眼来统计,而是通过分析系统输出的log信息来统计,这样才显得更加专业。   首先了解2个概念:   1. Android是基于Linux内核的系统,因此Android的启动过程...
  • 国内客流统计系统市场,经过十几年的角逐竞争,市场从蓝海到红海。图像/视频处理技术类公司,安防监控类公司,零售系统开发软件公司,视频传输等设备厂商纷纷入局,目前市场上能提供客流统计系统的公司,不下几十家...
  • 某校要召开运动会,现要求开发一个学校运动会成绩统计系统,参加运动会有n个系,比赛分成m个男子项目,和w个女子项目。 系信息(系编号为D01……n,系名称Dname), 运动员信息:(编号,姓名,性别,所属系,成绩...
  • 药店的药品销售统计系统(排序应用) 要用到文件操作,使用CodeBlocks,建议放到同一目录下 [问题描述] 设计一系统,实现医药公司定期对销售各药品的记录进行统计,可按药品的编号、单价、销售量或销售额做出...
  • 本文介绍的是一种替代这一类产品的开源系统——piwik,功能上一点不输于商业的统计系统。 piwik 目录[-] piwik简介 piwik安装 piwik使用 JS追踪 log追踪 其他一些问题 piwik简介 最近...
  • 本文是《Android内核开发》系列的第七篇文章,通过上一篇文章《Android内核开发:图解Android系统的启动过程》我们大致了解了Android系统的启动过程,那么本文就从实践的角度,简单...这里所说的统计系统的启动时间,
  • 国内有很多著名的网站统计系统,如: CNZZ,51La,百度统计,量子统计等,但是国内的统计网址几乎都是实时的,对于国外的统计网站来说,大家比较熟悉的是Google Analytics,StatCounter,SiteMeter等,其中GA不是...
  • 主流网站统计系统的评测

    千次阅读 2007-12-14 17:42:00
    【摘自www.twocity.cn】 随着网站的日益增多,统计系统的重要性已经凸现出来,本文选择了站长常用的或是很有特色的几个网站统计系统,并进行测试和分析,希望能给站长一些参考! Google Analytics 首先介绍Google ...
  • C语言进阶 (一)前言: 在学习c语言基础语法后我们需要通过...(二)题目要求:学生成绩统计系统实现 设计结构体数组,结构中包含学生数据为:学号、姓名、物理分数、数学分数、外语分数、计算机分数。 设...
  • clicki网站统计系统logo设计

    万次阅读 2011-11-15 17:28:27
    clicki网站统计系统logo 小编最近给博客增加了个个性的统计工具《clicki网站统计》刚开始就因为其独特的界面所吸引,其中很多工具都显得很实用,其中最经典的应该莫属于Widgets插件,可以最便捷的方式即时查看...
  • 数据结构(C语言)课设5——药店的药品销售统计系统 题目描述: 你在一个L药品店当一名仓库管理员,马上就要放寒假回家过年了,L药店对这一年的销售情况做了统计,但这看起来太杂乱无章了。因此,你需要对这些数据...
  • 可能你还不知道,他们都是利用第三方工具微信复制统计后台强化自己的推广效果,具体问题具体分析,解决各种投放难题! 一:加粉复制统计 支持移动端和PC端统计,可以统计长按复制,点击复制,发送短信,拨打电话,...
  • 五、药店的药品销售统计系统(排序应用)

    千次阅读 多人点赞 2017-04-05 21:22:45
    五、药店的药品销售统计系统(排序应用) [问题描述] 设计一系统,实现医药公司定期对销售各药品的记录进行统计,可按药品的编号、单价、销售量或销售额做出排名。 [实现提示] 在本设计中,首先从数据文件中读出...
  • 分布式环境下,用F5做负载均衡,如何获得系统用户实时在线人数? 怎么去确认连接是否断开,http协议里面,有什么属性可以判断连接一直未断开吗?
  • C语言 成绩分析统计系统

    千次阅读 2019-05-12 22:05:04
    printf("\t\t学生成绩分析系统\n"); printf("★★★★★★★★★★★★★★★★★★★★★★★★\n"); printf("★\t ※1.求总分,平均分,最高分,最低分 ★\n"); printf("★\t ※2.分段统计人数和及格率 ★\n")...
  • 职工工作量统计系统设计

    千次阅读 2020-06-03 15:49:19
    文档,基于java+jdbc 运行图,其中gui改为控制台输入,其他均按照文档标准 代码:稍后
  • 一般展会的举行时间都在3-7天之间,展会作为市场部门一项...目前成熟的解决方案有,视频客流统计、WiFi客流统计、红外线客流统计等方式。由于展会通常的时间都比较短,视频、红外线客流统计采购及安装价格都比较高,需

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 913,848
精华内容 365,539
关键字:

统计系统