移动游戏数据分析-----行业背景知识 移动游戏数据分析(入门篇)

此博客为转载,原作者地址请点击此处

移动游戏数据分析(入门篇)

前言:

最近一段时间在做部门网游业务的分析沉淀和框架培训,刚好应网大的需求在公司内开设游戏产品数据分析师认证的课程,因此开始尝试把这几年做数据分析的经验和心得整理出来,希望能形成一份相对完整的 游戏数据分析 的培训材料;

根据以往的培训和招聘经验,个人把游戏数据分析师分为3个层次:
asd
“入门篇”主要把“产品健康度”监控相关的指标(描述性指标,告诉我们是什么)做一个系统的梳理,希望能够帮助刚刚入行或准备入行的朋友,快速熟悉游戏运营分析相关的指标含义及应用场景;

关于渠道优化、运营活动分析、流失分析、用户行为分析等具体案例分析会在“进阶篇”中跟大家分享;

在这里还是要重申一个观点:

  • 1、数据分析的本质是一种意识,一种以客观事实为导向进行产品管理和客户管理的意识;
  • 2、数据分析师本质上是一个产品分析师,只是在分析的过程中从数据的角度进行切入而已;
  • 3、数据分析的价值在于数据应用,没有业务理解和对各部门作业流程的详细了解,是无法对数据作出分析和解释的;不熟悉业务的数据分析师只能称为“数据取数员”;

正文:

对移动游戏数据这块, 我一般喜欢用经典的“水池图”来做说明;
这里写图片描述
作为CP,无论我们从什么角度做数据分析,最终还是希望能够帮助我们更好的实现最终目的:赚到更多的¥

从一个庸俗易懂的公式出发:

Revenue = AU * PUR *ARPPU

统计周期内的收入流水 = 统计周期内的活跃用户规模 * 活跃用户付费比例 * 平均每付费用户付费金额;

因此,我们要做的事情是:“最大化活跃用户规模,并在此规模之上最大化用户付费转化及付费强度”.

【最大化活跃用户规模】:如果我们把当前的活跃用户看做一个水池,要想提升水池内的含水量,我们可以有几种做法:

1.开源:让更多的水注入,导入更多用户;通过市场推广:

  • 1.1拓展新渠道;
  • 1.2增加推广费用,提高渠道导入、媒体广告导入量;
  • 1.3自有资源与其它APP换量;
  • 1.4口碑管理、增加市场认知度和认同度,提高自然导入量;

2.节流, 减少水池的出水量,降低用户流失;

  • 2.1.通过运营活动、版本更新 提高用户的游戏参与度(玩的更久)
  • 2.2.通过老玩家召回的活动,唤醒沉默用户;可以想象成,水池中的部分水分被蒸发,并没有真正的离开流走,可以再通过降雨的方式重新回到水池中;

【最大化用户付费转化及付费强度】:在维持水池水量的同时,我们可以通过各种养殖和捕捞的方式(游戏内的消费埋点、促销、充值活动等)打到更多的鱼;

当然,价值挖掘 和 用户规模的维护 并不是完全割裂开的,过度的追求高ARPPU也有可能导致用户的流失增加;这是一个相辅相成的过程;

综上所述,移动游戏数据分析指标可以分解为3个模块:

1、市场推广相关指标(包括:激活、上线、各节点转化率、成本指标、渠道质量等),它的任务是帮助我们进行“渠道优化”和“产品优化”,最小化用户获取成本,实现更多的新增导入;

2、用户活跃 & 留存相关指标(包括:DAU\MAU、AT(日均使用时长)、日、周、月留存、回归率等),它的任务是帮助我们在宏观数据表现层面,快速判断产品存在的问题,并对运营活动及产品改进给予“方向性”指导;

3、用户付费相关指标(包括:LTV、PUR(活跃用户付费比)、ARPPU(每付费用户付费强度)、充值结构、充值时段等),它的任务也是帮助我们在宏观数据表现层面明确产品盈利能力,并对运营活动及产品改进给予“方向性”指导。


市场推广相关指标

《【入门篇】移动游戏数据分析框架-概论》中提到我们可以通过增加水管或者把水龙头拧的更大一些来增加注水量,移动游戏的用户导入主要有4个途径

