Laravel无法写入日志的问题

最近遇到一个比较严重的问题,某一天的凌晨时分,运营人员反馈,某个线上系统全部无法访问,当时正在家中昏昏欲睡,收到消息后脑子瞬间惊醒。遂登陆后台查看得知,深夜11点多日志正常,但几分钟后的第二日凌晨却全部over了。于是想到和之前某次遇到的问题相似,经过排查发现,果然是日志权限问题。经过梳理,问题如下:

由于系统中有定时任务自动执行,而启动定时任务时是非www用户,同时该任务中含有写日志的部分(按天生成日志),此时目录下尚无当日的日志,于是生成了权限为非www用户的日志文件,当后续www用户操作需要写日志时,就没有写入的权限了,因此后续程序全部被中断。

经过比较,解决方案如下:

第一种,取消正在运行的定时任务,指定执行定时任务的用户为www用户,重新启动定时任务;

第二种,为了不影响线上业务,不取消定时任务,用脚本定时检查日志文件是否对www开放写入权限,如果没有则加入;

第三种,利用laravel日志中的channel功能,在www用户需要写入日志的地方,指定channel写入,不与定时任务产生的日志文件相冲突。

【转】浅谈算法和数据结构: 哈希表

作者: yangecnu(yangecnu’s Blog on 博客园)

出处:http://www.cnblogs.com/yangecnu/ http://www.cnblogs.com/yangecnu/p/Introduce-Hashtable.html

基于无序列表的顺序查找,基于有序数组的二分查找,平衡查找树,以及红黑树,下图是他们在平均以及最差情况下的时间复杂度:

Different structure with different efficient

可以看到在时间复杂度上,红黑树在平均情况下插入,查找以及删除上都达到了lgN的时间复杂度。

那么有没有查找效率更高的数据结构呢,答案就是本文接下来要介绍了散列表,也叫哈希表(Hash Table)

什么是哈希表
哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。

哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。

使用哈希查找有两个步骤:

使用哈希函数将被查找的键转换为数组的索引。在理想的情况下,不同的键会被转换为不同的索引值,但是在有些情况下我们需要处理多个键被哈希到同一个索引值的情况。所以哈希查找的第二个步骤就是处理冲突
处理哈希碰撞冲突。有很多处理哈希碰撞冲突的方法,本文后面会介绍拉链法和线性探测法。
哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。

哈希函数
哈希查找第一步就是使用哈希函数将键映射成索引。这种映射函数就是哈希函数。如果我们有一个保存0-M数组,那么我们就需要一个能够将任意键转换为该数组范围内的索引(0~M-1)的哈希函数。哈希函数需要易于计算并且能够均匀分布所有键。比如举个简单的例子,使用手机号码后三位就比前三位作为key更好,因为前三位手机号码的重复率很高。再比如使用身份证号码出生年月位数要比使用前几位数要更好。

在实际中,我们的键并不都是数字,有可能是字符串,还有可能是几个值的组合等,所以我们需要实现自己的哈希函数。

  1. 正整数
    获取正整数哈希值最常用的方法是使用除留余数法。即对于大小为素数M的数组,对于任意正整数k,计算k除以M的余数。M一般取素数。
  2. 字符串
    将字符串作为键的时候,我们也可以将他作为一个大的整数,采用保留除余法。我们可以将组成字符串的每一个字符取值然后进行哈希,比如

public int GetHashCode(string str)
{
char[] s = str.ToCharArray();
int hash = 0;
for (int i = 0; i < s.Length; i++)
{
hash = s[i] + (31 * hash);
}
return hash;
}
上面的哈希值是Horner计算字符串哈希值的方法,公式为:

h = s[0] · 31L–1 + … + s[L – 3] · 312 + s[L – 2] · 311 + s[L – 1] · 310

举个例子,比如要获取”call”的哈希值,字符串c对应的unicode为99,a对应的unicode为97,L对应的unicode为108,所以字符串”call”的哈希值为 3045982 = 99·313 + 97·312 + 108·311 + 108·310 = 108 + 31· (108 + 31 · (97 + 31 · (99)))

如果对每个字符去哈希值可能会比较耗时,所以可以通过间隔取N个字符来获取哈西值来节省时间,比如,可以 获取每8-9个字符来获取哈希值:

