hxp
2024-10-31 402ed2e6a90a785d2fce3eca23cd324f350d54c5
10162 后台优化(增加全服报表)
10个文件已修改
7个文件已添加
2218 ■■■■■ 已修改文件
Account/User.php 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Common/CommFunc.php 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Common/PayOrder.php 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Server/eventdata/CommFunc.py 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Server/eventdata/QueryAccountLoginOut.py 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
db/DBOper.php 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
db/struct.php 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eventreport/eventreport.php 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.php 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
js/common.js 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
serverrep/ImportAccountLoginpay.php 444 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
serverrep/allview.php 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
serverrep/dailyuser.php 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
serverrep/keeplogin.php 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
serverrep/ltv.php 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
serverrep/report.php 785 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
task/minuteLoop.php 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Account/User.php
@@ -19,6 +19,16 @@
    if (!isset($PermissionModules)) {
        $PermissionModules = array(
            array(
                "ModuleID" => "ServerRep",
                "Name" => \Lang\gettext("报表"),
                "PList" => array(
                    Permission::P_REP_AllView => array("Name" => \Lang\gettext("全服总览")),
                    Permission::P_REP_DailyUser => array("Name" => \Lang\gettext("全服每日用户")),
                    Permission::P_REP_KeepLogin => array("Name" => \Lang\gettext("全服留存")),
                    Permission::P_REP_LTV => array("Name" => \Lang\gettext("全服LTV")),
                )
            ),
            array(
                "ModuleID" => "ServerInfo",
                "Name" => \Lang\gettext("服务器"),
                "PList" => array(
@@ -197,6 +207,12 @@
    const GroupAdmin = "admin";
    const P_System = "P_System";
    //报表
    const P_REP_AllView = "P_REP_AllView";
    const P_REP_DailyUser = "P_REP_DailyUser";
    const P_REP_KeepLogin = "P_REP_KeepLogin";
    const P_REP_LTV = "P_REP_LTV";
    //服务器
    const P_Online = "P_Online";
    const P_PlayerLV = "P_PlayerLV";
Common/CommFunc.php
@@ -730,7 +730,7 @@
}
#进行post请求
function DoPost($url, $post, $passSession = false)
function DoPost($url, $post, $passSession = false, $timeout=30)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
@@ -739,7 +739,7 @@
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    if ($passSession) {
        curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID=' . $_COOKIE['PHPSESSID']);
    }
Common/PayOrder.php
@@ -196,9 +196,37 @@
    } else {
        \Logging\LogInfo("setDBOrderData:" . print_r($setDBOrderData, true));
    }
    AddAccountFirstPay($appID, $AccountID, $setDBOrderData);
    return $returnArr;
}
/**添加平台账号首充 */
function AddAccountFirstPay($appID, $AccountID, $payOrderInfo)
{
    $find = array("Channel" => $appID, "AccountID" => $AccountID);
    if (!\DBOper\FindOne("AccountFirstPay", $find, $findData, null, false)) {
        return;
    }
    if (isset($findData)) {
        // 已存在,不再重复添加
        return;
    }
    $insArray = array(
        "Channel" => $appID,
        "AccountID" => $AccountID,
        "OrderID" => $payOrderInfo["OrderID"],
        "OrderIDSDK" => $payOrderInfo["OrderIDSDK"],
        "ServerID" => $payOrderInfo["ServerID"],
        "OrderInfo" => $payOrderInfo["OrderInfo"],
        "OrderAmount" => $payOrderInfo["OrderAmount"], // 中心的这个字段是实际支付金额
        "OriginalAmount" => $payOrderInfo["OriginalAmount"],
        "PayTime" => $payOrderInfo["PayTime"],
        "PayYMD" => substr($payOrderInfo["PayTime"], 0, 10),
        "Extras" => $payOrderInfo["Extras"],
    );
    \DBOper\Insert("AccountFirstPay", $insArray);
}
/**
 * 发送渠道回调充值到游戏服务器
 * @param string $spid 渠道
Server/eventdata/CommFunc.py
@@ -315,8 +315,9 @@
    @return: 是否成功
    '''
    logging.info("loopCenterbakRarDR %s" % str(argvDict))
    allServer = argvDict.get("allServer", 0)
    mixServerIDInfo = argvDict.get("mixServerIDInfo", "")
    if not mixServerIDInfo:
    if not allServer and not mixServerIDInfo:
        queryBackupCenterError("can not found mixServerIDInfo!")
        return
    
@@ -334,7 +335,7 @@
    serverIDList = [OnlyServerID] if OnlyServerID else parseMixServerIDInfo(mixServerIDInfo)
    spfileFormat = "Event_%s-s" % spID
    
    logging.info("serverIDList=%s,spfileFormat=%s" % (serverIDList, spfileFormat))
    logging.info("allServer=%s,serverIDList=%s,spfileFormat=%s" % (allServer, serverIDList, spfileFormat))
    # 备份日期文件夹
    for backupDateFile in os.listdir(backupPath):
        backupDateStr = backupDateFile[len("Event_"):]
@@ -351,7 +352,7 @@
                if not backupServer.startswith(spfileFormat):
                    continue
                serverInfo = backupServer[len(spfileFormat):]
                if not checkIsBackupServerFolder(serverIDList, serverInfo):
                if not allServer and not checkIsBackupServerFolder(serverIDList, serverInfo):
                    continue
                
                logging.info("loop %s" % os.path.join(backupPath, backupDateFile, backupServer))
@@ -404,7 +405,7 @@
                if not backupServer.startswith(spfileFormat):
                    continue
                serverInfo = backupServer[len(spfileFormat):-4]
                if not checkIsBackupServerFolder(serverIDList, serverInfo):
                if not allServer and not checkIsBackupServerFolder(serverIDList, serverInfo):
                    continue
                #logging.info("backupServer:%s" % backupServer)
                #rarfile.namelist() # 列表,rar文件中所有子文件的path(相对于rar文件包而言的)
Server/eventdata/QueryAccountLoginOut.py
New file
@@ -0,0 +1,124 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import CommFunc
import ConfigParser
import mylog
import logging
import json
cfg = ConfigParser.ConfigParser()
cfg.read("../../InterfaceConfig.php")
ServerPath = cfg.get("ServerInfo", "ServerPath")
# 需要处理的流向名及顺序列表
DRNameList = ["LogInOut"]
def queryAccountLoginoutInfo(argvDict):
    logging.info("queryAccountLoginoutInfo %s" % str(argvDict))
    startDate = argvDict.get("startDate", "")
    endDate = argvDict.get("endDate", "")
    # 查询中心备份的
    if CommFunc.isQueryCenterbak(argvDict):
        queryCenterBak(startDate, endDate, argvDict)
        return
    loginoutDateAccIDInfo = {} # {"日期":{"AccID":[loginTime, logoutTime, IP], ...}, ...}
    needQueryCenterbak = CommFunc.loopMainServerDR(cfg, startDate, endDate, argvDict, checkDrFileNeedParseFunc, parseLineFunc,
                                                   loginoutDateAccIDInfo, drNameList=DRNameList)
    logging.info("needQueryCenterbak %s" % needQueryCenterbak)
    if needQueryCenterbak:
        bakDataInfo = CommFunc.queryBackupCenterDR(cfg, argvDict)
        logging.info("queryBackupCenterDR OK")
        if bakDataInfo == None:
            return
        loginoutDateAccIDInfoBak = bakDataInfo[0]
        # 合并数据
        for bakDate, accIDDictBak in loginoutDateAccIDInfoBak.items():
            if bakDate not in loginoutDateAccIDInfo:
                loginoutDateAccIDInfo[bakDate] = accIDDictBak
                continue
            accIDDict = loginoutDateAccIDInfo[bakDate]
            for accID, loginoutListBak in accIDDictBak.items():
                if accID not in accIDDict:
                    accIDDict[accID] = loginoutListBak
                    continue
                # 备份的数据一定是较旧的,如果没有数据再用备份数据替换
                loginoutList = accIDDict[accID]
                if not loginoutList[0]:
                    loginoutList[0] = loginoutListBak[0]
                if not loginoutList[1]:
                    loginoutList[1] = loginoutListBak[1]
                if not loginoutList[2]:
                    loginoutList[2] = loginoutListBak[2]
    logging.info("query all data OK")
    ret = {"OK": 1, "loginoutDateAccIDInfo": loginoutDateAccIDInfo}
    print json.dumps(ret, ensure_ascii=False, default=lambda obj: obj.__dict__)
    return
def queryCenterBak(startDate, endDate, argvDict):
    loginoutDateAccIDInfoBak = {}
    if not CommFunc.loopCenterbakRarDR(cfg, startDate, endDate, argvDict, checkDrFileNeedParseFunc, parseLineFunc,
                                       loginoutDateAccIDInfoBak, drNameList=DRNameList):
        return
    return CommFunc.queryBackupCenterOK([loginoutDateAccIDInfoBak])
def checkDrFileNeedParseFunc(drFileName, *parseArgs, **kv):
    ''' 检查流向是否需要处理
    @param drFileName: 流向文件名  xxx_日期.txt
    @param *parseArgs: 自定义参数
    @return: isNeed, checkNeedParseRetInfo
    '''
    return True, None
def parseLineFunc(drName, dateStr, checkNeedParseRetInfo, line, *parseArgs, **kv):
    ''' 解析流向行内容
    @param drName: 流向名
    @param dateStr: 对应日期字符串
    @param checkNeedParseRetInfo: checkDrFileNeedParseFunc 返回的内容
    @param line: 本行内容
    @param *parseArgs: 自定义参数
    '''
    loginoutDateAccIDInfo = parseArgs[0]
    # 统计登录
    if drName == "LogInOut":
        if "127.0.0.1" in line:
            #logging.info("    脱机登录,不处理")
            return
        drDict = eval(line)
        accID = drDict["AccID"]
        if dateStr not in loginoutDateAccIDInfo:
            loginoutDateAccIDInfo[dateStr] = {}
        accIDDict = loginoutDateAccIDInfo[dateStr]
        if accID not in accIDDict:
            accIDDict[accID] = ["", "", ""]
        loginoutList = accIDDict[accID]
        if drDict["Type"] == "login":
            loginoutList[0] = drDict["LoginTime"]
        else:
            loginoutList[1] = drDict["LogoutTime"]
        loginoutList[2] = drDict["IP"]
    return
def main():
    CommFunc.setdefaultencoding()
    argvDict = CommFunc.parse_args()
    mylog.InitMyLog(argvDict.get("eventType", ""))
    queryAccountLoginoutInfo(argvDict)
    return
if __name__ == "__main__":
    try:
        main()
    except:
        CommFunc.printExceptionError()
db/DBOper.php
@@ -302,7 +302,7 @@
        if (isset($ret) && $ret["ok"] == 1) {
            $retArray = $ret["result"];
        }
        \Logging\LogInfo('Mongo Aggregate: $ret=' . $ret);
        // \Logging\LogInfo('Mongo Aggregate: $ret=' . $ret);
        MongoDBPool::Close($conn);
        return true;
    }
@@ -362,6 +362,24 @@
    return true;
}
function FindOneSort($collectionName, $find, &$retArray, $fields = null, $sortArray = null)
{
    $dbName = GetDBName();
    if (!$dbName) {
        return false;
    }
    if (!MongoDBOper::Find($collectionName, $find, $retFindArray, $fields, $sortArray, 1)) {
        return false;
    }
    if (isset($retFindArray)) {
        foreach ($retFindArray as $value) {
            $retArray = $value;
            break;
        }
    }
    return true;
}
function Find(
    $collectionName,
    $find,
db/struct.php
@@ -20,14 +20,14 @@
    "GameRoles" => array(
        array(
            array("Channel" => 1, "AccountID" => 1),
            array("‘unique" => true),
            array("unique" => true),
        )
    ),
    "GameServerInfo" => array(
        array(
            array("Channel" => 1, "ServerID" => 1),
            array("‘unique" => true),
            array("unique" => true),
        )
    ),
@@ -64,8 +64,81 @@
        array(
            array("Channel" => 1, "State" => 1, "PayTime" => -1),
            array(),
        ),
        array(
            array("PayTime" => 1),
            array(),
        ),
    ),
    "ServerEvent" => array(
        array(
            array("Key" => 1),
            array("unique" => true),
        )
    ),
    "AccountFirstLogin" => array(
        array(
            array("Channel" => 1, "CreateYMD" => 1),
            array(),
        ),
        array(
            array("Channel" => 1, "AccountID" => 1),
            array("unique" => true),
        ),
    ),
    "AccountFirstLoginReport" => array(
        array(
            array("Channel" => 1, "CreateYMD" => 1),
            array(),
        ),
        array(
            array("Channel" => 1, "ReportYMD" => -1),
            array(),
        ),
    ),
    "AccountFirstPay" => array(
        array(
            array("Channel" => 1, "PayYMD" => 1),
            array(),
        ),
        array(
            array("Channel" => 1, "AccountID" => 1),
            array("unique" => true),
        ),
    ),
    "AccountFirstPayReport" => array(
        array(
            array("Channel" => 1, "FirstPayYMD" => 1),
            array(),
        ),
        array(
            array("Channel" => 1, "ReportYMD" => -1),
            array(),
        ),
    ),
    "AccountDayActive" => array(
        array(
            array("Channel" => 1, "ActiveYMD" => 1),
            array(),
        ),
        array(
            array("Channel" => 1, "AccountID" => 1, "ActiveYMD" => 1),
            array("unique" => true),
        ),
    ),
    "DailyReport" => array(
        array(
            array("Channel" => 1, "YMD" => -1),
            array("unique" => true),
        ),
    )
);
foreach ($structInfo as $collectionName => $indexList) {
eventreport/eventreport.php
@@ -153,6 +153,9 @@
            return;
        }
    }
    $ServerID = \CommFunc\GetServerIDBySid($RegionName);
    AddAccountFirstLogin($ServerID, $roleInfo);
    AddAccountDayActive($ServerID, $roleInfo);
    return true;
}
@@ -193,3 +196,60 @@
    }
    return true;
}
/**添加平台账号首登 */
function AddAccountFirstLogin($ServerID, $roleInfo)
{
    global $Channel, $Time;
    $AccountID = $_GET["AccountID"];
    $find = array("Channel" => $Channel, "AccountID" => $AccountID);
    if (!\DBOper\FindOne("AccountFirstLogin", $find, $findData, null, false)) {
        return;
    }
    if (isset($findData)) {
        // 已存在,不再重复添加
        return;
    }
    $CreateRoleTime = $Time;
    if (array_key_exists("CreateRoleTime", $roleInfo)) {
        $CreateRoleTime = $roleInfo["CreateRoleTime"];
    }
    $insArray = array(
        "Channel" => $Channel,
        "AccountID" => $AccountID,
        "CreateYMD" => substr($CreateRoleTime, 0, 10),
        "CreateTime" => $CreateRoleTime,
        "ServerID" => $ServerID,
        "PlayerID" => $roleInfo["PlayerID"],
        "PlayerName" => $roleInfo["PlayerName"],
        "Job" => $roleInfo["Job"],
        "IP" => $roleInfo["IP"],
    );
    \DBOper\Insert("AccountFirstLogin", $insArray);
}
/**添加平台账号日活 */
function AddAccountDayActive($ServerID, $roleInfo)
{
    global $Channel, $Time;
    $AccountID = $_GET["AccountID"];
    $curYMD = substr($Time, 0, 10);
    $find = array("Channel" => $Channel, "AccountID" => $AccountID, "ActiveYMD" => $curYMD);
    $roleInfo["ServerID"] = $ServerID;
    // 直接更新
    $actInfo = array(
        "Channel" => $Channel,
        "AccountID" => $AccountID,
        "ActiveYMD" => $curYMD,
        "ServerID" => $ServerID,
        "IP" => $roleInfo["IP"],
    );
    if (array_key_exists("LoginTime", $roleInfo)) {
        $actInfo["LoginTime"] = $roleInfo["LoginTime"];
    }
    if (array_key_exists("LogoffTime", $roleInfo)) {
        $actInfo["LogoffTime"] = $roleInfo["LogoffTime"];
    }
    \DBOper\Update("AccountDayActive", $find, $actInfo, false, true);
}
index.php
@@ -37,6 +37,13 @@
\Logging\LogInfo("权限表: " . print_r($permissions, true));
$gmtMenu = array(
    \Lang\gettext("报表") => array(
        array(\User\Permission::P_REP_AllView, "serverrep/allview.php", \Lang\gettext("全服总览")),
        array(\User\Permission::P_REP_DailyUser, "serverrep/dailyuser.php", \Lang\gettext("全服每日用户")),
        array(\User\Permission::P_REP_KeepLogin, "serverrep/keeplogin.php", \Lang\gettext("全服留存")),
        array(\User\Permission::P_REP_LTV, "serverrep/ltv.php", \Lang\gettext("全服LTV")),
    ),
    \Lang\gettext("服务器") => array(
        array(\User\Permission::P_Online, "serverinfo/online.php", \Lang\gettext("在线人数")),
        array(\User\Permission::P_PlayerLV, "serverinfo/playerlv.php", \Lang\gettext("等级分布")),
js/common.js
@@ -135,4 +135,88 @@
    isQuerying = true;
    document.getElementById(key).value = gt.gettext("查询中...");
    return true;
}
/**
 * 绘制曲线图
 * @param {*} chartID 图表ID,关联html中的元素ID
 * @param {*} chartText 图表总标题
 * @param {*} xText x轴文本, 如等级
 * @param {*} yText y轴文本, 如人数
 * @param {*} labels x轴刻度文本列表, 如 等级列表
 * @param {*} datasetDataList 数据表数据列表,即每条线的数据,线的数据长度必须与x轴刻度文本列表长度一致
 * @param {*} datasetLabList 数据表标题列表,即每条线的标题,有几条线的数据即有几个标题
 */
 function drawChart_Line(chartID, chartText, xText, yText, labels, datasetDataList, datasetLabList = []) {
    var backgroundColors = [
        'rgba(79, 66, 255, 0.2)',
        'rgba(255, 99, 132, 0.2)',
        'rgba(255, 206, 86, 0.2)',
        'rgba(54, 162, 235, 0.2)',
        'rgba(75, 192, 192, 0.2)',
        'rgba(153, 102, 255, 0.2)',
        'rgba(255, 159, 64, 0.2)'
    ];
    var borderColors = [
        'rgba(79, 66, 255, 1)',
        'rgba(255, 99, 132, 1)',
        'rgba(255, 206, 86, 1)',
        'rgba(54, 162, 235, 1)',
        'rgba(75, 192, 192, 1)',
        'rgba(153, 102, 255, 1)',
        'rgba(255, 159, 64, 1)'
    ];
    var colorIndex = 0;
    var datasets = [];
    for (let i = 0; i < datasetDataList.length; i++) {
        datasets.push({
            label: datasetLabList.length > i ? datasetLabList[i] : "",
            data: datasetDataList[i],
            backgroundColor: backgroundColors[colorIndex % backgroundColors.length],
            borderColor: borderColors[colorIndex % borderColors.length],
            borderWidth: 1,
            lineTension: 0.5,
            pointRadius: 1
        });
        colorIndex += 1;
    }
    var ctx = document.getElementById(chartID);
    ctx.width = 400;
    ctx.height = 100;
    var myLineChart = new Chart(ctx, {
        type: "line",
        data: {
            labels: labels,
            datasets: datasets
        },
        options: {
            plugins: {
                title: {
                    display: true,
                    text: chartText
                }
            },
            scales: {
                x: {
                    title: {
                        display: true,
                        text: xText
                    }
                },
                y: {
                    title: {
                        display: true,
                        text: yText
                    },
                    beginAtZero: true,
                    ticks: {
                        stepSize: 1, // 刻度间隔1
                    }
                }
            }
        }
    });
}
serverrep/ImportAccountLoginpay.php
New file
@@ -0,0 +1,444 @@
<?php
set_time_limit(600); //暂时设置本脚本执行时间x秒,0不限制
ini_set('memory_limit', '256M'); // 将内存限制设置为256MB
include_once '/Common/CommFunc.php';
include_once '/Common/Logging.php';
include_once "/db/DBOper.php";
include_once "/serverrep/report.php";
header("Content-type: text/html; charset=utf-8");
\Logging\CreateLogging("rep.ImportAccountLoginpay.php");
$Channel = $_GET["appID"];
$opt = $_GET["opt"];
if (!$Channel) {
    echo "no appID";
    exit();
}
logweb("开始导入:" . $Channel);
if (!$opt || $opt == "first") {
    logweb("firstfirstfirst:" . $Channel);
    impAccountFirstLogin();
    impAccountFirstPay();
}
if (!$opt || $opt == "day") {
    logweb("daydaydaydayday:" . $Channel);
    impAccountDayActive();
    // impAccountDayActiveTestData();
}
if (!$opt || $opt == "rep") {
    logweb("repreprepreprep:" . $Channel);
    \Report\CheckAndExportDailyReport($Channel);
    \Report\CheckAndExportFirstKeepReport($Channel);
}
logweb("处理完毕!");
exit();
function impAccountFirstLogin()
{
    global $Channel;
    logweb("开始导入首登账号");
    \DBOper\Find("AccountFirstLogin", array("Channel" => $Channel), $findFirstLoginDataList, array("AccountID" => 1));
    $firstLoginAccountIDList = array();
    foreach ($findFirstLoginDataList as $findData) {
        $firstLoginAccountIDList[$findData["AccountID"]] = 1;
    }
    logweb("已存在首登账号数:" . count($firstLoginAccountIDList));
    $limit = 10000;
    $skip = 0;
    $succCountTotal = 0;
    $gameRolesCount = \DBOper\Count("GameRoles", array("Channel" => $Channel));
    logweb("GameRoles 总条数:" . $gameRolesCount);
    while ($gameRolesCount > 0) {
        $ret = \DBOper\Find("GameRoles", array("Channel" => $Channel), $rolesDataList, null, null, $limit, $skip);
        logweb("查询GameRoles:  limit:" . $limit . "    skip:" . $skip . "    ret:" . $ret . " findCount:" . count($rolesDataList));
        $batchInsFirstLogin = array();
        foreach ($rolesDataList as $findData) {
            $AccountID = $findData["AccountID"];
            if (isset($firstLoginAccountIDList[$AccountID])) {
                continue;
            }
            $firstLoginAccountIDList[$AccountID] = 1;
            $firstLoginInfo = null;
            $firstLoginTime = "";
            foreach ($findData as $field => $value) {
                $ServerID = \CommFunc\GetServerIDBySid($field);
                if ($ServerID <= 0) {
                    continue;
                }
                $roleInfo = $value;
                $roleInfo["ServerID"] = $ServerID;
                $CreateRoleTime = $roleInfo["CreateRoleTime"];
                // $PlayerID = $roleInfo["PlayerID"];
                // $PlayerName = $roleInfo["PlayerName"];
                // $Job = $roleInfo["Job"];
                // $IP = $roleInfo["IP"];
                // $LoginTime = $roleInfo["LoginTime"];
                // $LogoffTime = $roleInfo["LogoffTime"];
                // 首次创角
                if ($CreateRoleTime < $firstLoginTime || $firstLoginTime == "") {
                    $firstLoginTime = $CreateRoleTime;
                    $firstLoginInfo = $roleInfo;
                }
            }
            if (isset($firstLoginInfo)) {
                $CreateRoleTime = $firstLoginInfo["CreateRoleTime"];
                $CreateYMD = substr($CreateRoleTime, 0, 10);
                if (!array_key_exists($CreateYMD, $batchInsFirstLogin)) {
                    $batchInsFirstLogin[$CreateYMD] = array();
                }
                $insArray = $batchInsFirstLogin[$CreateYMD];
                array_push($insArray, array(
                    "Channel" => $Channel,
                    "AccountID" => $AccountID,
                    "CreateYMD" => $CreateYMD,
                    "CreateTime" => $CreateRoleTime,
                    "ServerID" => $firstLoginInfo["ServerID"],
                    "PlayerID" => $firstLoginInfo["PlayerID"],
                    "PlayerName" => $firstLoginInfo["PlayerName"],
                    "Job" => $firstLoginInfo["Job"],
                    "IP" => $firstLoginInfo["IP"],
                ));
                $batchInsFirstLogin[$CreateYMD] = $insArray;
            }
        }
        logweb("    firstLoginAccountIDList:" . count($firstLoginAccountIDList));
        $gameRolesCount -= $limit;
        $skip += $limit;
        if (count($batchInsFirstLogin)) {
            ksort($batchInsFirstLogin);
            $succCount = 0;
            foreach ($batchInsFirstLogin as $CreateYMD => $insArray) {
                if (DBOper\BatchInsert("AccountFirstLogin", $insArray)) {
                    // logweb("批量导入首登账号数:  CreateYMD:" . $CreateYMD . " count:" . count($insArray));
                    $succCount += count($insArray);
                } else {
                    logweb("###批量导入首登账号数异常:  CreateYMD:" . $CreateYMD . " count:" . count($insArray));
                }
            }
            $succCountTotal += $succCount;
            logweb("新增导入首登账号数: " . $succCount);
        }
    }
    logweb("本次新增导入首登账号总数: " . $succCountTotal);
    \Logging\LogInfo("---------------------------------------------------------------");
    echo "<hr/>";
}
function impAccountFirstPay()
{
    global $Channel;
    logweb("开始导入首充账号");
    \DBOper\Find("AccountFirstPay", array("Channel" => $Channel), $findFirstPayDataList, array("AccountID" => 1));
    $firstPayAccountIDList = array();
    foreach ($findFirstPayDataList as $findData) {
        $firstPayAccountIDList[$findData["AccountID"]] = 1;
    }
    logweb("已存在首充账号数:" . count($firstPayAccountIDList));
    $payOrderCount = \DBOper\Count("PayOrder", array("Channel" => $Channel));
    logweb("PayOrder 总条数:" . $payOrderCount);
    $limit = 10000;
    $skip = 0;
    $succCountTotal = 0;
    while ($payOrderCount > 0) {
        $ret = \DBOper\Find("PayOrder", array("Channel" => $Channel), $payDataList, null, array("PayTime" => 1), $limit, $skip);
        logweb("查询PayOrder:  limit:" . $limit . "    skip:" . $skip . "    ret:" . $ret . " findCount:" . count($payDataList));
        $batchInsFirstPay = array();
        foreach ($payDataList as $payOrderInfo) {
            $AccountID = $payOrderInfo["AccountID"];
            if (isset($firstPayAccountIDList[$AccountID])) {
                continue;
            }
            $firstPayAccountIDList[$AccountID] = 1;
            $PayYMD = substr($payOrderInfo["PayTime"], 0, 10);
            if (!array_key_exists($PayYMD, $batchInsFirstPay)) {
                $batchInsFirstPay[$PayYMD] = array();
            }
            $insArray = $batchInsFirstPay[$PayYMD];
            array_push($insArray, array(
                "Channel" => $Channel,
                "AccountID" => $AccountID,
                "OrderID" => $payOrderInfo["OrderID"],
                "OrderIDSDK" => $payOrderInfo["OrderIDSDK"],
                "ServerID" => $payOrderInfo["ServerID"],
                "OrderInfo" => $payOrderInfo["OrderInfo"],
                "OrderAmount" => $payOrderInfo["OrderAmount"], // 中心的这个字段是实际支付金额
                "OriginalAmount" => $payOrderInfo["OriginalAmount"],
                "PayTime" => $payOrderInfo["PayTime"],
                "PayYMD" => $PayYMD,
                "Extras" => $payOrderInfo["Extras"],
            ));
            $batchInsFirstPay[$PayYMD] = $insArray;
        }
        logweb("    firstPayAccountIDList:" . count($firstPayAccountIDList));
        $payOrderCount -= $limit;
        $skip += $limit;
        if (count($batchInsFirstPay)) {
            ksort($batchInsFirstPay);
            $succCount = 0;
            foreach ($batchInsFirstPay as $PayTime => $insArray) {
                if (DBOper\BatchInsert("AccountFirstPay", $insArray)) {
                    // logweb("批量导入首充账号数:  PayTime:" . $PayTime . " count:" . count($insArray));
                    $succCount += count($insArray);
                } else {
                    logweb("###批量导入首充账号数异常:  PayTime:" . $PayTime . " count:" . count($insArray));
                }
            }
            $succCountTotal += $succCount;
            logweb("新增导入首充账号数: " . $succCount);
        }
    }
    logweb("本次新增导入首充账号总数: " . $succCountTotal);
    \Logging\LogInfo("---------------------------------------------------------------");
    echo "<hr/>";
}
function impAccountDayActive()
{
    global $Channel;
    $startDate = $_GET["startDate"];
    if (!$startDate) {
        $startDate = "2024-05-01";
    }
    logweb("开始从中心导入备档日活数据! startDate:" . $startDate);
    $allDateAccIDInfo = array(); // 所有记录
    // 查询备份流向
    $pack_data = array();
    $pack_data["spID"] = $Channel;
    $pack_data["eventType"] = "QueryAccountLoginOut";
    $pack_data["queryCenterbak"] = 1; # 设置查询中心的
    $pack_data["allServer"] = 1; # 设置检索所有服务器流向
    $pack_data["startDate"] = $startDate;
    $pack_data["endDate"] = "";
    $centerToolUrl = 'http://' . $_SERVER['HTTP_HOST'] . "/Server/eventdata/toolcenter.php";
    logweb("centerToolUrl:" . $centerToolUrl);
    $retStr = \CommFunc\DoPost($centerToolUrl, $pack_data, false, 300);
    $ret = json_decode($retStr, true);
    if (isset($ret)) {
        $allDateAccIDInfo = $ret[0];
        ksort($allDateAccIDInfo);
        foreach ($allDateAccIDInfo as $dateStr => $accIDInfo) {
            logweb("中心流向备档日活数量:" . " dateStr:" . $dateStr . " count:" . count($accIDInfo));
            // foreach ($accIDInfo as $accID => $loginoutInfo) {
            //     logweb("    accID:" . $accID. json_encode($loginoutInfo));
            // }
        }
    } else {
        logweb("query center backup error: " . $retStr);
        return;
    }
    logweb("开始从子服导入日活数据! startDate:" . $startDate);
    $pack_data = array();
    $pack_data["spID"] = $Channel;
    $pack_data["eventType"] = "QueryAccountLoginOut";
    // $pack_data["queryAllData"] = "on"; // 设置可查询备份数据,子服不查备份流向,防止同时多服访问多次
    $pack_data["startDate"] = $startDate;
    $pack_data["endDate"] = "";
    $sendServers = array();
    $serverPageInfos = \CommFunc\GetGameServerPageInfo($Channel);
    // 多服查询的
    foreach ($serverPageInfos as $mainServerID => $serverInfo) {
        $sendServers[] = array($mainServerID, \CommFunc\GetQueryEventToolUrl($serverInfo['Page']), $pack_data);
    }
    \Logging\LogInfo("待发送的服务器信息: " . count($sendServers) . " " . print_r($sendServers, true));
    $retList = array();
    $retMulti = \CommFunc\DoPostMulti($sendServers);
    if ($retMulti && count($retMulti) == count($sendServers)) {
        for ($i = 0; $i < count($sendServers); $i++) {
            $ret = json_decode($retMulti[$i], true);
            if (!isset($ret)) {
                $retList[$sendServers[$i][0]] = $retMulti[$i];
            } else {
                $retList[$sendServers[$i][0]] = $ret;
            }
        }
    }
    $retCount = count($retList);
    \Logging\LogInfo("返回查询结果条数: " .  $retCount);
    // \Logging\LogInfo("retList: " .  print_r($retList, true));
    // 先合并中心备份 及 各服务器数据
    foreach ($retList as $serverName => $ret) {
        logweb("-----");
        if (is_array($ret) && $ret["OK"] == 1) {
            ksort($ret["loginoutDateAccIDInfo"]);
            foreach ($ret["loginoutDateAccIDInfo"] as $dateStr => $accIDInfo) {
                logweb("服务器返回日活数量 serverName:" . $serverName . " dateStr:" . $dateStr . " count:" . count($accIDInfo));
                if (!isset($allDateAccIDInfo[$dateStr])) {
                    $allDateAccIDInfo[$dateStr] = array();
                }
                $allAccIDInfo = $allDateAccIDInfo[$dateStr];
                foreach ($accIDInfo as $accID => $loginoutInfo) {
                    $allAccIDInfo[$accID] = $loginoutInfo; // 各子服一定是最新的,直接替换备份流向数据
                }
                $allDateAccIDInfo[$dateStr] = $allAccIDInfo;
            }
        } else {
            logweb("###服务器返回日活数据异常! serverName:" . $serverName . " ret:" . $ret);
        }
    }
    ksort($allDateAccIDInfo);
    foreach ($allDateAccIDInfo as $dateStr => $accIDInfo) {
        logweb("最终日活数量:" . " dateStr:" . $dateStr . " count:" . count($accIDInfo));
        $dateInsInfo = array();
        foreach ($accIDInfo as $accID => $loginoutInfo) {
            // logweb("    accID:" . $accID. json_encode($loginoutInfo));
            $accIDParts = explode("@", $accID);
            $AccountID = implode("@", array_slice($accIDParts, 0, count($accIDParts) - 2));
            $ServerID = \CommFunc\GetServerIDBySid($accIDParts[count($accIDParts) - 1]);
            $LoginTime = $loginoutInfo[0];
            $LogoffTime = $loginoutInfo[1];
            $IP = $loginoutInfo[2];
            $ActiveYMD = "";
            if ($LoginTime != "") {
                $ActiveYMD = substr($LoginTime, 0, 10);
            } else {
                $ActiveYMD = substr($LogoffTime, 0, 10);
            }
            array_push($dateInsInfo, array(
                "Channel" => $Channel,
                "AccountID" => $AccountID,
                "ActiveYMD" => $ActiveYMD,
                "LoginTime" => $LoginTime,
                "LogoffTime" => $LogoffTime,
                "ServerID" => $ServerID,
                "IP" => $IP,
            ));
        }
        $actCount = DBOper\Count("AccountDayActive", array("Channel" => $Channel, "ActiveYMD" => $dateStr));
        if ($actCount == count($dateInsInfo)) {
            logweb("日活数据数相同,不重新插入: " . $dateStr . " count:" . $actCount);
            continue;
        }
        // 数据量不同,则删除后重新插入
        if ($actCount > 0) {
            if (!DBOper\Remove("AccountDayActive", array("Channel" => $Channel, "ActiveYMD" => $dateStr), false)) {
                logweb("###日活数据数不同,移除时失败!: " . $dateStr . " delCount:" . $actCount . " insCount:" . count($dateInsInfo));
                continue;
            }
        }
        if (DBOper\BatchInsert("AccountDayActive", $dateInsInfo)) {
            logweb("批量导入日活数:" . $dateStr . " count:" . count($dateInsInfo));
        } else {
            logweb("###批量导入日活数失败:" . $dateStr . " count:" . count($dateInsInfo));
        }
    }
    \Logging\LogInfo("---------------------------------------------------------------");
    echo "<hr/>";
}
// 插入山寨日活数据
function impAccountDayActiveTestData()
{
    global $Channel;
    DBOper\Remove("AccountDayActive", array(), false);
    \DBOper\Find("AccountFirstLogin", array("Channel" => $Channel), $findFirstLoginDataList, array("CreateYMD" => 1, "AccountID" => 1));
    $firstAccountIDInfo = array();
    foreach ($findFirstLoginDataList as $findData) {
        $CreateYMD = $findData["CreateYMD"];
        $AccountID = $findData["AccountID"];
        if (!isset($firstAccountIDInfo[$CreateYMD])) {
            $firstAccountIDInfo[$CreateYMD] = array();
        }
        $accountIDList = $firstAccountIDInfo[$CreateYMD];
        array_push($accountIDList, $AccountID);
        $firstAccountIDInfo[$CreateYMD] = $accountIDList;
    }
    $keepPer = 100;
    $keepPerSet = array(100, 50, 35, 30, 25, 15, 10);
    ksort($firstAccountIDInfo);
    foreach ($firstAccountIDInfo as $CreateYMD => $accountIDList) {
        logweb("CreateYMD:" . $CreateYMD . " count:" . count($accountIDList));
        if ($CreateYMD < "2024-05-23") {
            continue;
        }
        $dateInsInfo = array();
        $accountCount = count($accountIDList);
        $onePerCount = $accountCount * 0.1;
        for ($i = 0; $i < 30; $i++) {
            // $randNum = mt_rand(1, 100);
            if (count($keepPerSet) > $i) {
                $keepPer = $keepPerSet[$i];
            } else {
                $keepPer = mt_rand(-1, 5);
            }
            if ($keepPer <= 0) {
                continue;
            }
            $keepCount = $accountCount;
            if ($keepPer < 100) {
                $keepCount = intval($accountCount * $keepPer / 100.0);
                $keepCount = mt_rand($keepCount - $onePerCount, $keepCount + $onePerCount);
            }
            if ($keepCount <= 0) {
                continue;
            }
            $ActiveYMD = date("Y-m-d", strtotime("+" . $i . " day", strtotime($CreateYMD)));
            logweb("CreateYMD:" . $CreateYMD . " i:" . $i . " ActiveYMD:" . $ActiveYMD . " keepPer:" . $keepPer . " keepCount:" . $keepCount);
            for ($k = 0; $k < $keepCount; $k++) {
                $AccountID = $accountIDList[$k];
                array_push($dateInsInfo, array(
                    "Channel" => $Channel,
                    "AccountID" => $AccountID,
                    "ActiveYMD" => $ActiveYMD,
                    "LoginTime" => $ActiveYMD . " 00:00:00",
                    "LogoffTime" => $ActiveYMD . " 23:59:59",
                    "ServerID" => 999,
                    "IP" => "127.0.0.1",
                ));
            }
        }
        if (DBOper\BatchInsert("AccountDayActive", $dateInsInfo)) {
            logweb("批量山寨日活数, CreateYMD:" . $CreateYMD . " count:" . count($dateInsInfo));
        } else {
            logweb("###批量山寨日活数失败, CreateYMD:" . $CreateYMD . " count:" . count($dateInsInfo));
        }
    }
}
function logweb($msg, $showWeb = True)
{
    \Logging\LogInfo($msg);
    if ($showWeb) {
        echo date('Y-m-d H:i:s'), $msg, "<br/>";
    }
}
serverrep/allview.php
New file
@@ -0,0 +1,136 @@
<?php
include_once "/Common/Logging.php";
include_once "/Account/User.php";
include_once "/language/lang.php";
include_once "/serverrep/report.php";
\Logging\CreateLogging("rep.allview.php");
$Permission = \User\Permission::P_REP_DailyUser;
$alertMsg = "";
$channel = $_SESSION['spid'];
$UserAccount = $_SESSION['UserAccount'];
$user = new \User\User($UserAccount);
if (!$user->HavePermission($Permission)) {
    exit;
}
// 固定显示今日实时数据 + 昨日数据 + 额外指定查询日期范围
$todayDate = date("Y-m-d");
$yesterdayDate = date("Y-m-d", strtotime("-1 days"));
$reportDict = \Report\GetDailyReport($channel, $yesterdayDate, $todayDate, true);
\Logging\LogInfo($yesterdayDate . " ~ " . $todayDate . " reportDict: " . print_r($reportDict, true));
$statDataArray = array(
    $todayDate => null,
    $yesterdayDate => null,
);
foreach ($statDataArray as $key => $value) {
    if (!isset($reportDict[$key])) {
        continue;
    }
    $dataInfo = $reportDict[$key];
    $statDataArray[$key] = array(
        "AU" => $dataInfo["DAU"],
        "NU" => $dataInfo["DNU"],
        "PU" => $dataInfo["DPU"],
        "NPU" => $dataInfo["DNPU"],
        "RPU" => $dataInfo["DRPU"],
        "RNPU" => $dataInfo["DRNPU"],
    );
}
$startDate = $todayDate;
$endDate = $todayDate;
if (array_key_exists("startDate", $_POST) || array_key_exists("endDate", $_POST)) {
    $startDate = array_key_exists("startDate", $_POST) ? $_POST["startDate"] : $todayDate;
    $endDate = array_key_exists("endDate", $_POST) ? $_POST["endDate"] : $todayDate;
    $statDataArray[$startDate . " ~ " . $endDate] = array(
        "AU" => \Report\QueryActiveUserCount($channel, $startDate, $endDate),
        "NU" => \Report\QueryNewUserCount($channel, $startDate, $endDate),
        "PU" => \Report\QueryPayUserCount($channel, $startDate, $endDate),
        "NPU" => \Report\QueryNewPayUserCount($channel, $startDate, $endDate),
        "RPU" => \Report\QueryPayTotal($channel, $startDate, $endDate),
        "RNPU" => "-", // 周期查询的该值暂不统计
    );
}
// 计算其他值
foreach ($statDataArray as $key => $dataInfo) {
    $dataInfo["ARPU"] = $dataInfo["AU"] > 0 ? round($dataInfo["RPU"] / $dataInfo["AU"], 2) : 0;
    $dataInfo["ARPPU"] = $dataInfo["PU"] > 0 ? round($dataInfo["RPU"] / $dataInfo["PU"], 2) : 0;
    $statDataArray[$key] = $dataInfo;
}
\Logging\LogInfo("statDataArray: " . print_r($statDataArray, true));
$lineArray = array(
    "活跃用户数量" => array("AU", "统计周期内,登录过游戏的用户数"),
    "新增用户数量" => array("NU", "统计周期内,新增的首登用户数"),
    "充值用户数量" => array("PU", "统计周期内,有充值的用户数"),
    "新增充值用户" => array("NPU", "统计周期内,新增的首次充值用户数"),
    "累计充值总额" => array("RPU", "统计周期内,累计充值总额"),
    "新增充值用户充值" => array("RNPU", "统计周期内,新增充值用户累计充值总额"),
    "ARPU" => array("ARPU", "统计周期内,每用户平均收入,累计充值总额/活跃用户数量 计算得出。"),
    "ARPPU" => array("ARPPU", "统计周期内,每充值用户平均收入,累计充值总额/充值用户数量 计算得出。"),
);
?>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title><?php echo \Lang\gettext("全服总览"); ?></title>
    <link rel="stylesheet" type="text/css" href="/css/table.css">
    <link rel="gettext" type="application/x-po" href="../language/<?php echo \Lang\getLang(); ?>/LC_MESSAGES/<?php echo \Lang\getjspodomain(); ?>.po" />
</head>
<body>
    <center>
        <p><b><?php echo \Lang\gettext("全服总览"); ?></b></P>
    </center>
    <form method="post">
        <?php echo \Lang\gettext("查询时间"); ?>:
        <input type="text" name="startDate" id="startDate" onclick="new Calendar().show(this);" readonly value="<?php echo $startDate; ?>" size="8" />
        ~
        <input type="text" name="endDate" id="endDate" onclick="new Calendar().show(this);" readonly value="<?php echo $endDate; ?>" size="8" />
        <input type="submit" value="<?php echo \Lang\gettext("查询"); ?>" />
        <hr />
        <table width="50%">
            <?php
            echo "<thead><tr>";
            echo "<th align='center' width='200'></th>";
            foreach ($statDataArray as $key => $dataInfo) {
                $title = $key;
                if ($title == $todayDate) {
                    $title = "今天(" . $todayDate . ")";
                } else if ($title == $yesterdayDate) {
                    $title = "昨天(" . $yesterdayDate . ")";
                }
                echo "<th align='center' width='300'>" .  $title . "</th>";
            }
            echo "</tr></thead>";
            foreach ($lineArray as $key => $value) {
                $dataKey = $value[0];
                $spanTitle = $value[1];
                echo "<tr class='trc'>";
                echo "<td align='center'><span title='" . $spanTitle . "'>" . $key . "</span></td>";
                foreach ($statDataArray as $key => $dataInfo) {
                    echo "<td align='center'>" . $dataInfo[$dataKey] . "</td>";
                }
                echo "</tr>";
            }
            ?>
        </table>
        <hr />
    </form>
</body>
<script type='text/javascript' src='/language/gettext.js'></script>
<script type='text/javascript' src="/js/calendar.js"></script>
<script type='text/javascript' src="/js/common.js"></script>
<script type="text/javascript">
</script>
</html>
serverrep/dailyuser.php
New file
@@ -0,0 +1,97 @@
<?php
include_once "/Common/Logging.php";
include_once "/Account/User.php";
include_once "/language/lang.php";
include_once "/serverrep/report.php";
\Logging\CreateLogging("rep.dailyuser.php");
$Permission = \User\Permission::P_REP_DailyUser;
$alertMsg = "";
$channel = $_SESSION['spid'];
$UserAccount = $_SESSION['UserAccount'];
$user = new \User\User($UserAccount);
if (!$user->HavePermission($Permission)) {
    exit;
}
$startDate = array_key_exists("startDate", $_POST) ? $_POST["startDate"] : date("Y-m-d", strtotime("-30 days"));
$endDate = array_key_exists("endDate", $_POST) ? $_POST["endDate"] : date("Y-m-d");
$reportDict = \Report\GetDailyReport($channel, $startDate, $endDate, true);
$showTime = strtotime($startDate);
$endTime = strtotime($endDate);
$showLabelInfo = array(
    "活跃用户DAU" => "DAU",
    "新增用户DNU" => "DNU",
    "充值用户DPU" => "DPU",
    "新增充值用户DNPU" => "DNPU",
);
$xLabels = array(); // x轴为日期
$dataArray = array();
$doCount = 0;
while ($showTime <= $endTime && $doCount < 1440) {
    $doCount += 1;
    $dateStr = date("Y-m-d", $showTime);
    array_push($xLabels, $dateStr);
    foreach ($showLabelInfo as $key => $value) {
        if (!isset($dataArray[$key])) {
            $dataArray[$key] = array();
        }
        $dataList = $dataArray[$key];
        array_push($dataList, $reportDict[$dateStr] ? $reportDict[$dateStr][$value] : 0);
        $dataArray[$key] = $dataList;
    }
    $showTime = strtotime("+1 day", $showTime);
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title><?php echo \Lang\gettext("全服每日用户"); ?></title>
    <link rel="gettext" type="application/x-po" href="../language/<?php echo \Lang\getLang(); ?>/LC_MESSAGES/<?php echo \Lang\getjspodomain(); ?>.po" />
</head>
<body>
    <center>
        <p><b><?php echo \Lang\gettext("全服每日用户"); ?></b></P>
    </center>
    <form method="post">
        <?php echo \Lang\gettext("查询时间"); ?>:
        <input type="text" name="startDate" id="startDate" onclick="new Calendar().show(this);" readonly value="<?php echo $startDate; ?>" size="8" />
        ~
        <input type="text" name="endDate" id="endDate" onclick="new Calendar().show(this);" readonly value="<?php echo $endDate; ?>" size="8" />
        <input type="submit" value="<?php echo \Lang\gettext("查询"); ?>" />
        <hr />
        <div id="MyChart"></div>
        <hr />
    </form>
</body>
<script type='text/javascript' src='/language/gettext.js'></script>
<script type='text/javascript' src="/js/calendar.js"></script>
<script type='text/javascript' src="/js/common.js"></script>
<script type='text/javascript' src="/js/chart.min.js"></script>
<script type="text/javascript">
    window.onload = function() {
        let chartID = "myChart0";
        let insHtml = "";
        // insHtml += "<hr />";
        insHtml += "<canvas id=\"" + chartID + "\" ></canvas>"; // 插入画布
        document.getElementById("MyChart").insertAdjacentHTML("beforeEnd", insHtml);
        var xLabels = JSON.parse('<?php echo json_encode($xLabels); ?>');
        var dataArray = JSON.parse('<?php echo json_encode($dataArray); ?>');
        const chartText = "每日用户数";
        const xText = "日期";
        const yText = "人数";
        drawChart_Line(chartID, chartText, xText, yText, xLabels, Object.values(dataArray), Object.keys(dataArray));
    }
</script>
</html>
serverrep/keeplogin.php
New file
@@ -0,0 +1,164 @@
<?php
include_once "/Common/Logging.php";
include_once "/Account/User.php";
include_once "/language/lang.php";
include_once "/serverrep/report.php";
\Logging\CreateLogging("rep.keeplogin.php");
$Permission = \User\Permission::P_REP_KeepLogin;
$alertMsg = "";
$channel = $_SESSION['spid'];
$UserAccount = $_SESSION['UserAccount'];
$user = new \User\User($UserAccount);
if (!$user->HavePermission($Permission)) {
    exit;
}
$startDate = array_key_exists("startDate", $_POST) ? $_POST["startDate"] : date("Y-m-d", strtotime("-7 days"));
$endDate = array_key_exists("endDate", $_POST) ? $_POST["endDate"] : date("Y-m-d");
$dayList = json_decode($_POST["dayList"], true);
if (!isset($dayList)) {
    $dayList = array(2, 3, 4, 5, 6, 7, 8, 10, 15);
}
$maxDay = max($dayList);
if (!in_array(1, $dayList)) {
    array_push($dayList, 1);
}
sort($dayList);
\Logging\LogInfo("dayList: " . print_r($dayList, true) . " maxDay:" . $maxDay);
\Report\GetAccountFirstLoginPayReport($channel, $startDate, $endDate, $fistLoginReportArray, $fistPayReportArray);
?>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title><?php echo \Lang\gettext("全服留存"); ?></title>
    <link rel="stylesheet" type="text/css" href="/css/table.css">
    <link rel="gettext" type="application/x-po" href="../language/<?php echo \Lang\getLang(); ?>/LC_MESSAGES/<?php echo \Lang\getjspodomain(); ?>.po" />
</head>
<body>
    <center>
        <p><b><?php echo \Lang\gettext("全服留存"); ?></b></P>
    </center>
    <form method="post">
        <?php echo \Lang\gettext("首登或首充时间"); ?>:
        <input type="text" name="startDate" id="startDate" onclick="new Calendar().show(this);" readonly value="<?php echo $startDate; ?>" size="8" />
        ~
        <input type="text" name="endDate" id="endDate" onclick="new Calendar().show(this);" readonly value="<?php echo $endDate; ?>" size="8" />
        <input type="submit" value="<?php echo \Lang\gettext("查询"); ?>" />
        <p />
        指定显示留存天: <input type="text" name="dayList" id="dayList" value="<?php echo json_encode($dayList) ?>" size="50" />
        <hr />
        <table width="100%">
            <?php
            echo "<caption>【创角留存】</caption>";
            echo "<thead><tr>";
            echo "<th align='center' width='70'>创角日期</th>";
            foreach ($dayList as $day) {
                if ($day == 1) {
                    $title = "首登人数";
                } else if ($day == 1) {
                    $title = "次日留存";
                } else {
                    $title = "第" . $day . "日留存";
                }
                echo "<th align='center' width='70'>" .  $title . "</th>";
            }
            echo "</tr></thead>";
            foreach ($fistLoginReportArray as $firstYMD => $statYMDInfo) {
                echo "<tr class='trc'>";
                echo "<td align='center' width='70'>" . $firstYMD . "</td>";
                $firstYMDTime = strtotime($firstYMD);
                $firstCount = 0;
                foreach ($dayList as $day) {
                    $statYMD = date("Y-m-d", strtotime("+" . ($day - 1) . " days", $firstYMDTime));
                    if ($day == 1) {
                        $statInfo = $statYMDInfo[$firstYMD] ? $statYMDInfo[$firstYMD] : array();
                        $firstCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
                        $text = $firstCount;
                    } else {
                        $statInfo = $statYMDInfo[$statYMD] ? $statYMDInfo[$statYMD] : array();
                        $statCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
                        if ($statCount == 0 || $firstCount == 0) {
                            $text = "";
                        } else {
                            $keepPer = round($statCount / $firstCount * 100, 2);
                            // $text = $keepPer . "%<br/>(" . $statCount . ")<br/>";
                            $text = $keepPer . "%";
                        }
                    }
                    echo "<td align='center' width='90'>" .  $text . "</td>";
                }
                echo "</tr>";
            }
            if (count($fistLoginReportArray) == 0) {
                echo "<tr class='trc'><td>无数据</td></tr>";
            }
            ?>
        </table>
        <hr />
        <table width="100%">
            <?php
            echo "<caption>【首充留存】</caption>";
            echo "<thead><tr>";
            echo "<th align='center' width='70'>首充日期</th>";
            foreach ($dayList as $day) {
                if ($day == 1) {
                    $title = "首充人数";
                } else if ($day == 1) {
                    $title = "次日留存";
                } else {
                    $title = "第" . $day . "日留存";
                }
                echo "<th align='center' width='70'>" .  $title . "</th>";
            }
            echo "</tr></thead>";
            foreach ($fistPayReportArray as $firstYMD => $statYMDInfo) {
                echo "<tr class='trc'>";
                echo "<td align='center' width='70'>" . $firstYMD . "</td>";
                $firstYMDTime = strtotime($firstYMD);
                $firstCount = 0;
                foreach ($dayList as $day) {
                    $statYMD = date("Y-m-d", strtotime("+" . $day . " days", $firstYMDTime));
                    if ($day == 1) {
                        $statInfo = $statYMDInfo[$firstYMD] ? $statYMDInfo[$firstYMD] : array();
                        $firstCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
                        $text = $firstCount;
                    } else {
                        $statInfo = $statYMDInfo[$statYMD] ? $statYMDInfo[$statYMD] : array();
                        $statCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
                        if ($statCount == 0 || $firstCount == 0) {
                            $text = "";
                        } else {
                            $keepPer = round($statCount / $firstCount * 100, 2);
                            // $text = $keepPer . "%<br/>(" . $statCount . ")<br/>";
                            $text = $keepPer . "%";
                        }
                    }
                    echo "<td align='center' width='90'>" .  $text . "</td>";
                }
                echo "</tr>";
            }
            if (count($fistPayReportArray) == 0) {
                echo "<tr class='trc'><td>无数据</td></tr>";
            }
            ?>
        </table>
        <hr />
    </form>
</body>
<script type='text/javascript' src='/language/gettext.js'></script>
<script type='text/javascript' src="/js/calendar.js"></script>
<script type='text/javascript' src="/js/common.js"></script>
<script type="text/javascript">
</script>
</html>
serverrep/ltv.php
New file
@@ -0,0 +1,161 @@
<?php
include_once "/Common/Logging.php";
include_once "/Account/User.php";
include_once "/language/lang.php";
include_once "/serverrep/report.php";
\Logging\CreateLogging("rep.ltv.php");
$Permission = \User\Permission::P_REP_LTV;
$alertMsg = "";
$channel = $_SESSION['spid'];
$UserAccount = $_SESSION['UserAccount'];
$user = new \User\User($UserAccount);
if (!$user->HavePermission($Permission)) {
    exit;
}
$startDate = array_key_exists("startDate", $_POST) ? $_POST["startDate"] : date("Y-m-d", strtotime("-7 days"));
$endDate = array_key_exists("endDate", $_POST) ? $_POST["endDate"] : date("Y-m-d");
$dayList = json_decode($_POST["dayList"], true);
$dayTotalList = json_decode($_POST["dayTotalList"], true);
if (!isset($dayList)) {
    $dayList = array(1, 2, 3, 4, 5, 6, 7);
}
if (!isset($dayTotalList)) {
    $dayTotalList = array(7, 14, 15, 21, 30);
}
if (!in_array(1, $dayList)) {
    array_push($dayList, 1);
}
$maxDay = max(max($dayList), max($dayTotalList));
sort($dayList);
sort($dayTotalList);
\Logging\LogInfo("dayList: " . print_r($dayList, true));
\Logging\LogInfo("dayTotalList: " . print_r($dayTotalList, true));
\Logging\LogInfo("maxDay:" . $maxDay);
\Report\GetAccountFirstLoginPayReport($channel, $startDate, $endDate, $fistLoginReportArray, $fistPayReportArray);
// \Logging\LogInfo("dayTotalList: " . print_r($fistLoginReportArray, true));
?>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title><?php echo \Lang\gettext("全服LTV"); ?></title>
    <link rel="stylesheet" type="text/css" href="/css/table.css">
    <link rel="gettext" type="application/x-po" href="../language/<?php echo \Lang\getLang(); ?>/LC_MESSAGES/<?php echo \Lang\getjspodomain(); ?>.po" />
</head>
<body>
    <center>
        <p><b><?php echo \Lang\gettext("全服LTV"); ?></b></P>
    </center>
    <form method="post">
        <?php echo \Lang\gettext("首登时间"); ?>:
        <input type="text" name="startDate" id="startDate" onclick="new Calendar().show(this);" readonly value="<?php echo $startDate; ?>" size="8" />
        ~
        <input type="text" name="endDate" id="endDate" onclick="new Calendar().show(this);" readonly value="<?php echo $endDate; ?>" size="8" />
        <input type="submit" value="<?php echo \Lang\gettext("查询"); ?>" />
        <p />
        LTV天当日: <input type="text" name="dayList" id="dayList" value="<?php echo json_encode($dayList) ?>" size="50" />
        <p />
        LTV天汇总: <input type="text" name="dayTotalList" id="dayTotalList" value="<?php echo json_encode($dayTotalList) ?>" size="50" />
        <hr />
        <table width="100%">
            <?php
            echo "<caption>【LTV】</caption>";
            echo "<thead><tr>";
            echo "<th align='center' width='70'>首登日期</th>";
            echo "<th align='center' width='70'>首登人数</th>";
            foreach ($dayList as $day) {
                $title = "第" . $day . "日LTV";
                echo "<th align='center' width='70'>" .  $title . "</th>";
            }
            foreach ($dayTotalList as $day) {
                $title = $day . "日总LTV";
                echo "<th align='center' width='70'>" .  $title . "</th>";
            }
            echo "<th align='center' width='70'>总LTV(天)</th>";
            echo "<th align='center' width='70'>总充值</th>";
            echo "</tr></thead>";
            $curDate = new DateTime();
            foreach ($fistLoginReportArray as $firstYMD => $statYMDInfo) {
                echo "<tr class='trc'>";
                echo "<td align='center' width='70'>" . $firstYMD . "</td>";
                $firstYMDTime = strtotime($firstYMD);
                $firstCount = 0;
                // 当日ltv
                foreach ($dayList as $day) {
                    $statYMD = date("Y-m-d", strtotime("+" . ($day - 1) . " days", $firstYMDTime));
                    $statInfo = $statYMDInfo[$statYMD] ? $statYMDInfo[$statYMD] : array();
                    if ($day == 1) {
                        $firstCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
                        echo "<td align='center' width='70'>" . $firstCount . "</td>";
                    }
                    $payTotal = $statInfo["payTotal"] ? $statInfo["payTotal"] : 0;
                    if ($payTotal == 0 || $firstCount == 0) {
                        $text = "";
                    } else {
                        $ltv = $payTotal / $firstCount;
                        $ltv =  $ltv > 0.001 ? round($ltv, 3) : round($ltv, 5);
                        // $text = $ltv . "(" . $payTotal . ")";
                        $text = $ltv;
                    }
                    echo "<td align='center' width='90'>" .  $text . "</td>";
                }
                // 统计累计天ltv
                $paySumArray = array();
                $interval = $curDate->diff(new DateTime($firstYMD));
                $ltvDays = $interval->days + 1; // ltv总天数 - 创角 ~ 今日
                $paySum = 0; // 总充值
                for ($i = 0; $i < $ltvDays; $i++) {
                    $statYMD = date("Y-m-d", strtotime("+" . $i . " days", $firstYMDTime));
                    $statInfo = $statYMDInfo[$statYMD] ? $statYMDInfo[$statYMD] : array();
                    $payTotal = $statInfo["payTotal"] ? $statInfo["payTotal"] : 0;
                    $paySum += $payTotal;
                    $paySumArray[$i + 1] = $paySum;
                }
                // \Logging\LogInfo("firstYMD:" . $firstYMD . " ltvDays:" . $ltvDays);
                // \Logging\LogInfo("paySumArray: " . print_r($paySumArray, true));
                // 汇总ltv
                foreach ($dayTotalList as $days) {
                    $daysPaySum = $paySumArray[$days] ? $paySumArray[$days] : 0;
                    if ($daysPaySum == 0 || $firstCount == 0) {
                        $text = "";
                    } else {
                        $ltv = round($daysPaySum / $firstCount, 3);
                        $text = $ltv;
                    }
                    echo "<td align='center' width='90'>" .  $text . "</td>";
                }
                // 总LTV(天)
                $ltvTotal = $firstCount == 0 ? 0 : round($paySum / $firstCount, 3);
                echo "<td align='center' width='90'>" .  $ltvTotal . "(" . $ltvDays . ")</td>";
                // 总充值
                echo "<td align='center' width='90'>" . $paySum . "</td>";
                echo "</tr>";
            }
            if (count($fistLoginReportArray) == 0) {
                echo "<tr class='trc'><td>无数据</td></tr>";
            }
            ?>
        </table>
        <hr />
    </form>
</body>
<script type='text/javascript' src='/language/gettext.js'></script>
<script type='text/javascript' src="/js/calendar.js"></script>
<script type='text/javascript' src="/js/common.js"></script>
<script type="text/javascript">
</script>
</html>
serverrep/report.php
New file
@@ -0,0 +1,785 @@
<?php
namespace Report;
include_once '/Common/CommFunc.php';
include_once '/Common/Logging.php';
include_once "/db/DBOper.php";
function OnExportRep()
{
    $curDate = getdate();
    // $dateStr = $curDate['year'] . "-" . $curDate['mon'] . "-" . $curDate['mday'];
    $curHour = $curDate['hours'];
    // 每天x点固定检查导出报表
    if ($curHour != 1) {
        return;
    }
    $AllChannel = \CommFunc\GetAllChannel();
    for ($i = 0; $i < count($AllChannel); $i++) {
        $Channel = $AllChannel[$i];
        CheckAndExportDailyReport($Channel);
        CheckAndExportFirstKeepReport($Channel);
    }
}
/**检查并导出每日报表 */
function CheckAndExportDailyReport($Channel)
{
    if (!\DBOper\FindOneSort("DailyReport", array("Channel" => $Channel), $retInfo, array("YMD" => 1), array("YMD" => -1))) {
        return;
    }
    $StartYMDTime = 0; // 需要导出的起始日期时间戳
    if (isset($retInfo)) {
        // \Logging\LogInfo("DailyReport retInfo:" . print_r($retInfo, true));
        $lastActiveYMD = $retInfo["YMD"]; // 最后一次导出的报表日期
        $StartYMDTime = strtotime("+1 day", strtotime($lastActiveYMD)); // 从+1天开始导出
    } else {
        // 取出最早的首登日期
        if (!\DBOper\FindOneSort("AccountFirstLogin", array("Channel" => $Channel), $retInfo, array("CreateYMD" => 1), array("CreateYMD" => 1))) {
            return;
        }
        if (!isset($retInfo)) {
            // \Logging\LogInfo("AccountFirstLogin 还没有数据,不用导出! Channel:" . $Channel);
            return;
        }
        // \Logging\LogInfo("AccountFirstLogin retInfo:" . print_r($retInfo, true));
        $StartYMDTime = strtotime($retInfo["CreateYMD"]);
    }
    if (!$StartYMDTime) {
        return;
    }
    $EndYMDTime = strtotime("-1 day", strtotime(date("Y-m-d"))); // 只导出到昨天
    $diffSeconds = $EndYMDTime - $StartYMDTime;
    $diffDays = floor($diffSeconds / (3600 * 24));
    if ($diffDays <= 0) {
        \Logging\LogInfo("【 " . $Channel . " 】不需要导出每日报表! StartYMDTime:" . date("Y-m-d", $StartYMDTime));
        return;
    }
    \Logging\LogInfo("===== 开始导出每日报表 【" . $Channel . "】 =====");
    \Logging\LogInfo("StartYMDTime:" . $StartYMDTime . " " . date("Y-m-d", $StartYMDTime));
    $batchInsDayActive = array();
    while ($StartYMDTime <= $EndYMDTime && $diffDays >= 0) {
        $diffDays -= 1;
        $exportActiveYMD = date("Y-m-d", $StartYMDTime);
        $repArray = StatDailyReport($Channel, $exportActiveYMD);
        array_push($batchInsDayActive, $repArray);
        $StartYMDTime = strtotime("+1 day", $StartYMDTime);
    }
    if (count($batchInsDayActive) > 0) {
        \DBOper\BatchInsert("DailyReport", $batchInsDayActive);
    }
    \Logging\LogInfo("==============================================");
}
/**
 * 统计日报表
 * @param string $Channel
 * @param string $YMD 要统计的yyyy-MM-dd
 */
function StatDailyReport($Channel, $YMD)
{
    \Logging\LogInfo("=== 统计: " . $YMD);
    $DAU = \DBOper\Count("AccountDayActive", array("Channel" => $Channel, "ActiveYMD" => $YMD));
    \Logging\LogInfo(" 日活跃用户数 DAU:" . $DAU);
    $DNU = \DBOper\Count("AccountFirstLogin", array("Channel" => $Channel, "CreateYMD" => $YMD));
    \Logging\LogInfo(" 日新增用户数 DNU:" . $DNU);
    $DNPU = \DBOper\Count("AccountFirstPay", array("Channel" => $Channel, "PayYMD" => $YMD));
    \Logging\LogInfo(" 日新增充值用户数 DNPU:" . $DNPU);
    $ret = \DBOper\Aggregate("AccountFirstPay", array(
        array(
            '$match' => array("Channel" => $Channel, "PayYMD" => $YMD),
        ),
        array(
            '$group' => array(
                '_id' => null,
                'total' => array('$sum' => '$OrderAmount'),
            ),
        )
    ), $retInfo);
    $DRNPU = 0;
    if ($ret && isset($retInfo) && count($retInfo) > 0) {
        $DRNPU = $retInfo[0]["total"];
    }
    $DRNPU = round($DRNPU, 2);
    \Logging\LogInfo(" 日新增充值用户充值总额 DRNPU:" . $DRNPU);
    $ret = \DBOper\Aggregate("PayOrder", array(
        array(
            '$match' => array(
                "Channel" => $Channel, "State" => 1,
                "PayTime" => array('$gte' => $YMD . " 00:00:00", '$lte' => $YMD . " 23:59:59")
            ),
        ),
        array(
            '$group' => array(
                '_id' => array('AccountID' => '$AccountID'),
                'total' => array('$sum' => '$OrderAmount'),
            ),
        )
    ), $retInfo);
    $DPU = 0;
    $DRPU = 0;
    if ($ret && isset($retInfo)) {
        foreach ($retInfo as $info) {
            $DPU += 1;
            $DRPU += $info["total"];
        }
    }
    $DRPU = round($DRPU, 2);
    \Logging\LogInfo(" 日充值用户数 DPU:" . $DPU);
    \Logging\LogInfo(" 日充值用户数充值总额 DRPU:" . $DRPU);
    return array(
        "Channel" => $Channel, "YMD" => $YMD,
        "DAU" => $DAU,
        "DNU" => $DNU,
        "DNPU" => $DNPU,
        "DRNPU" => $DRNPU,
        "DPU" => $DPU,
        "DRPU" => $DRPU,
    );
}
/**检查并导出首登/首充留存每日报表 */
function CheckAndExportFirstKeepReport($Channel)
{
    if (!\DBOper\FindOne("ServerEvent", array("Key" => "AccountFirstKeepReportYMD"), $ret, null, false)) {
        return;
    }
    $reportYMD = $ret["Value"] ? $ret["Value"] : ""; // 最后一次成功导出报表日期
    $StartYMDTime = 0; // 需要导出的起始日期时间戳
    if ($reportYMD) {
        $StartYMDTime = strtotime("+1 day", strtotime($reportYMD)); // 从+1天开始导出
    } else {
        // 取出最早的首登日期
        if (!\DBOper\FindOneSort("AccountFirstLogin", array("Channel" => $Channel), $retInfo, array("CreateYMD" => 1), array("CreateYMD" => 1))) {
            return;
        }
        if (!isset($retInfo)) {
            return;
        }
        $StartYMDTime = strtotime($retInfo["CreateYMD"]);
    }
    if (!$StartYMDTime) {
        return;
    }
    $StartYMD = date("Y-m-d", $StartYMDTime);
    $EndYMDTime = strtotime("-1 day", strtotime(date("Y-m-d"))); // 只导出到昨天
    $diffSeconds = $EndYMDTime - $StartYMDTime;
    $diffDays = floor($diffSeconds / (3600 * 24));
    if ($diffDays <= 0) {
        \Logging\LogInfo("【 " . $Channel . " 】不需要导出首登每日报表! StartYMD:" . $StartYMD);
        return;
    }
    $EndYMD = date("Y-m-d", $EndYMDTime);
    \Logging\LogInfo("===== 开始导出首登每日报表 【" . $Channel . "】 =====");
    \Logging\LogInfo("StartYMD:" . $StartYMD . " EndYMD:" . $EndYMD);
    $fistLoginReportArray = array();
    $fistPayReportArray = array();
    while ($StartYMDTime <= $EndYMDTime && $diffDays >= 0) {
        $diffDays -= 1;
        $exportActiveYMD = date("Y-m-d", $StartYMDTime);
        if (!StatFirstKeepReport($Channel, $exportActiveYMD, $fistLoginReportArray, $fistPayReportArray, $accountFirstLoginDateInfo, $accountFirstPayDateInfo)) {
            return;
        }
        $StartYMDTime = strtotime("+1 day", $StartYMDTime);
    }
    \Logging\LogInfo("fistLoginReportArray:" . print_r($fistLoginReportArray, true));
    \Logging\LogInfo("fistPayReportArray:" . print_r($fistPayReportArray, true));
    // 生成插入数据
    $batchInsDatas = array();
    foreach ($fistLoginReportArray as $CreateYMD => $ymdArray) {
        foreach ($ymdArray as $ReportYMD => $statInfo) {
            $keepCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
            $payTotal = $statInfo["payTotal"] ? $statInfo["payTotal"] : 0;
            array_push($batchInsDatas, array(
                "Channel" => $Channel,
                "CreateYMD" => $CreateYMD,
                "ReportYMD" => $ReportYMD,
                "KeepCount" => $keepCount,
                "PayTotal" => $payTotal,
            ));
        }
    }
    if (count($batchInsDatas) > 0) {
        // 每次清除后重新插入
        \DBOper\Remove("AccountFirstLoginReport", array("Channel" => $Channel, "ReportYMD" => array('$gte' => $StartYMD)));
        if (!\DBOper\BatchInsert("AccountFirstLoginReport", $batchInsDatas)) {
            return;
        }
    }
    // 生成插入数据
    $batchInsDatas = array();
    foreach ($fistPayReportArray as $FirstPayYMD => $ymdArray) {
        foreach ($ymdArray as $ReportYMD => $statInfo) {
            $keepCount = $statInfo["keepCount"] ? $statInfo["keepCount"] : 0;
            array_push($batchInsDatas, array(
                "Channel" => $Channel,
                "FirstPayYMD" => $FirstPayYMD,
                "ReportYMD" => $ReportYMD,
                "KeepCount" => $keepCount,
            ));
        }
    }
    if (count($batchInsDatas) > 0) {
        // 每次清除后重新插入
        \DBOper\Remove("AccountFirstPayReport", array("Channel" => $Channel, "ReportYMD" => array('$gte' => $StartYMD)));
        if (!\DBOper\BatchInsert("AccountFirstPayReport", $batchInsDatas)) {
            return;
        }
    }
    // 全部处理完毕才更新
    \DBOper\Update("ServerEvent", array("Key" => "AccountFirstKeepReportYMD"), array("Value" => $EndYMD), false, true);
    \Logging\LogInfo("==============================================");
}
/**
 * 统计首登相关留存日期报表,包含充值(用于ltv计算)
 * @param string $Channel
 * @param string $YMD 要统计的报表日期yyyy-MM-dd
 * @param array $fistLoginReportArray 统计首登报表结果 {首登日期:{统计日期:{keepCount:x, payTotal:x}} ...}}
 * @param array $fistPayReportArray 统计首充报表结果 {首充日期:{统计日期:{keepCount:x}} ...}}
 * @param array $accountFirstLoginDateInfo 账号首登日期信息 {accountID:firstLoginYMD, ...}
 * @param array $accountFirstPayDateInfo 账号首充日期信息 {accountID:firstPayYMD, ...}
 */
function StatFirstKeepReport($Channel, $YMD, &$fistLoginReportArray, &$fistPayReportArray, &$accountFirstLoginDateInfo = null, &$accountFirstPayDateInfo = null)
{
    \Logging\LogInfo("=== 统计: " . $YMD);
    if (!isset($fistLoginReportArray)) {
        $fistLoginReportArray = array();
    }
    if (!isset($fistPayReportArray)) {
        $fistPayReportArray = array();
    }
    if (!isset($accountFirstLoginDateInfo)) {
        $accountFirstLoginDateInfo = array();
    }
    if (!isset($accountFirstPayDateInfo)) {
        $accountFirstPayDateInfo = array();
    }
    if (
        !\DBOper\Find(
            "AccountDayActive",
            array("Channel" => $Channel, "ActiveYMD" => $YMD),
            $activeRetArray,
            array("AccountID" => 1)
        ) || !isset($activeRetArray)
    ) {
        return;
    }
    $dayActiveAccIDList = array(); // 统计日期活跃账号ID列表 [accountID, ..]
    $queryFirstLoginAccIDList = array(); // 需要查询首登日期的账号列表 [accountID, ..]
    $queryFirstPayAccIDList = array(); // 需要查询首充日期的账号列表 [accountID, ..]
    foreach ($activeRetArray as $activeInfo) {
        $AccountID = $activeInfo["AccountID"];
        array_push($dayActiveAccIDList, $AccountID);
        if (!isset($accountFirstLoginDateInfo[$AccountID])) {
            array_push($queryFirstLoginAccIDList, $AccountID);
        }
        if (!isset($accountFirstPayDateInfo[$AccountID])) {
            array_push($queryFirstPayAccIDList, $AccountID);
        }
    }
    if (count($queryFirstLoginAccIDList) > 0) {
        if (
            !\DBOper\Find(
                "AccountFirstLogin",
                array("Channel" => $Channel, "AccountID" => array('$in' => $queryFirstLoginAccIDList)),
                $accountRetArray,
                array("AccountID" => 1, "CreateYMD" => 1)
            ) || !isset($accountRetArray)
        ) {
            return;
        }
        foreach ($accountRetArray as $accountInfo) {
            $accountFirstLoginDateInfo[$accountInfo["AccountID"]] = $accountInfo["CreateYMD"];
        }
    }
    if (count($queryFirstPayAccIDList) > 0) {
        if (
            !\DBOper\Find(
                "AccountFirstPay",
                array("Channel" => $Channel, "AccountID" => array('$in' => $queryFirstPayAccIDList)),
                $accountPayRetArray,
                array("AccountID" => 1, "PayYMD" => 1)
            ) || !isset($accountPayRetArray)
        ) {
            return;
        }
        foreach ($accountPayRetArray as $accountInfo) {
            $accountFirstPayDateInfo[$accountInfo["AccountID"]] = $accountInfo["PayYMD"];
        }
    }
    if (
        !\DBOper\Find(
            "PayOrder",
            array(
                "Channel" => $Channel, "State" => 1, "PayTime" => array('$gte' => $YMD . " 00:00:00", '$lte' => $YMD . " 23:59:59"),
                "AccountID" => array('$in' => $dayActiveAccIDList)
            ),
            $payRetArray,
            array("AccountID" => 1, "PayTime" => 1, "OrderAmount" => 1)
        ) || !isset($payRetArray)
    ) {
        return;
    }
    $accountPayDict = array(); // 统计日期活跃账号充值额 {accountID:充值总额, ...}
    foreach ($payRetArray as $payInfo) {
        $AccountID = $payInfo["AccountID"];
        $OrderAmount = $payInfo["OrderAmount"];
        $accountPayDict[$AccountID] = $accountPayDict[$AccountID] + $OrderAmount;
    }
    // 汇总信息
    for ($i = 0; $i < count($dayActiveAccIDList); $i++) {
        $AccountID = $dayActiveAccIDList[$i];
        // 首登
        $FirstLoginYMD = $accountFirstLoginDateInfo[$AccountID];
        if ($FirstLoginYMD) {
            if (!isset($fistLoginReportArray[$FirstLoginYMD])) {
                $fistLoginReportArray[$FirstLoginYMD] = array();
            }
            $statYMDInfo = $fistLoginReportArray[$FirstLoginYMD];
            if (!isset($statYMDInfo[$YMD])) {
                $statYMDInfo[$YMD] = array();
            }
            $statInfo = $statYMDInfo[$YMD];
            $statInfo["keepCount"] = ($statInfo["keepCount"] ? $statInfo["keepCount"] : 0) + 1;
            $payTotal = $accountPayDict[$AccountID] ? $accountPayDict[$AccountID] : 0;
            $statInfo["payTotal"] = ($statInfo["payTotal"] ? $statInfo["payTotal"] : 0) + $payTotal;
            $statYMDInfo[$YMD] = $statInfo;
            $fistLoginReportArray[$FirstLoginYMD] = $statYMDInfo;
        }
        // 首充
        $FirstPayYMD = $accountFirstPayDateInfo[$AccountID];
        if ($FirstPayYMD) {
            if (!isset($fistPayReportArray[$FirstPayYMD])) {
                $fistPayReportArray[$FirstPayYMD] = array();
            }
            $statYMDInfo = $fistPayReportArray[$FirstPayYMD];
            if (!isset($statYMDInfo[$YMD])) {
                $statYMDInfo[$YMD] = array();
            }
            $statInfo = $statYMDInfo[$YMD];
            $statInfo["keepCount"] = ($statInfo["keepCount"] ? $statInfo["keepCount"] : 0) + 1;
            $statYMDInfo[$YMD] = $statInfo;
            $fistPayReportArray[$FirstPayYMD] = $statYMDInfo;
        }
    }
    \Logging\LogInfo("统计每日首登OK " . $YMD);
    return true;
}
/**
 * 获取每日报表
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @param boolean $returnDateKey 是否以日期为字典key格式返回
 * @return array [{k:v, ...}, ...] 或 {dateYMD:{k:v, ...}, ...}
 */
function GetDailyReport($Channel, $fromYMD, $toYMD, $returnDateKey = False)
{
    CheckAndExportDailyReport($Channel); // 每次获取检查导出
    $find = array("Channel" => $Channel, "YMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    \DBOper\Find("DailyReport", $find, $retArray);
    if (!isset($retArray)) {
        $retArray = array();
    }
    $curYMD = date("Y-m-d");
    if ($toYMD >= $curYMD) {
        // 包含当日,实时统计
        array_push($retArray, StatDailyReport($Channel, $curYMD));
    }
    if ($returnDateKey) {
        $retDict = array();
        foreach ($retArray as $repData) {
            $retDict[$repData["YMD"]] = $repData;
        }
        return $retDict;
    }
    return $retArray;
}
/**
 * 查询日期范围内新用户数总数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @return int 总数
 */
function QueryNewUserCount($Channel, $fromYMD = "", $toYMD = "")
{
    $find = array("Channel" => $Channel);
    $ymdCond = array();
    if ($fromYMD) {
        $ymdCond['$gte'] = $fromYMD;
    }
    if ($toYMD) {
        $ymdCond['$lte'] = $toYMD;
    }
    if (count($ymdCond)) {
        $find["CreateYMD"] = $ymdCond;
    }
    $count = \DBOper\Count("AccountFirstLogin", $find);
    return $count;
}
/**
 * 查询日期范围内每日新用户数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @param array $dateCountInfo {date:count, ...}
 * @return int 总数
 */
function QueryNewUserCountByDate($Channel, $fromYMD, $toYMD, &$dateCountInfo)
{
    $count = 0;
    $find = array("Channel" => $Channel, "CreateYMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    $ret = \DBOper\Aggregate("AccountFirstLogin", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => '$CreateYMD',
                'count' => array('$sum' => 1),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo)) {
        foreach ($retInfo as $info) {
            $count += $info["count"];
            $dateCountInfo[$info["_id"]] = $info["count"];
        }
        ksort($dateCountInfo);
    }
    return $count;
}
/**
 * 查询日期范围内新付费用户数总数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @return int 总数
 */
function QueryNewPayUserCount($Channel, $fromYMD = "", $toYMD = "")
{
    $find = array("Channel" => $Channel);
    $ymdCond = array();
    if ($fromYMD) {
        $ymdCond['$gte'] = $fromYMD;
    }
    if ($toYMD) {
        $ymdCond['$lte'] = $toYMD;
    }
    if (count($ymdCond)) {
        $find["PayYMD"] = $ymdCond;
    }
    $count = \DBOper\Count("AccountFirstPay", $find);
    return $count;
}
/**
 * 查询日期范围内每日新付费用户数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @param array $dateCountInfo {date:count, ...}
 * @return int 总数
 */
function QueryNewPayUserCountByDate($Channel, $fromYMD, $toYMD, &$dateCountInfo)
{
    $count = 0;
    $find = array("Channel" => $Channel, "PayYMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    $ret = \DBOper\Aggregate("AccountFirstPay", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => '$PayYMD',
                'count' => array('$sum' => 1),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo)) {
        foreach ($retInfo as $info) {
            $count += $info["count"];
            $dateCountInfo[$info["_id"]] = $info["count"];
        }
        ksort($dateCountInfo);
    }
    return $count;
}
/**
 * 查询日期范围内活跃用户数总数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @return int 总数
 */
function QueryActiveUserCount($Channel, $fromYMD, $toYMD)
{
    $count = 0;
    $find = array("Channel" => $Channel, "ActiveYMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    $ret = \DBOper\Aggregate("AccountDayActive", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => '$AccountID', // 按账号去重分组
                'count' => array('$sum' => 1),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo)) {
        $count = count($retInfo);
    }
    return $count;
}
/**
 * 查询日期范围内每日活跃用户数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @param array $dateCountInfo {date:count, ...}
 * @return int 总数(未去重账号)
 */
function QueryActiveUserCountByDate($Channel, $fromYMD, $toYMD, &$dateCountInfo)
{
    $count = 0;
    $find = array("Channel" => $Channel, "ActiveYMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    $ret = \DBOper\Aggregate("AccountDayActive", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => '$ActiveYMD',
                'count' => array('$sum' => 1),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo)) {
        foreach ($retInfo as $info) {
            $count += $info["count"];
            $dateCountInfo[$info["_id"]] = $info["count"];
        }
        ksort($dateCountInfo);
    }
    return $count;
}
/**
 * 查询日期范围内充值用户及付费总额
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @return array (总人数, 总额度)
 */