1、各分发渠道联运资源的导入;
2、付费广告导入(广告联盟、应用墙等);
3、自有资源与其它CP的免费互换;
4、用户的自然导入;

这里插播先简单介绍以下移动游戏推广的几种结算方式

1、CPS(Cost per Sale) 按销售额计费 :根据网络广告所产生的直接销售数量而付费的一种定价模式可以是按销售额分成。

  • 各渠道联运:渠道商用自有流量为游戏导入用户,根据相关用户的充值流水,按照事先规定好的分成比例进行结算

  • 官方商店(AppStore、Google Play等):用户通过官方商店下载的游客户端充值后,根据充值流水按照固定的比例支付渠道费用;

2、CPD(Cost per Day) 按天数计费(或 包时段付费):在固定时间内(通常是24小时)买断固定广告位。互联网媒体广告一般采用这种结算方式,如视频类、banner、弹窗等;广告主在固定时段内买断广告位,媒体不保证导入的用户量;

3、CPC(Cost per Click) 按点击付费:根据广告被点击的次数收费。关键词广告一般采用这种定价模式;

4、CPA(Cost per Action) 按行动付费:对于用户行动有特别的定义,包括形成一次交易、获得一个注册用户、或者对网络广告的一次点击等;在移动游戏领域,“A”通常可拆解为 Down(按下载付费)、Activation(按激活付费)、Login(按登录付费);联盟广告、应用墙 一般采用这种结算方式。

回到拉新的任务上,我们在做市场推广的时候,除了
1、上新渠道,拿到更多的联运资源
2、增加市场费用,增加更多广告导入
3、提升产品质量、做好口碑管理增加自然导入

还可以从哪些方面帮助我们提升新增用户的导入数量和质量?这里,我们先对“新增用户”做一个定义:

NU(New Users) 新增用户:统计周期内,首次注册账号并登录游戏的用户;

备注:入门篇中所定义的“用户”均以“账号”进行衡量;账号:游戏账号库中的唯一标识,在单款游戏中全局唯一;

=================1、通过导入环节各个节点的优化来提升新增导入量===================

我们都会关注用户新增之后的流失行为,并通过各种数据分析来发现可能导致用户流失的原因并作相应的功能调整;其实,从用户看到广告素材开始,你的用户“流失”就已经开始了;

我们可以对“从用户看到或得知信息开始,到用户登录游戏”的每一个步骤进行拆解:

曝光、点击、下载、安装、激活、注册、登录,在这些数量指标的背后我们关注的本质是一个 转换率;

我把转化率定义为:在产品设计的每个可控环节当中进行埋点,并监控每个节点的漏斗转换,用于帮助发现产品设计中的问题;

通过改善这些环节,我们的可以获得更多的新增;
这里写图片描述
由于技术问题,在移动广告和渠道推广监控过程中,通常情况下我们只能获取到 点击->激活->登录 这3个节点的数据;(具体的原因,我会在《【高阶篇】渠道监控平台设计及开发》 中说明)

首先,是“曝光->点击”环节:

Clicks 点击次数:广告素材被点击的次数,一台设备可以产生多次点击;

唯一点击数:规定时间段内,广告被同一台设备多次点击只计算1次;

在移动广告投放中,一般用户点击之后直接跳转到下载地址;所以通常情况下,点击即代表用户开始下载;

也有部分会先跳转到游戏介绍详情(比如:给 AppStore包代量的时候),然后在点击下载按钮;但是这个时候通常跳转的是媒体或分发市场自己的下载地址,所以无法获取下载节点的数据;

影响点击量的因素 主要有 位置、Icon、广告素材等;很多CP在做市场推广之前是没有做素材测试的,在正式推广之前,买一些联盟和积金墙的量 用于做素材的 A/B test 是很有必要的;一个好的素材对点击量有非常大的影响;

其次,是“点击->激活”环节:

Activations 激活数:通过广告下载客户端并安装后,打开客户端的设备数;

激活率:激活数 / 唯一点击数;

在移动端上,通常情况下载完成后都会自动弹出安装提示;因此影响激活率的因素 主要有:
1、包大小、联网环境、运营商 这些因素会影响用户的下载成功率;
2、程序BUG 影响客户端安装成功率;

最后,是“激活->登录”环节:

Logins 登录数:通过广告下载安装客户端后,打开客户端并登录游戏的账号数;

激活登录率:登录账号数 / 激活数;