public int GetHashCode(string str)
{
char[] s = str.ToCharArray();
int hash = 0;
int skip = Math.Max(1, s.Length / 8);
for (int i = 0; i < s.Length; i+=skip)
{
hash = s[i] + (31 * hash);
}
return hash;
}
但是,对于某些情况,不同的字符串会产生相同的哈希值,这就是前面说到的哈希冲突(Hash Collisions),比如下面的四个字符串:

hash code collision

如果我们按照每8个字符取哈希的话,就会得到一样的哈希值。所以下面来讲解如何解决哈希碰撞:

避免哈希冲突
拉链法 (Separate chaining with linked lists)
通过哈希函数,我们可以将键转换为数组的索引(0-M-1),但是对于两个或者多个键具有相同索引值的情况,我们需要有一种方法来处理这种冲突。

一种比较直接的办法就是,将大小为M 的数组的每一个元素指向一个条链表,链表中的每一个节点都存储散列值为该索引的键值对,这就是拉链法。下图很清楚的描述了什么是拉链法。

seperate chaining with link list

图中,”John Smith”和”Sandra Dee” 通过哈希函数都指向了152 这个索引,该索引又指向了一个链表, 在链表中依次存储了这两个字符串。

该方法的基本思想就是选择足够大的M,使得所有的链表都尽可能的短小,以保证查找的效率。对采用拉链法的哈希实现的查找分为两步,首先是根据散列值找到等一应的链表,然后沿着链表顺序找到相应的键。 我们现在使用我们之前介绍符号表中的使用无序链表实现的查找表SequentSearchSymbolTable 来实现我们这里的哈希表。当然,您也可以使用.NET里面内置的LinkList。

首先我们需要定义一个链表的总数,在内部我们定义一个SequentSearchSymbolTable的数组。然后每一个映射到索引的地方保存一个这样的数组。

public class SeperateChainingHashSet : SymbolTables where TKey : IComparable, IEquatable
{
private int M;//散列表大小
private SequentSearchSymbolTable[] st;//

public SeperateChainingHashSet()
    : this(997)
{

}

public SeperateChainingHashSet(int m)
{
    this.M = m;
    st = new SequentSearchSymbolTable<TKey, TValue>[m];
    for (int i = 0; i < m; i++)
    {
        st[i] = new SequentSearchSymbolTable<TKey, TValue>();
    }
}

private int hash(TKey key)
{
    return (key.GetHashCode() & 0x7fffffff) % M;
}

public override TValue Get(TKey key)
{
    return st[hash(key)].Get(key);
}

public override void Put(TKey key, TValue value)
{
    st[hash(key)].Put(key, value);
}

}
可以看到,该实现中使用

Get方法来获取指定key的Value值,我们首先通过hash方法来找到key对应的索引值,即找到SequentSearchSymbolTable数组中存储该元素的查找表,然后调用查找表的Get方法,根据key找到对应的Value。
Put方法用来存储键值对,首先通过hash方法找到改key对应的哈希值,然后找到SequentSearchSymbolTable数组中存储该元素的查找表,然后调用查找表的Put方法,将键值对存储起来。
hash方法来计算key的哈希值, 这里首先通过取与&操作,将符号位去除,然后采用除留余数法将key应到到0-M-1的范围,这也是我们的查找表数组索引的范围。
实现基于拉链表的散列表,目标是选择适当的数组大小M,使得既不会因为空链表而浪费内存空间,也不会因为链表太而在查找上浪费太多时间。拉链表的优点在于,这种数组大小M的选择不是关键性的,如果存入的键多于预期,那么查找的时间只会比选择更大的数组稍长,另外,我们也可以使用更高效的结构来代替链表存储。如果存入的键少于预期,索然有些浪费空间,但是查找速度就会很快。所以当内存不紧张时,我们可以选择足够大的M,可以使得查找时间变为常数,如果内存紧张时,选择尽量大的M仍能够将性能提高M倍。

线性探测法
线性探测法是开放寻址法解决哈希冲突的一种方法,基本原理为,使用大小为M的数组来保存N个键值对,其中M>N,我们需要使用数组中的空位解决碰撞冲突。如下图所示:

open address

对照前面的拉链法,在该图中,”Ted Baker” 是有唯一的哈希值153的,但是由于153被”Sandra Dee”占用了。而原先”Snadra Dee”和”John Smith”的哈希值都是152的,但是在对”Sandra Dee”进行哈希的时候发现152已经被占用了,所以往下找发现153没有被占用,所以存放在153上,然后”Ted Baker”哈希到153上,发现已经被占用了,所以往下找,发现154没有被占用,所以值存到了154上。