function QueryPayUserCountAndTotal($Channel, $fromYMD, $toYMD)
{
    $count = 0;
    $total = 0;
    $find = array("Channel" => $Channel, "State" => 1);
    $ymdCond = array();
    if ($fromYMD) {
        $ymdCond['$gte'] = $fromYMD . " 00:00:00";
    }
    if ($toYMD) {
        $ymdCond['$lte'] = $toYMD . " 23:59:59";
    }
    if (count($ymdCond)) {
        $find["PayTime"] = $ymdCond;
    }
    $ret = \DBOper\Aggregate("PayOrder", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => '$AccountID', // 按账号去重分组
                'total' => array('$sum' =>  '$OrderAmount'),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo)) {
        foreach ($retInfo as $info) {
            $count += 1;
            $total += $info["total"];
        }
    }
    return array($count, $total);
}
/**
 * 查询日期范围内充值用户总数
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @return int 总数
 */
function QueryPayUserCount($Channel, $fromYMD, $toYMD)
{
    $count = 0;
    $find = array("Channel" => $Channel, "State" => 1);
    $ymdCond = array();
    if ($fromYMD) {
        $ymdCond['$gte'] = $fromYMD . " 00:00:00";
    }
    if ($toYMD) {
        $ymdCond['$lte'] = $toYMD . " 23:59:59";
    }
    if (count($ymdCond)) {
        $find["PayTime"] = $ymdCond;
    }
    $ret = \DBOper\Aggregate("PayOrder", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => '$AccountID', // 按账号去重分组
                'count' => array('$sum' => 1),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo)) {
        $count = count($retInfo);
    }
    return $count;
}
/**
 * 查询日期范围内累计付费
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @return int 总付费
 */