在移动端上,通常情况下载完成后都会自动弹出安装提示;

因此影响激活率的因素 主要有

包大小、联网环境、运营商 这些因素会影响用户的下载成功率;

程序BUG 影响客户端安装成功率;

除了优化产品自身的一些细节,提高各个环节的转化率外;对渠道各项转化率指标的长期监控,以及追踪不同渠道、媒体来源用户的后续质量(包括:登录、活跃、留存、付费等),能够帮助我们快速发现渠道异常、调整广告投放策略等;

=================2、通过新增后续行为分析以及实时监控优化投放策略===================

一、通过对转换率的实时监控,当某个环节数据出现异常的时候可以及时发送警报通知管理员,及时排查是媒体的原因,还是CP自身的原因;

二、在用户新增登录环节之后,对其后续的行为做持续跟踪,用于判断渠道来源的用户质量

常用的指标主要有以下几个:
2
OSUR(One Session User Rate) 一次会话用户占比:用户从新增之日起截止统计当日只有一次登录行为,且登录时长地域规定阈值的用户数,占统计周期内新增用户的比例;

ACT_N(Active N_Day)新增N日活跃:新增用户在其首次注册登入后第N天还有登录的用户占新增用户的比例;在一次用户占比没有太大差异的情况下,用于对比不同渠道用户质量;

LTV_N(Lift Time Value N_Day)生命周期价值:平均一个账号在其生命周期内(第一次登录游戏到最后一次登录游戏),为该游戏创造的收入总计;结合导入成本 CPL(每登录用户成本) 来判断产品的回本周期;

通过数量指标、转化率指标、质量指标 和 成本指标 的分析,结合实际业务需求来不断调整广告投放策略;如,一些媒体能够导入大量用户但是用户质量较低,就适合新游冲榜;有的媒体很难导入用户,但是导入的用户质量都非常好,适合长期稳定投入等;

综上所述,可以将市场推广相关指标分为4类:数量指标、转化率指标、质量指标和成本指标.
3


活跃&留存相关指标

1、活跃相关的指标基本都是描述性指标,DAU、MAU、AT、MAU/DAU、PCU、ACU,除了描述游戏的在线规模和用于一些异常监控以外,还有什么用。

2、留存是最蛋疼的事情,因为市面上有各种各样的留存算法,各有各的道理,但是不知道他们之间的区别到底是什么,在哪些情况下应该应用哪些算法。

还是回到CP的根本任务“最大化活跃用户规模,并在此规模之上最大化用户付费转化及付费强度”;

最大化活跃用户规模可以拆解为2个部分:
1是规模,更多的人玩,除了通过增加新增导入以外,还需要延迟用户生命周期(玩的更久)也就是提高留存,再有就是沉默用户的唤醒;
2是活跃,更高的参与度(每日游戏时长,每月游戏天数),在固定周期内,用户参与游戏的时间越久,我们就越有机会让用户转换为付费用户;

在做进一步讲解之前,我们先对活跃用户进行一下定义

AU(Active Users)活跃用户:统计周期内,登录过游戏的用户数;根据统计周期不同又划分为DAU(日活跃用户),WAU(周活跃用户),MAU(月活跃用户);

备注:所定义的“用户”均以“账号”进行衡量;账号:游戏账号库中的唯一标识,在单款游戏中全局唯一;

回到正题,活跃比较好理解,所以我们先来说留存,业内有很多留存的算法,首先我们来看一下最简单的留存定义:

DRR(Daily Retention Rate)日留存率:统计当日登录过游戏,且后一日也有登录游戏的用户 占 统计当日活跃用户的比例

WRR(Weekly Retention Rate)周留存率:统计当周登录过游戏,且下一周至少登录一次游戏的用户占 统计当周活跃用户比例;

MRR(Monthly Retention Rate)月留存率:统计当月登录过游戏,且下一月至少登录一次游戏的用户占 统计当月活跃用户比例;

简而言之,就是当前统计周期(日、周、月)内有登录游戏的用户,在下一个统计周期内还有登录过的用户,即为留存用户;所以留存率的时效性会延迟一个统计周期.
这里写图片描述
关于 日留存率 业界有一个拓展定义:统计当日登录游戏的用户,在之后N日内至少登录一次游戏的用户 占 统计当周活跃用户比例;
这里写图片描述
为什么要做这样一个拓展定义?我们在做数据分析的时候,留存率只是告诉我们一个值,这个值本身意义不是非常大,但是流失它可以帮助我们发现游戏存在的问题;