开放寻址法中最简单的是线性探测法:当碰撞发生时即一个键的散列值被另外一个键占用时,直接检查散列表中的下一个位置即将索引值加1,这样的线性探测会出现三种结果:

命中,该位置的键和被查找的键相同
未命中,键为空
继续查找,该位置和键被查找的键不同。
实现线性探测法也很简单,我们只需要两个大小相同的数组分别记录key和value。

public class LinearProbingHashSet : SymbolTables where TKey : IComparable, IEquatable
{
private int N;//符号表中键值对的总数
private int M = 16;//线性探测表的大小
private TKey[] keys;
private TValue[] values;

public LinearProbingHashSet()
{
    keys = new TKey[M];
    values = new TValue[M];
}

private int hash(TKey key)
{
    return (key.GetHashCode() & 0xFFFFFFF) % M;
}

public override TValue Get(TKey key)
{
    for (int i = hash(key); keys[i] != null; i = (i + 1) % M)
    {
        if (key.Equals(keys[i])) { return values[i]; }
    }
    return default(TValue);
}

public override void Put(TKey key, TValue value)
{
    int hashCode = hash(key);
    for (int i = hashCode; keys[i] != null; i = (i + 1) % M)
    {
        if (keys[i].Equals(key))//如果和已有的key相等,则用新值覆盖
        {
            values[i] = value;
            return;
        }
        //插入
        keys[i] = key;
        values[i] = value;
    }
}

}
线性探查(Linear Probing)方式虽然简单,但是有一些问题,它会导致同类哈希的聚集。在存入的时候存在冲突,在查找的时候冲突依然存在。

性能分析
我们可以看到,哈希表存储和查找数据的时候分为两步,第一步为将键通过哈希函数映射为数组中的索引, 这个过程可以认为是只需要常数时间的。第二步是,如果出现哈希值冲突,如何解决,前面介绍了拉链法和线性探测法下面就这两种方法进行讨论:

对于拉链法,查找的效率在于链表的长度,一般的我们应该保证长度在M/8~M/2之间,如果链表的长度大于M/2,我们可以扩充链表长度。如果长度在0~M/8时,我们可以缩小链表。

对于线性探测法,也是如此,但是动态调整数组的大小需要对所有的值从新进行重新散列并插入新的表中。

不管是拉链法还是散列法,这种动态调整链表或者数组的大小以提高查询效率的同时,还应该考虑动态改变链表或者数组大小的成本。散列表长度加倍的插入需要进行大量的探测, 这种均摊成本在很多时候需要考虑。

哈希碰撞攻击
我们知道如果哈希函数选择不当会使得大量的键都会映射到相同的索引上,不管是采用拉链法还是开放寻址法解决冲突,在后面查找的时候都需要进行多次探测或者查找, 在很多时候会使得哈希表的查找效率退化,而不再是常数时间。下图清楚的描述了退化后的哈希表:

hashCollision

哈希表攻击就是通过精心构造哈希函数,使得所有的键经过哈希函数后都映射到同一个或者几个索引上,将哈希表退化为了一个单链表,这样哈希表的各种操作,比如插入,查找都从O(1)退化到了链表的查找操作,这样就会消耗大量的CPU资源,导致系统无法响应,从而达到拒绝服务供给(Denial of Service, Dos)的目的。之前由于多种编程语言的哈希算法的“非随机”而出现了Hash碰撞的DoS安全漏洞,在ASP.NET中也曾出现过这一问题。

在.NET中String的哈希值内部实现中,通过使用哈希值随机化来对这种问题进行了限制,通过对碰撞次数设置阈值,超过该阈值就对哈希函数进行随机化,这也是防止哈希表退化的一种做法。下面是BCL中string类型的GetHashCode方法的实现,可以看到,当碰撞超过一定次数的时候,就会开启条件编译,对哈希函数进行随机化。

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical, __DynamicallyInvokable]
public override unsafe int GetHashCode()
{
if (HashHelpers.s_UseRandomizedStringHashing)
{
return InternalMarvin32HashString(this, this.Length, 0L);
}
fixed (char* str = ((char) this)) { char chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*) chPtr;
int length = this.Length;
while (length > 2)
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
numPtr += 2;
length -= 4;
}
if (length > 0)
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
}
return (num + (num2 * 0x5d588b65));
}
}