function QueryPayTotal($Channel, $fromYMD = "", $toYMD = "")
{
    $total = 0;
    $find = array("Channel" => $Channel, "State" => 1);
    $ymdCond = array();
    if ($fromYMD) {
        $ymdCond['$gte'] = $fromYMD . " 00:00:00";
    }
    if ($toYMD) {
        $ymdCond['$lte'] = $toYMD . " 23:59:59";
    }
    if (count($ymdCond)) {
        $find["PayTime"] = $ymdCond;
    }
    $ret = \DBOper\Aggregate("PayOrder", array(
        array(
            '$match' => $find,
        ),
        array(
            '$group' => array(
                '_id' => null,
                'total' => array('$sum' =>  '$OrderAmount'),
            ),
        )
    ), $retInfo);
    if ($ret && isset($retInfo) && count($retInfo) > 0) {
        $total = $retInfo[0]["total"];
        // foreach ($retInfo as $info) {
        //     $total += $info["total"];
        // }
    }
    return round($total, 2);
}
/**
 * 获取首登及首充用户相关报表
 * @param string $Channel
 * @param string $fromYMD 起始日期yyyy-MM-dd
 * @param string $toYMD 到日期yyyy-MM-dd
 * @param array &$fistLoginReportArray {首登日期:{统计日期:{keepCount:x, payTotal:x}} ...}}
 * @param array &$fistPayReportArray {首充日期:{统计日期:{keepCount:x}} ...}}
 */