大多数人在做数据分析的时候,都会干一件事情,把“流失玩家”的等级分布拉出来,计算一个等级流失率,观察出现流失高峰时候的用户状态,在通过这次状态去反推游戏设计上可能存在的问题;这个时候就“流失”判断的精度要求就比较高,只有发现真正意义上的流失用户,在去排查他们在流失之前的行为、流失当下的属性等,才能更准确的帮助我们发现游戏内的问题;

那么,在简单的留存算法下,定义的流失会有2个问题:
1、精度不高,用户在某天或某周没有登录,不代表用户“不玩”游戏,有可能只是刚好没有登录而已;
2、在计算周、月留存的时候,对每个个体存在不公平现象,A 用户周一注册,周三,周五,周天登录后流失;B 用户周五注册,下周一再次登录后流失;那么在周留存计算中,会认为B用户是 周留存用户,而A用户是周流失用户;其实A玩的时间比B更久;

拓展后的日留存定义,本质上是在尝试定义精确的流失;

Users Leave用户流失:统计日登录游戏后,在随后N日内未登录过游戏的用户;

笔者通过某平台的登录流水数据进行计算,N=1 流失概率56.71%;N=7 流失概率 95.16%;N=14 流失概率98.56%;以N=14为例,即一个账号连续14天不登陆游戏,则再次登录(自然上线或通过运营活动召回)的概率不到1.44%;

在定义精确流失之后,在RPG游戏中一个经典的应用就是计算等级流失高峰;
这里写图片描述
如果只是单纯的把流失等级分布拉出来意义不大,因为必然是低等级流失的用户最多,所以通常情况下我们可以观察每个等级的流失概率;

等级流失概率:截止统计当日,某等级的流失用户数 / 服务器上大于等于该等级的所有用户数;

以上留存算法都是以统计周期内的所有用户为基数进行的计算;

在页游时代和手游时代 针对新增用户,还有另外一套留存算法,我更喜欢称为新增用户活跃度.

也就是大家平时经常听到的次日留存、7日留存、14日留存等

ACT_N(Active N_Day)用户活跃度:统计周期内,新增用户在随后不同时期的登录情况;

公式:ACT _N = 统计周期内,一批新增用户在其首次登入后第N天还有登录的用户数 / 新增用户数;

备注:活跃度需要长期跟踪,根据需求可以设定30日、60日 或 90日;ACT仅针对统计周期内新增账号进行观察;

主要的作用是帮助CP、发行商和渠道商 快速的判断产品的质量;

如何应用 留存的数值对产品在线做估算,会在《【进阶篇】产品收益预估模型》里详解.

除了留存&流失 之外,还有一个重要指标就是 回归率;

回归率:曾经流失,重新登录游戏的用户占 流失用户的比例;

公式:回归率 = 回归用户 / 流失用户池;

回归用户:曾经流失,重新登录游戏的用户;

流失用户池:过去一段时间内流失的用户数;

备注:精准的回归率 分母除以 历史以来流失的用户总数,但是由于游戏的用户是不断累积的,因此会导致回归率越来越低趋近于0;因此,通常去过去3个月内流失的用户作为流失用户池;

回归率最经常应用的场景就是评估运营活动的效果

最后是活跃相关的数据指标:

文章开篇有提到,除了增加活跃用户的规模之外,还需要提高活跃用户的质量,即游戏参与度,在固定周期内,用户参与游戏的时间越久,我们就越有机会让用户转换为付费用户,因此在游戏中,我们通常会关心以下2个指标:

AT(Daily Avg. Online Time)日均使用时长:活跃用户平均每日在线时长;

EC(Engagement Count)用户登录频率:用户开打游戏客户端记为一次登录,登录频率即统计周期内平均每用户登录游戏的总次数;

衡量用户的游戏参与度,游戏人气的变化趋势等

如何结合游戏内的数据,做分析并帮助指定运营计划和版本功能,以达到提升活跃的目的,这部分会在 进阶篇中列举详细案例具体说明;

小结:活跃相关

AU(Active Users)活跃用户

定义:用户开打游戏客户端记为一次登录;