总结
基于无序列表的顺序查找,基于有序数组的二分查找,平衡查找树,以及红黑树,本篇文章最后介绍了查找算法中的最后一类即符号表又称哈希表,并介绍了哈希函数以及处理哈希冲突的两种方法:拉链法和线性探测法。各种查找算法的最坏和平均条件下各种操作的时间复杂度如下图:

search method efficient conclusion

在实际编写代码中,如何选择合适的数据结构需要根据具体的数据规模,查找效率要求,时间和空间局限来做出合适的选择

【转载】NASA航拍地球延时短片《宁静之美》

本文转载自v电影网(http://www.vmovier.com/52860?from=index_new_title)
NASA约翰逊航天中心10月27日发布了一段视频,与大家分享了我们星球上无与伦比的宁静之美。
我们共乘一艘太空飞船,名叫地球。
本片由宇航员Sergey Ryazanskiy, Paolo Nespoli, and Commander Randy Bresnik拍摄,记录了2017年八月至10月国际空间站在低轨道运行时所见的景象。
背景音乐:”The Sound of Silence” Written by Paul Simon.

关于git的一些使用分享


关于版本控制

什么是版本控制?我为什么要关心它呢?版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。我们除了可以对软件代码作版本控制管理之外,还可以在其他方面也实行版本控制,可以对任何类型的文件进行版本控制,例如多人协作完成大型的文学作品的时候。

版本控制又分为:

  • 本地版本控制系统
  • 集中式版本控制系统
  • 分布式版本控制系统

为什么使用版本控制(好处)

使用版本控制,方便多人协作开发时的代码合并;
可以使我们对文件,代码等的修改情况有一个清晰的记录,当新的修改不满意时,可以准确的回滚到先前的版本;
当本地代码因各种原因损坏时,可以迅速的从仓库还原至本地;
同时当多人协作的时候,可以明确成员各自做了什么修改,当代码/文本等产生bug的时候便于追根溯源.

常用工具

  • SVN(集中式)
  • Git(分布式)
    注意 git ≠ GitHub,一个是工具,一个是平台
git-logo
GitHub-logo

Git的安装

在 Linux 上安装

在Linux安装,以CentOS 上为例,一般使用 yum的方式:

  $ sudo yum install git


其他Linux版本参考Git 官方网站上的各种系统的安装步骤,网址为 http://git-scm.com/download/linux。

在 Mac 上安装

在 Mac 上安装 Git 有多种方式。 最简单的方法是安装 Xcode Command Line Tools。 Mavericks (10.9) 或更高版本的系统中,在 Terminal 里尝试首次运行 git 命令即可。 如果没有安装过命令行开发者工具,将会提示你安装。
或者前往下载相关安装包:http://git-scm.com/download/mac。

在 Windows 上安装

在 Windows 上安装 Git 也有几种安装方法。 官方版本可以在 Git 官方网站下载。 打开 http://git-scm.com/download/win,下载会自动开始。 要注意这是一个名为 Git for Windows的项目(也叫做 msysGit),和 Git 是分别独立的项目;更多信息请访问 http://msysgit.github.io/。

另一个简单的方法是安装 GitHub for Windows。 该安装程序包含图形化和命令行版本的 Git。 它也能支持 Powershell,提供了稳定的凭证缓存和健全的 CRLF 设置。 稍后我们会对这方面有更多了解,现在只要一句话就够了,这些都是你所需要的。 你可以在 GitHub for Windows 网站下载,网址为 http://windows.github.com。

配置

用户配置
如下配置一次,避免后面每次操作都需要输入用户信息

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

安装完成后,使用Git还需要做一些配置用于向远程仓库拉取和推送代码,其中需要在本地生成私钥和公钥文件,以Mac为例,执行以下命令:

    ssh-keygen -t rsa -C "xxx@hauxi100.com"

最后可以在 ~/.ssh 目录下找到生成的 id_rsaid_rsa.pub 文件,即相应的私钥和公钥,用编辑器打开查看id_rsa.pub 复制其中内容到GitHub或者GitLab用户中心相关的设置中,就可以开始使用Git了.

开始使用

1.对本地现有项目进行Git管理

进入项目根目录并使用如下命令进行初始化:

    git init

这样会在项目根目录生成.git目录,其中含有相关的文件
接下来需要将项目中的全部文件加入版本控制

    git add *.c

并提交到第一个版本,加入说明

git commit -m '第一次提交初始化版本'

2.从仓库获取项目

此时需要知晓仓库地址并进行git clone操作,仓库地址在GitHub/Gitlab网站上可以看到 例如成都范儿项目:

    git clone git@**.***.***:****/***.git

等待项目克隆完毕即可开始开发,上述命令后跟上自定义目录名可以将项目克隆到指定目录中
使用git remote -v 列出远程仓库
使用git remote show origin查看远程仓库更多信息

3.提交第一次修改

每次当对项目进行修改,删除,新增后需要提交新版本时,需要执行以下命令:

    git status

用于查看有何变化,这会列出当前项目的变化情况,包含较于上一版本有变化的文件信息

On branch production
Your branch is up to date with 'origin/production'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   ../****/***/***/VoteController.php

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    ../***/***/****/laravel-debugbar/
no changes added to commit (use "git add" and/or "git commit -a")

此时我们应该将状态为modified的文件加入新版本中执行:

    git add ../****/***/***/VoteController.php
    git commit -m "这是一次提交,修改了某某文件...."

此时完成了一次提交,但是并没有推送到远程仓库中,还需要:

 git push

这样这个版本的代码已经推送到远程仓库,其他用户执行git pull(相当于fetch => merge)即可拉取到刚刚推送的修改(也可使用git push origin xxxx来推送到指定分支)

4.进一步操作

每次需要向仓库做操作时应该先执行git status查看当前项目有哪些发生变化;

查看尚未add的文件相比之前发生什么变化,使用git diff命令(这个命令本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动);

需要查看最近提交过哪些版本时,使用git log查看版本信息(包含版本号,提交者,提交时间等),其中git log -p -2 可以查看最近两次提交的内容差异

合并

git merge xxx

解决冲突

有时候合并会遇到文件冲突,需要先git status查看冲突情况,并且手动去解决冲突文件,然后使用git add文件

当把错误代码提交到commit时,需要使用git reset --hard xxxx来撤销这条commit

当错误代码已经push到仓库,使用git revert xxxx来撤销push

  • 如果出错内容在私有 branch:在本地把内容修正后,强制 push (push -f)一次就可以解决;
  • 如果出错内容在 master:不要强制 push,而要用 revert 把写错的 commit 撤销。

5.在IDE中使用Git(PHPstorm)

项目中右键->git->各个详细操作

其他

在 Git 中有一个特殊的文本文件:.gitignore。这个文本文件记录了所有你希望被 Git 忽略的目录和文件。

常用命令

git checkout  ##检出 (用于创建/切换分支),**慎重使用**

git status  ##查看当前项目状态(修改,新增,删除等)

git checkout -b ##检出(指定分支)

git commit  ##提交版本

git diff  ##比较文件差异

git add  ##提交指定文件到版本

git clean ##删除所有没有被版本控制过的文件

git fetch --prune  ##从仓库获取内容

git pull --ff-only  ##获取内容并且合并

git branch  ##查看当前分支名
git branch -d xxx ##删除分支

git push  ##推送

git merge xx ##合并分支

git rm  ##删除文件

git mv  ##移动文件 相当于mv => rm => add操作

git rebase ##高级合并

git reset  ##撤销commit 

git revert  ##撤销push

git push origin :yourbranch  ##推送到指定分支

部分内容参考出处 Git 原理详解及实用指南

Yii2批量插入/更新数据

在使用yii2开发项目时,有时候会遇到这样的情况: 向后台发送多条数据,其中一些数据已经存在记录,只需要对其部分字段的值进行修改;而另一部分的数据则需要新添加进去. 这就需要对添加的数据进行判断,其中一些执行update,剩下的执行insert 代码如下,不对的地方请指教:

```
....其他代码
//批量更新,并将需要批量插入的数据放入数组中
foreach($goods as $k => $v)
{
    if(yourModel::updateAllCounters(
        ['goods_num' => $v],
        ['goods_id' => $k,'user_id' => $id] 
    ))
    {
        continue;//如果已经更新,则跳过此次循环,到下一次
    }
    $data[] = [
        'user_id' => $id,
        'goods_id' => $k,
        'goods_num' => $v,
        'created_time' => $time,
    ]
}
//再执行批量插入
if (isset($data)) 
{
    Yii::$app->db->createCommand()
             ->batchInsert(yourModel::tableName(),['user_id','goods_id','goods_num','created_time'],
             $data)
             ->execute();
}
......其他代码
```

ECharts数据可视化–以H5统计项目为例

前言

目前,每到各种节假日和营销事件出现的时候,朋友圈会转发各种H5,比如2018年底的年终总结类的,其他时候各种测评类的,以及商家为营销做的各种趣味游戏类的.公司也做了大量的H5,累计了大量的数据,这些数据应该如何产生价值?

1.通过H5可以获得什么数据?

H5是移动互联网时代重要的传播形式,通过它,可以累计的足够多的数据(包括用户之间的阅读,分享,转发等).根据经验,我们不难得出下面的结论

  • 根据用户之间的转发关系和分享链,可以发掘出每一场传播中的KOL是哪些人群;
  • 根据每个H5的转发总量,可以知道信息最终传达到多少用户手中;
  • 而根据不同时间段的阅读次数,可以得到用户使用移动设备的高低峰时间,从而指导我们在正确的时间去发送和传播内容;
  • 分析用户的地区,设备品牌,型号,使用场景,用户性别等数据可以制订出更加精准的内容和营销策略,进一步提升效率….

2.关于数据可视化

目前开发人员经常接触和使用的一般是Echartshighcharts 两款js插件,文档都比较齐全,使用比较方便.不仅能渲染柱状图,折线图.饼状图等基本图表,还可以渲染更高级的内容

简单的折线图
基础柱状图
力导向图(可用于分析用户间传播趋势)

echarts 和 highcharts 使用方法大同小异,都是在页面中引用库文件,同时提供对应格式的数据进行初始化并且执行渲染.

简单的使用例子:

<script src="https://cdn.bootcss.com/echarts/4.2.0-rc.2/echarts.min.js"></script>
    <div id="main" style="width:auto;height:300px;"></div>
    <script>
        {{--绘制图表,初始化数据--}}
        echarts.init(document.getElementById('main')).setOption({
            series: {
                type: 'pie',//选择类型
                data: [
                    {name: 'A', value: 1212},//提供数据
                    {name: 'B', value: 2323},
                    {name: 'C', value: 1919}
                ]
            }
        });
    </script>

而我们目前在项目中使用的基本是通过PHP查询数据表中数据,整理成如上述代码中的data参数所需要的格式,并且在整个个页面加载前就直接转换为json并直接在页面保存,供js使用

3.关于后台处理逻辑

目前H5统计项目原理:

(1).综合浏览趋势

综合浏览趋势数据主要为查询表中指定H5_id,且类型为浏览的结果,并根据时间进行分类,最终将结果返回页面

综合浏览趋势数据:

(2)综合传播渠道

综合传播渠道目前统计前5级(0~4)传播量,做了两个处理方案:
–1.前端处理,原链接URL不带有传播层级参数,用户主动浏览,属于第0级传播,请求接口时传参level=0;当用户进行分享时,检测当前链接中是否带有level参数(level在URL中混淆加密处理),若有则level+=1,否则level=1,以此类推,减轻服务器处理的压力;
–2.后端处理,每小时自动运行,后端对数据表中的分享记录进行递归处理,查询指定H5_id的记录,根据用户openid分组去重,和若没有父级节点,则当前传播为第0级传播,若有父级节点,则再查询相同H5_id且openid=父级节点的记录,以此类推,假如查询了三层父节点时停止,则表示当前传播为第3级….这种方式随着数据量的提升,运算规模会变为N的N次方的N次方…容易导致内存溢出,因此目前使用的是第一种方案

(3)综合分享去向

这里比较简单,根据分享类型查询数据,并且去重即可

(4)综合分享关系

原理类似综合传播渠道,查询的时候还带上了用户之间的分享关系,同时带上了用户性别,分享数量等数据,位于中心点的向四周发送数据的点表示分享者,分享线条越多,该用户越可能是KOL,是传播该H5的关键用户

(5)pv地域分布

此处的数据是根据页面请求分享记录接口时,服务到获取到的ip地址进行处理而得来的用户地域分布,使用了纯真ip地址库解析出物理位置,并且渲染数据而得来

(6)用户设备品牌

根据前端发送的user-agent信息匹配而来,目前识别的主流的手机品牌,其余未能识别的归入到其他品牌中

(7)用户性别分布

根据获取用户微信授权时拿到的基本数据得来

(8)用户设备操作系统和使用网络

获取方式同(6)

(9)关键传播用户

原理同传播渠道和分享关系类似,数据展示为表格化,包含用户基本信息和头像

4.关于对目前界面的优化建议

页面分页分部分处理;
H5pv/uv排行;
缺失数据的处理;
每一个H5统计中根据文章/H5的标签给KOL用户贴标签;