function GetAccountFirstLoginPayReport($Channel, $fromYMD, $toYMD, &$fistLoginReportArray, &$fistPayReportArray)
{
    CheckAndExportFirstKeepReport($Channel); // 每次获取检查导出
    $find = array("Channel" => $Channel, "CreateYMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    \DBOper\Find("AccountFirstLoginReport", $find, $newUserRetArray, array("CreateYMD" => 1, "ReportYMD" => 1, "KeepCount" => 1, "PayTotal" => 1));
    if (!isset($newUserRetArray)) {
        $newUserRetArray = array();
    }
    $find = array("Channel" => $Channel, "FirstPayYMD" => array('$gte' => $fromYMD, '$lte' => $toYMD));
    \DBOper\Find("AccountFirstPayReport", $find, $newPayUserRetArray, array("FirstPayYMD" => 1, "ReportYMD" => 1, "KeepCount" => 1));
    if (!isset($newPayUserRetArray)) {
        $newPayUserRetArray = array();
    }
    $curYMD = date("Y-m-d");
    if ($toYMD >= $curYMD) {
        // 包含当日,实时统计
        StatFirstKeepReport($Channel, $curYMD, $fistLoginReportArray, $fistPayReportArray);
    }
    foreach ($newUserRetArray as $info) {
        $FirstLoginYMD = $info["CreateYMD"];
        $ReportYMD = $info["ReportYMD"];
        if (!isset($fistLoginReportArray[$FirstLoginYMD])) {
            $fistLoginReportArray[$FirstLoginYMD] = array();
        }
        $statYMDInfo = $fistLoginReportArray[$FirstLoginYMD];
        $statYMDInfo[$ReportYMD] = array("keepCount" => $info["KeepCount"], "payTotal" => $info["PayTotal"]);
        $fistLoginReportArray[$FirstLoginYMD] = $statYMDInfo;
    }
    ksort($fistLoginReportArray);
    foreach ($newPayUserRetArray as $info) {
        $FirstPayYMD = $info["FirstPayYMD"];
        $ReportYMD = $info["ReportYMD"];
        if (!isset($fistPayReportArray[$FirstPayYMD])) {
            $fistPayReportArray[$FirstPayYMD] = array();
        }
        $statYMDInfo = $fistPayReportArray[$FirstPayYMD];
        $statYMDInfo[$ReportYMD] = array("keepCount" => $info["KeepCount"]);
        $fistPayReportArray[$FirstPayYMD] = $statYMDInfo;
    }
    ksort($fistPayReportArray);
    return;
}
task/minuteLoop.php
@@ -4,6 +4,7 @@
include_once "/db/DBOper.php";
include_once "/Common/ServerOPS.php";
include_once "/Common/Commtox7.php";
include_once "/serverrep/report.php";
\Logging\CreateLogging("task.minuteLoop.php");
@@ -57,6 +58,7 @@
function OnHour($hourStr)
{
    \Logging\LogInfo("Todo OnHour: " . $hourStr);
    \Report\OnExportRep();
}
function OnMinute($minuteStr)