拓展应用:根据统计周期段又划分为DAU(日活跃用户),WAU(周活跃用户),MAU(月活跃用户);

应用场景:衡量产品的核心用户规模,观察产品在线的周期性变化;

PCU(Peak Concurrent Users)最高同时在线用户人数

定义:统计周期内,同一时点(通常精确至分)的最高在线人数;

备注:PCU<=DAU,通常情况下PCU受游戏内运营活动影响较大;

ACU(Average Concurrent Users)平均同时在线用户人数

定义:统计周期内,每个时点(通常精确到分)的平均在线人数;

公式:DAU * AT / 时间精度(若精确到分钟,则除以 24*60);

AT(Daily Avg. Online Time)日均使用时长:

定义:活跃用户平均每日在线时长;

公式:AT = 日总在线时长 / DAU

EC(Engagement Count)用户登录频率:

定义:用户开打游戏客户端记为一次登录,登录频率即统计周期内平均每用户登录游戏的总次数;

备注:根据统计周期不同,通常每日登录频率统计的是登录次数;周及月的登录频率统计的是登录天次(一天登录多次记为一次)

应用场景:衡量用户的游戏参与度,游戏人气的变化趋势等

小结:留存相关

UsersLeave 用户流失

定义:统计日登录游戏,但在随后N日内未登入游戏的用户占 统计日活跃用户的比例 ;

应用场景:精确定义流失行为,通过观察流失用户的状态、流失前行为来判断游戏产品可能存在的问题;

流失标准:根据N的取值不同,可设置不同流失标准:N=1 流失概率56.71%;N=7 流失概率 95.16%;N=14流失概率98.56%;

DRR(Daily Retention Rate)日留存率

定义:统计当日登录游戏的用户,在之后N日内至少登录一次游戏的用户 占 统计当周活跃用户比例;

WRR(Weekly Retention Rate)周留存率

定义:统计当周登录过游戏,且下一周至少登录一次游戏的用户 占 统计当周活跃用户比例;

MRR(Monthly Retention Rate)月留存率

定义:统计当月登录过游戏,且下一月至少登录一次游戏的用户 占 统计当月活跃用户比例;

ULR(Users Leave Rate)用户流失率

定义:1-留存率;

备注:根据统计周期不同,可以区分为日留存、周留存、月留存;

ACT_N(Active N_Day)用户活跃度

定义:统计周期内,新增用户在随后不同时期的登录情况;

公式:ACT _N = 统计周期内,一批新增用户在其首次登入后第N天还有登录的用户数 / 新增用户数;

备注:活跃度需要长期跟踪,根据需求可以设定30日、60日 或 90日;ACT仅针对统计周期内新增账号进行观察;

应用场景:主要的作用是帮助CP、发行商和渠道商快速的判断产品的质量;

回归率:曾经流失,重新登录游戏的用户 占 流失用户的比例;

公式:回归率 = 回归用户 / 流失用户池;

回归用户:曾经流失,重新登录游戏的用户;

流失用户池:过去一段时间内流失的用户数;

备注:精准的回归率 分母除以历史以来流失的用户总数,但是由于游戏的用户是不断累积的,因此会导致回归率越来越低趋近于0;因此,通常去过去3个月内流失的用户作为流失用户池。


收入相关指标详解

==========在做进一步讲解之前,依旧先对活跃用户进行定义===================

AU(Active Users)活跃用户:统计周期内,登录过游戏的用户数;根据统计周期不同又划分为DAU(日活跃用户),WAU(周活跃用户),MAU(月活跃用户);

备注:入门篇中所定义的“用户”均以“账号”进行衡量;账号:游戏账号库中的唯一标识,在单款游戏中全局唯一;

==========================================================

仍然从应收的公式进行推导 Revenue = AU * PUR * ARPPU;在活跃用户规模固定的前提下,PUR 和 ARPPU 是衡量游戏盈利能力最基础的2个指标;

国内做游戏数据分析的时候 ARPPU 和 ARPU 经常被混在一起,这里为了严谨,单独把这2个指标拿出来对比一下;

ARPU(Average Revenue Per User) 平均每用户收入
定义:统计周期内,活跃用户对游戏产生的平均收入;
公式: ARPU = Revenue / AU

