Kaynağa Gözat

操作日志写入MongoDB, 玩家数据写入MongoDB.

王刚 3 yıl önce
ebeveyn
işleme
9fb00aaeac

+ 7 - 12
Gameserver/Amfphp/Services/AppServer.php

@@ -59,13 +59,14 @@ class AppServer {
             }
         }
         $resp->AfterProc();
-        self::LogCmd($resp);                                                    # 记录操作日志
+//        self::LogCmd($resp);                                                    # 向MySQL记录操作日志
+        self::LogCmdMongo($resp);                                               # 向MongoDB写入操作日志
         CLog::flush();                                                          # flush日志
     }
 
     /**
      * 新版: 使用pdo_mysql+dao版本
-     * @param type $resp
+     * @param Resp $resp
      */
     private static function LogCmd($resp) {
         $tablename = 'tab_op_log' . date('Ymd');                                # 今天的表名
@@ -85,20 +86,14 @@ class AppServer {
      * @param Resp $resp
      */
     private static function LogCmdMongo($resp) {
-        $tablename = 'tab_op_log' . date('Ym');                                 # 当月的日志表名 
+        $tablename = 'ylsj2019.tab_op_log' . date('Ym');                        # 当月的日志表名 
         $arr = array(
             'uid' => req()->uid,
-            'zoneid' => req()->zoneid,
-            'cmd' => req()->cmd,
-            'params' => req()->paras,
+            'req' => req()->storage(),
             'ret' => $resp,
+            'ts' => TimeUtil::dtCurrent(),
         );
-        $sql .= sprintf("insert into %s (`uid`,`zoneid`,`cmd`,`days`,`param`,`ret`) values ('%s', %d, %d, %d, '%s', '%s');", #
-                $tablename, req()->uid, req()->zoneid, req()->cmd, #            # Uid, zoneid, cmd
-                (isset(req()->userInfo) ? (totalDays() - totalDays(req()->userInfo->game->baseInfo->firstLogin)) : 0), #  # ps.留存天数
-                JsonUtil::encode(req()->paras), #                               # req->paras
-                JsonUtil::encode($resp->result)); #                             # resp->result
-        daoInst()->exec($sql);                                                  # 执行sql
+        gMongo()->insert($tablename, $arr);
     }
 
     /**

+ 13 - 2
Gameserver/Amfphp/base/HashSaver.php

@@ -6,7 +6,9 @@ namespace loyalsoft;
  * 对象字段将以hash表结构存入Redis中.
  * 以达到拆分读写各个子节点的目标, 最终能够节省(带宽、内存、运算)消耗,提升性能.
  * @author gwang 
- * @version 1.0.0 创建. 2年以前就跟高健讨论过的理念,我一直没有落到实处(高健已经使用了). --gwang 2020.4.24
+ * @version
+ *           1.0.1 增加了一个stVer 记录存储次数.
+ *           1.0.0 创建. 2年以前就跟高健讨论过的理念,我一直没有落到实处(高健已经使用了). --gwang 2020.4.24
  */
 class HashSaver extends Object_ext {
 
@@ -20,6 +22,12 @@ class HashSaver extends Object_ext {
         self::$save_tag[] = $name;
     }
 
+    /**
+     * storeage version 存储版本(每写入一次+1)
+     * @var int
+     */
+    public $stVer = 0;
+
     //
     // <editor-fold defaultstate="collapsed">
 
@@ -41,17 +49,20 @@ class HashSaver extends Object_ext {
      * 存储数据到redis
      */
     function updateDataFull($mem_key) {
+        $this->stVer++;
         return gMem()->hmset($mem_key, $this);
     }
 
     /**
-     * 存储数据到redis
+     * 存储数据到redis(这个设计目的是只保存改变的部分)
      */
     function updateDataByTag($mem_key) {
         $data = array();
         foreach (self::$save_tag as $k) {
             $data[$k] = $this->$k;
         }
+        $this->stVer++;
+        $data['stVer'] = $this->stVer;
         return gMem()->hmset($mem_key, $data);
     }
 

+ 2 - 2
Gameserver/Amfphp/base/Object_ext.php

@@ -32,7 +32,7 @@ class Object_ext {
      * 从对象加载数据(赋值给自己的字段)
      * @param array/Object $obj
      */
-    protected function LoadFrom($obj) {
+    public function LoadFrom($obj) {
         if (func_num_args() != 1 || is_null($obj)) {
             die("too many args or arg obj was null!");
         }
@@ -50,7 +50,7 @@ class Object_ext {
         return $this->toString();
     }
 
-    protected function toString() {
+    public function toString() {
         $str = JsonUtil::encode($this);
         return $str;
     }

+ 8 - 0
Gameserver/Amfphp/base/Req.php

@@ -102,6 +102,14 @@ class Req extends Object_ext {
         return $str;
     }
 
+    public function storage() {
+        $clone = clone $this;
+        if (!$clone->userInfoChanged) {
+            unset($clone->userInfo);                                                # 删除不必要的字段
+        }
+        return $clone;
+    }
+
     // <editor-fold defaultstate="collapsed" desc="单例:想着以后不在通过函数透传此对象了">
 
     /**

+ 4 - 16
Gameserver/Amfphp/model/User/Data_UserGame.php

@@ -100,19 +100,19 @@ class Data_UserGame extends HashSaver {
      * @var Info_College
      */
     public $college;
-    
+
     /**
      * 神庙
      * @var Info_ShenMiao
      */
     public $shenmiao;
-    
+
     /**
      * 宝石系统
      * @var Info_Gem
      */
     public $Gem;
-    
+
     /**
      * 
      * @var Info_Pay
@@ -138,7 +138,6 @@ class Data_UserGame extends HashSaver {
         $this->taskCardShop = new Info_TaskCard_Shop();
         //$this->college->initialize();//改为按天解锁了
         $this->shenmiao->initialize();
-        
     }
 
     /**
@@ -167,18 +166,7 @@ class Data_UserGame extends HashSaver {
             $this->shenmiao = new Info_ShenMiao();
             $this->pay = new Info_Pay();
         } else {                                                                # 实参
-            parent::__construct($arg);                                          # 调用Object的构造函数
-//            $this->shopdata = new Info_UserShop($this->shopdata);
-////            $this->NewbieGuide= new info_us
-//            $this->baseInfo = new Info_UserBase($this->baseInfo);
-//            $this->gates = new Info_UserGateDifficulty($this->gates);
-//            $this->heros = new Info_UserGameHero($this->heros);
-//            $this->privateState = new Info_PrivateState($this->privateState);
-////            $this->profile = n
-//            $this->pvp = new Info_UserPVP($this->pvp);
-//            $this->store = new Info_Store($this->store);
-//            $this->taskCardShop = new Info_TaskCard_Shop($this->taskCardShop);
-//            $this->userSecretshop = new Info_UserSecretshop($this->userSecretshop);
+            parent::__construct($arg);                                          # 调用Object的构造函数 
         }
         $this->profile = new Data_UserProfile();                                     # 初始化用户画像模块
     }

+ 0 - 11
Gameserver/Amfphp/model/User/UserInfoMo.php

@@ -8,17 +8,6 @@ namespace loyalsoft;
  * @author gwang
  */
 class UserInfoMo {
-//    /**
-//     * 分区Id
-//     * @var string
-//     */
-//    public $zoneid;
-//
-//    /**
-//     * 玩家唯一id
-//     * @var string
-//     */
-//    public $uid;
 
     /**
      * 玩家游戏数据

+ 54 - 19
Gameserver/Amfphp/process/UserProc.php

@@ -1,7 +1,9 @@
 <?php
 
 namespace loyalsoft;
+
 require_once __DIR__ . '/../service_call/pay/official/pay_op.php';
+
 /**
  * Description of UserProc
  * 玩家数据处理流程
@@ -53,24 +55,24 @@ class UserProc {
                 Err(ErrCode::cmd_err);
         }
     }
-    
+
     /**
      * 检测遗漏订单
      */
-    static function checkMissOrder() {       
+    static function checkMissOrder() {
         $tableName = "tpl_order_tab";
-        
-        if (daoInst()->tableExist($tableName)) {         
+
+        if (daoInst()->tableExist($tableName)) {
             $arr = daoInst()->select("*")->from($tableName)
-                ->where('uid')->eq(req()->uid)
-                ->andWhere('zoneid')->eq(req()->zoneid)
-                ->andWhere('status')->eq(1)
-                ->andWhere('drawed_ts')->eq(0)    
-                ->fetchAll();
-           
-            if(count($arr)!=null){
-                foreach ($arr as $item){           
-                    $result = pay_op::CheckAndDrawOrder(req()->uid,$item->cpOrderId,array(new PayProc,'distributePayGoods'));
+                    ->where('uid')->eq(req()->uid)
+                    ->andWhere('zoneid')->eq(req()->zoneid)
+                    ->andWhere('status')->eq(1)
+                    ->andWhere('drawed_ts')->eq(0)
+                    ->fetchAll();
+
+            if (count($arr) != null) {
+                foreach ($arr as $item) {
+                    $result = pay_op::CheckAndDrawOrder(req()->uid, $item->cpOrderId, array(new PayProc, 'distributePayGoods'));
                 }
             }
         }
@@ -309,7 +311,7 @@ class UserProc {
                 if ($zone->isRecommended > 0 && $zone->status == 1) {
                     $zoneList[] = $zone;
                 } else {
-
+                    
                 }
             } else {
                 $zoneList[] = $zone;
@@ -398,7 +400,7 @@ class UserProc {
             self::checkMissOrder();                                             #校验是否有漏单
             UserProc::updateUserInfo();                                         # 这一步回存操作只有在 userInfo正常存在的情况下才进行
             $resp = Resp::ok($userInfo);                                        # 设置返回值
-            self::backupUserInfo();                                             # 数据回写
+//            self::backupUserInfo();                                             # 数据回写
 
             AuctionProc::TriggerSettlement();                                   #结算流拍信息
             self::updtateUserZoneInfo();                                        # 1. 更新玩家分区记录
@@ -481,7 +483,7 @@ class UserProc {
         ShopProc::DailyCheck();
         TaskProc::ResetDailyTaskCards();                                        # 重置每日任务卡
     }
-    
+
 // <editor-fold defaultstate="collapsed" desc="创建新用户">
 
     /**
@@ -553,7 +555,17 @@ class UserProc {
         $key = MemKey_User::Info_hash($zoneid, $uid);
         $a = new Data_UserGame();
         if (null == $a->readDataFromMem($key)) {
-            return null;
+            $collection = "ylsj2019.userInfoBack";
+            $cursor = gMongo()->find($collection, ['key' => $key]);
+            if ($cursor) {
+                foreach ($cursor as $v) {
+                    $a->LoadFrom($v->value);                                    # 加载
+                    $a->updateDataFull($key);                                   # 反向写回redis
+                    break;                                                      # 其实是只有一条  
+                }
+            } else {
+                return null;
+            }
         }
         $usrInfo = new UserInfoMo();
         $g = new Data_UserGame($a);
@@ -575,14 +587,16 @@ class UserProc {
      * @param UserInfoMo $userInfo
      */
     public static function setUserInfo($userInfo) {
+        $OK = false;
         if ($userInfo) {
             $zoneid = req()->zoneid;
             $uid = req()->uid;
             $userInfo->game->baseInfo->lastSaveTs = now();
             $key = MemKey_User::Info_hash($zoneid, $uid);
-            return $userInfo->game->updateDataFull($key);
+            $OK = $userInfo->game->updateDataFull($key);                        # 向Redis回写玩家数据
+            self::backupUserInfoMongo();                                        # 向MongoDB备份数据
         }
-        return false;
+        return $OK;
     }
 
 // </editor-fold>
@@ -647,6 +661,27 @@ class UserProc {
         daoInst()->exec($sql);                                                  # 也可以用exec($sql)
     }
 
+    /**
+     * 备份玩家数据,(玩家数据落地),一天一份,当天记为最后一次登录时的状态而非最后一次操作的状态
+     * @history
+     *           version 4.0.0 切换到MongoDB存储
+     *           version 3.0.13 mysql版备份玩家数据, (性能优化后表现还不错)
+     *                      除非用脚本在redis中实现备份,否则直接写入mysql.
+     *                      主要是mysql版本利用存储过程之后性能已经和redis版相差不多.
+     *           version 2.0.0 Redis storage 从MySQL转到Redis中存储. 这个备份数据没有大规模导出硬盘的需求
+     *                      因为mysql版本实在是负载太大了, 上百毫秒. 所以改在redis中了.
+     *           version 1.0.0 Mysql storagef 1. 分表不做了, 2. 存储过程不用了 3. 先能用再说
+     */
+    public static function backupUserInfoMongo() {
+        $collectionName = "ylsj2019.userInfoBack";                              # 表名
+        $key = MemKey_User::Info_hash(req()->zoneid, req()->uid);
+        $doc = array('key' => $key, #                                           # 最新文档
+            'ts' => TimeUtil::dtCurrent(), #                                    # 更新时间
+            'value' => req()->userInfo->game);                                  # 玩家数据
+        $filter = array('key' => $key);                                         # 指定条件 
+        gMongo()->update($collectionName, $filter, $doc, true);                 # 更新
+    }
+
 // </editor-fold>
 //</editor-fold>
 //

+ 14 - 12
Gameserver/Amfphp/test.php

@@ -28,21 +28,23 @@ $cursor = gMongo()->find($collection, $filter, $opt->GetOption());
 if (false === $cursor) {
     exit("查询失败");
 }
-foreach ($cursor as $document) {
-    var_dump($document);
-}
+//foreach ($cursor as $document) {
+//    var_dump($document);
+//}
 // insert
-$collection = "foo.bar";
-$addArr = ['name' => "王刚1", 'age' => 37];
-CLog::Assert("insert", gMongo()->insert($collection, $addArr), "插入数据");
-
-// delete
-$collection = "foo.bar";
-$deletFilter = ['name' => "王刚1"];
-echoLine("删除" . gMongo()->delete($collection, $deletFilter) . "条");
+//$collection = "foo.bar";
+//$addArr = ['name' => "王刚1", 'age' => 37];
+//CLog::Assert("insert", gMongo()->insert($collection, $addArr), "插入数据");
+//
+//// delete
+//$collection = "foo.bar";
+//$deletFilter = ['name' => "王刚1"];
+//echoLine("删除" . gMongo()->delete($collection, $deletFilter) . "条");
 // update
 $collection = "foo.bar";
-$updateArr = [];
+$updateFilter = ['Name' => "王刚(wanggangzero@qq.com)1"];
+$updateArr = ['Name' => "王刚(wanggangzero@qq.com)1", 'Age' => 37, '性别' => "男", '爱好' => '女'];
+echoLine("修改" . gMongo()->update($collection, $updateFilter, $updateArr, true, false));
 
 //
 

+ 32 - 2
Gameserver/Amfphp/util/MongoUtil.php

@@ -50,8 +50,8 @@ class MongoUtil {
      * 查询
      * @author gwang<wanggangzero@qq.com>
      * @param string $collection 集合
-     * @param array $filter
-     * @param array $options
+     * @param array $filter 同过滤器语法
+     * @param array $options 请利用QueryOptionBuilder()来辅助定制选项
      * @return \MongoDB\Driver\Cursor|false 
      */
     public function find($collection, $filter = [], $options = []) {
@@ -123,6 +123,36 @@ class MongoUtil {
         return false;
     }
 
+    /**
+     * 更新
+     * @param string $collection
+     * @param array $whereArr 条件
+     * @param array $newPropertiesArr 可以是一个新文档, 也可以是一个opration数组(eg. $set表达式), 还可以是一组update 管道(批量参考https://docs.mongodb.com/manual/reference/command/update/#update-with-an-aggregation-pipeline)
+     * @param bool $upsert 当符合条件的文档不存在时是否作为一条新的文档插入(当新数据为文档时生效,operation不可以)
+     * @param bool $multi 是否一次更新多个文档的值(当新数据为operation表达式时可以,否则不可以) 默认false
+     * @return boolean
+     */
+    public function update($collection, $whereArr, $newPropertiesArr, $upsert = true, $multi = false) {
+        if (empty($whereArr) || empty($newPropertiesArr)) {
+            return false;
+        }
+        $options = ['multi' => $multi, 'upsert' => $upsert];
+        $conn = $this->mc;
+        if (empty($conn)) {
+            return false;
+        }
+        try {
+            $bulk = new \MongoDB\Driver\BulkWrite();
+            $bulk->update($whereArr, $newPropertiesArr, $options);
+            $writeConcern = new \MongoDB\Driver\WriteConcern(\MongoDB\Driver\WriteConcern::MAJORITY, 30000);
+            $result = $conn->executeBulkWrite($collection, $bulk, $writeConcern);
+            return $result->getUpsertedCount() + $result->getModifiedCount();
+        } catch (\Exception $e) {
+            CLog::err($e->getMessage());                                        # 记录错误日志
+        }
+        return false;
+    }
+
 }
 
 /**