From 402ed2e6a90a785d2fce3eca23cd324f350d54c5 Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期四, 31 十月 2024 11:42:16 +0800
Subject: [PATCH] 10162 后台优化(增加全服报表)

---
 Common/CommFunc.php                      |    4 
 db/struct.php                            |   77 ++
 Common/PayOrder.php                      |   28 
 db/DBOper.php                            |   20 
 js/common.js                             |   84 ++
 eventreport/eventreport.php              |   60 +
 index.php                                |    7 
 Server/eventdata/QueryAccountLoginOut.py |  124 +++
 serverrep/ltv.php                        |  161 ++++
 Account/User.php                         |   16 
 Server/eventdata/CommFunc.py             |    9 
 serverrep/keeplogin.php                  |  164 ++++
 serverrep/report.php                     |  785 +++++++++++++++++++++
 task/minuteLoop.php                      |    2 
 serverrep/ImportAccountLoginpay.php      |  444 ++++++++++++
 serverrep/dailyuser.php                  |   97 ++
 serverrep/allview.php                    |  136 +++
 17 files changed, 2,209 insertions(+), 9 deletions(-)

diff --git a/Account/User.php b/Account/User.php
index 97e8965..2e2b501 100644
--- a/Account/User.php
+++ b/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";
diff --git a/Common/CommFunc.php b/Common/CommFunc.php
index cfe2af9..d921f11 100644
--- a/Common/CommFunc.php
+++ b/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']);
 	}
diff --git a/Common/PayOrder.php b/Common/PayOrder.php
index 9f34b56..3e21b83 100644
--- a/Common/PayOrder.php
+++ b/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 娓犻亾
diff --git a/Server/eventdata/CommFunc.py b/Server/eventdata/CommFunc.py
index 0b86239..2bcc04e 100644
--- a/Server/eventdata/CommFunc.py
+++ b/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() # 鍒楄〃锛宺ar鏂囦欢涓墍鏈夊瓙鏂囦欢鐨刾ath锛堢浉瀵逛簬rar鏂囦欢鍖呰�岃█鐨勶級
diff --git a/Server/eventdata/QueryAccountLoginOut.py b/Server/eventdata/QueryAccountLoginOut.py
new file mode 100644
index 0000000..6df9eda
--- /dev/null
+++ b/Server/eventdata/QueryAccountLoginOut.py
@@ -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):
+    ''' 瑙f瀽娴佸悜琛屽唴瀹�
+    @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()
diff --git a/db/DBOper.php b/db/DBOper.php
index bdd6c42..f399e7f 100644
--- a/db/DBOper.php
+++ b/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,
diff --git a/db/struct.php b/db/struct.php
index e95f6e9..b304cd8 100644
--- a/db/struct.php
+++ b/db/struct.php
@@ -20,14 +20,14 @@
 	"GameRoles" => array(
 		array(
 			array("Channel" => 1, "AccountID" => 1),
-			array("鈥榰nique" => true),
+			array("unique" => true),
 		)
 	),
 
 	"GameServerInfo" => array(
 		array(
 			array("Channel" => 1, "ServerID" => 1),
-			array("鈥榰nique" => 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) {
diff --git a/eventreport/eventreport.php b/eventreport/eventreport.php
index e632e97..80715bf 100644
--- a/eventreport/eventreport.php
+++ b/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);
+}
diff --git a/index.php b/index.php
index c718882..42c8ed4 100644
--- a/index.php
+++ b/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("绛夌骇鍒嗗竷")),
diff --git a/js/common.js b/js/common.js
index f1d9ea2..16c5160 100644
--- a/js/common.js
+++ b/js/common.js
@@ -135,4 +135,88 @@
 	isQuerying = true;
 	document.getElementById(key).value = gt.gettext("鏌ヨ涓�...");
 	return true;
+}
+
+/**
+ * 缁樺埗鏇茬嚎鍥�
+ * @param {*} chartID 鍥捐〃ID锛屽叧鑱攈tml涓殑鍏冪礌ID
+ * @param {*} chartText 鍥捐〃鎬绘爣棰�
+ * @param {*} xText x杞存枃鏈紝 濡傜瓑绾�
+ * @param {*} yText y杞存枃鏈紝 濡備汉鏁�
+ * @param {*} labels x杞村埢搴︽枃鏈垪琛紝 濡� 绛夌骇鍒楄〃
+ * @param {*} datasetDataList 鏁版嵁琛ㄦ暟鎹垪琛紝鍗虫瘡鏉$嚎鐨勬暟鎹紝绾跨殑鏁版嵁闀垮害蹇呴』涓巟杞村埢搴︽枃鏈垪琛ㄩ暱搴︿竴鑷�
+ * @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
+					}
+				}
+			}
+		}
+	});
 }
\ No newline at end of file
diff --git a/serverrep/ImportAccountLoginpay.php b/serverrep/ImportAccountLoginpay.php
new file mode 100644
index 0000000..1b5d57d
--- /dev/null
+++ b/serverrep/ImportAccountLoginpay.php
@@ -0,0 +1,444 @@
+<?php
+set_time_limit(600); //鏆傛椂璁剧疆鏈剼鏈墽琛屾椂闂磝绉掞紝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/>";
+	}
+}
diff --git a/serverrep/allview.php b/serverrep/allview.php
new file mode 100644
index 0000000..8ad4566
--- /dev/null
+++ b/serverrep/allview.php
@@ -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>
\ No newline at end of file
diff --git a/serverrep/dailyuser.php b/serverrep/dailyuser.php
new file mode 100644
index 0000000..3a09388
--- /dev/null
+++ b/serverrep/dailyuser.php
@@ -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",
+    "鍏呭�肩敤鎴稤PU" => "DPU",
+    "鏂板鍏呭�肩敤鎴稤NPU" => "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>
\ No newline at end of file
diff --git a/serverrep/keeplogin.php b/serverrep/keeplogin.php
new file mode 100644
index 0000000..1b04650
--- /dev/null
+++ b/serverrep/keeplogin.php
@@ -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>
\ No newline at end of file
diff --git a/serverrep/ltv.php b/serverrep/ltv.php
new file mode 100644
index 0000000..bf1bec5
--- /dev/null
+++ b/serverrep/ltv.php
@@ -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>銆怢TV銆�</caption>";
+            echo "<thead><tr>";
+            echo "<th align='center' width='70'>棣栫櫥鏃ユ湡</th>";
+            echo "<th align='center' width='70'>棣栫櫥浜烘暟</th>";
+            foreach ($dayList as $day) {
+                $title = "绗�" . $day . "鏃TV";
+                echo "<th align='center' width='70'>" .  $title . "</th>";
+            }
+            foreach ($dayTotalList as $day) {
+                $title = $day . "鏃ユ�籐TV";
+                echo "<th align='center' width='70'>" .  $title . "</th>";
+            }
+            echo "<th align='center' width='70'>鎬籐TV(澶�)</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>";
+                }
+
+                // 缁熻绱澶﹍tv
+                $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));
+
+                // 姹囨�籰tv
+                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>";
+                }
+
+                // 鎬籐TV(澶�)
+                $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>
\ No newline at end of file
diff --git a/serverrep/report.php b/serverrep/report.php
new file mode 100644
index 0000000..a4c4b3b
--- /dev/null
+++ b/serverrep/report.php
@@ -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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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 鍒版棩鏈焬yyy-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;
+}
diff --git a/task/minuteLoop.php b/task/minuteLoop.php
index 9eb27e3..ae0d04c 100644
--- a/task/minuteLoop.php
+++ b/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)

--
Gitblit v1.8.0