ARPPU (Average Revenue Per Paying User) 平均每付费用户收入
定义:统计周期内,付费用户对游戏产生的平均收入;
公式:ARPPU = Revenue / APA

PUR(Pay User Rate)付费比率
定义:统计周期内,付费账号数占活跃账号数的比例;一般以自然月或自然周为单位进行统计;
公式:PUR = APA / AU;

APA(Active Payment Account)活跃付费账号数
定义:统计周期内,成功付费的账号数(排重统计);
公式:APA = AU * PUR;

拓展应用:

从公式的推导可以看出,实际上 ARPU = ARPPU * PUR;目前国内游戏数据做数据分析时所说的“ARPU”实际上是ARPPU,即平均每付费用户收入;

之所以将 ARPU 再拆解为 PUR 和 ARPPU,主要是因为 ARPU是对产品盈利能力的综合评价,为了更好的我们做决策,将付费指标拆解为 PUR(广度,更多的人付费) 和 ARPPU(深度,付更多的钱) 两个维度;

基于上诉原则,在做充值相关分析的时候,还可以对PUR 和 ARPPU 做进一步拆解,比如新老用户的 PUR 和 ARPPU,对 APA 的付费强度(统计周期内充值金额)进行分段统计,观察APA的结构,如大R占比,贡献率、小额充值的比重等;

在移动游戏数据分析领域,特别是渠道商在判断产品质量的时候,大家还会经常听到一个指标 LTV

LTV(Lift Time Value)生命周期价值
定义:平均一个账号在其生命周期内(第一次登录游戏到最后一次登录游戏),为该游戏创造的收入总计;
公式:LTV_N = 统计周期内,一批新增用户在其首次登入后N天内产生的累计充值 / NU(New Users);
应用场景:手机游戏数据分析中的发行指标,用于衡量渠道导入用户的回本周期,LTV_N>CPA(登录)

从LTV的定义上可以看出,CP可以通过不同渠道导入用户的LTV_N 与 导入成本(CPL)进行比较,用于计算不同媒体投放的回本率(这个在市场推广篇已经提到);另外,渠道商也可以通过这个指标和联运资源的成本对比,迅速判断一款产品是否值得投入联运资源;

由于LTV是基于新增用户进行计算的,因此受大R影响比较严重。
这里写图片描述
因此,在观察产品LTV数据的时候,通常情况下会选取一段时间的数据进行观察;在汇总计算时,如下图所示,计算LTV_N 时只抽取时间跨度足够的样本;

如,统计周期选择 4-10至4-19,LTV_4 仅通过 4-10 至 4-16的数据进行计算,因为 4-17至4-19 三天的新增账号还没有第4天的数据;

另外,由于受每日新增用户的质量影响较大,有可能出现LTV_N+1 小于 LTV_N的情况,因此要观察 LTV_N时,统计周期至少选择 N +14 天以上,保证每个指标都有14天以上的样本进行计算;
这里写图片描述
本文提及的收入指标主要是用于描述产品宏观数据,关于结合游戏内的其他数据做分析(包括IB分析、消费分析、首充分析等)以帮助我们制定相应的运营活动和版本计划,这部分会在 进阶篇 的案例中详细说明;

				<script>
					(function(){
						function setArticleH(btnReadmore,posi){
							var winH = $(window).height();
							var articleBox = $("div.article_content");
							var artH = articleBox.height();
							if(artH > winH*posi){
								articleBox.css({
									'height':winH*posi+'px',
									'overflow':'hidden'
								})
								btnReadmore.click(function(){
									if(typeof window.localStorage === "object" && typeof window.csdn.anonymousUserLimit === "object"){
										if(!window.csdn.anonymousUserLimit.judgment()){
											window.csdn.anonymousUserLimit.Jumplogin();
											return false;
										}else if(!currentUserName){
											window.csdn.anonymousUserLimit.updata();
										}
									}
									
									articleBox.removeAttr("style");
									$(this).parent().remove();
								})
							}else{
								btnReadmore.parent().remove();
							}
						}
						var btnReadmore = $("#btn-readmore");
						if(btnReadmore.length>0){
							if(currentUserName){
								setArticleH(btnReadmore,3);
							}else{
								setArticleH(btnReadmore,1.2);
							}
						}
					})()
				</script>
				</article>

猜你喜欢

转载自blog.csdn.net/weixin_41919236/article/details/84791313