From 453541f9bcb74e70885a0b3450cacc523295ad7b Mon Sep 17 00:00:00 2001 From: hujiabin <519660157@qq.com> Date: Wed, 17 May 2023 14:07:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8E=92=E4=BD=8D=E6=8E=92?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/game2006service/package-lock.json | 83 ++++++- server/game2006service/package.json | 3 +- server/game2006service/services/redis.js | 187 ++++++++++++++ server/game2006service/tasks/factory.js | 3 +- server/game2006service/tasks/rankings.js | 245 +++++++++++++++++++ webapp/bootstrap/constant.php | 2 +- webapp/controller/SeasonController.class.php | 83 +++++-- webapp/models/User.php | 66 ++--- 8 files changed, 601 insertions(+), 71 deletions(-) create mode 100644 server/game2006service/services/redis.js create mode 100644 server/game2006service/tasks/rankings.js diff --git a/server/game2006service/package-lock.json b/server/game2006service/package-lock.json index a467ef99..e9f8dc8f 100644 --- a/server/game2006service/package-lock.json +++ b/server/game2006service/package-lock.json @@ -8,7 +8,8 @@ "name": "game2006admin", "version": "1.0.0", "dependencies": { - "j7": "file:../../third_party/j7" + "j7": "file:../../third_party/j7", + "redis": "^3.1.2" } }, "../../../third_party/j7": { @@ -41,12 +42,63 @@ "urlencode": "^1.1.0" } }, + "node_modules/denque": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/j7": { "resolved": "../../third_party/j7", "link": true + }, + "node_modules/redis": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/redis/-/redis-3.1.2.tgz", + "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "dependencies": { + "denque": "^1.5.0", + "redis-commands": "^1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } } }, "dependencies": { + "denque": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" + }, "j7": { "version": "file:../../third_party/j7", "requires": { @@ -61,6 +113,35 @@ "redis": "^4.1.0", "urlencode": "^1.1.0" } + }, + "redis": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/redis/-/redis-3.1.2.tgz", + "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "requires": { + "denque": "^1.5.0", + "redis-commands": "^1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + } + }, + "redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "requires": { + "redis-errors": "^1.0.0" + } } } } diff --git a/server/game2006service/package.json b/server/game2006service/package.json index 1e0745b8..dac36ee8 100644 --- a/server/game2006service/package.json +++ b/server/game2006service/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": {}, "dependencies": { - "j7": "file:../../third_party/j7" + "j7": "file:../../third_party/j7", + "redis": "^3.1.2" } } diff --git a/server/game2006service/services/redis.js b/server/game2006service/services/redis.js new file mode 100644 index 00000000..aec4a412 --- /dev/null +++ b/server/game2006service/services/redis.js @@ -0,0 +1,187 @@ +const redis = require("redis") +const REDIS_CONF = { + port: 6379, + host: '127.0.0.1' +} + +class Redis { + + constructor() { + this.redisClient = redis.createClient({ + host: REDIS_CONF.host, + port: REDIS_CONF.port + // url: `redis://${REDIS_CONF.host}:${REDIS_CONF.port}`, + // legacyMode: true + }); + + // 配置redis的监听事件 + this.redisClient.on('ready', function() { + console.log('Redis Client: ready') + }) + + // 连接到redis-server回调事件 + // this.redisClient.on('connect', function () { + // console.log(new Date(), 'redis is now connected!'); + // }); + + // this.redisClient.on('reconnecting', function () { + // console.log(new Date(), 'redis reconnecting', arguments); + // }); + // + // this.redisClient.on('end', function () { + // console.log('Redis Closed!'); + // }); + // + // this.redisClient.on('warning', function () { + // console.log('Redis client: warning', arguments); + // }); + + // this.redisClient.on('error', err => { + // console.log('Redis Error ' + err); + // }); + + // // 判断redis是否连接 + // if (this.redisClient.isOpen) { + // console.log('rredis is now connected!') + // } else { + // this.redisClient.connect().catch(error => console.log(error)); + // } + } + + // async connect() { + // await this.redisClient.connect().catch(error => console.log(error)); + // } + + quit() { + this.redisClient.quit(); + } + + async exists(key) { + return new Promise((resolve, reject) => { + this.redisClient.exists(key, (err, result) => { + if (err) { + console.log(err); + reject(false); + } + resolve(result); + }) + }) + } + + async set(key, value, exprires) { + if (typeof value === 'object') { + value = JSON.stringify(value) + } + return new Promise((resolve, reject) => { + this.redisClient.set(key, value, (err, result) => { + if (err) { + reject(false); + } + if (!isNaN(exprires)) { + this.redisClient.expire(key, exprires); + } + resolve(result); + }) + }) + } + + async get(key) { + return new Promise((resolve, reject) => { + this.redisClient.get(key, (err, result) => { + if (err) { + reject(false); + } + resolve(result); + }) + }) + } + + async del(key) { + + return new Promise((resolve, reject) => { + this.redisClient.del(key, (err, result) => { + if (err) { + reject(false); + } + resolve(result); + }) + }); + } + async hset(key,field,value) { + if (typeof value === 'object') { + value = JSON.stringify(value) + } + return new Promise((resolve, reject) => { + this.redisClient.hset(key,field ,value,(err, result) => { + if (err) { + reject(false); + } + resolve(result); + }) + }) + } + + async hget(key,field) { + return new Promise((resolve, reject) => { + this.redisClient.hget(key,field ,(err, result) => { + if (err) { + reject(false); + } + resolve(result); + }) + }) + } + + async hgetall(key) { + return new Promise((resolve, reject) => { + this.redisClient.hgetall(key,(err, result) => { + if (err) { + reject(false); + } + resolve(result); + }) + }) + } + + // // push 将给定值推入列表的右端 返回值 当前列表长度 + // async rPush(key, list, exprires) { + // return new Promise((resolve, reject) => { + // this.redisClient.rPush(key, list, (err, length) => { + // if (err) { + // reject(false); + // } + // if (!isNaN(exprires)) { + // this.redisClient.exports(key, exprires); + // } + // resolve(length); + // }) + // }) + // } + // + // // 查询list的值 + // async lrange(key, startIndex = 0, stopIndex = -1) { + // return new Promise((resolve, reject) => { + // this.redisClient.lRange(key, startIndex, stopIndex, (err, result) => { + // if (err) { + // reject(false); + // } + // resolve(result) + // }) + // }) + // } + // + // // 清除list中n个值为value的项 + // async lrem(key, n = 1, value) { + // return new Promise((resolve, reject) => { + // this.redisClient.lrem(key, n, value, (err, result) => { + // if (err) { + // return false + // } + // resolve(result); + // }) + // }); + // } + +} + +module.exports = new Redis(); diff --git a/server/game2006service/tasks/factory.js b/server/game2006service/tasks/factory.js index de8ac510..2c73b3b9 100644 --- a/server/game2006service/tasks/factory.js +++ b/server/game2006service/tasks/factory.js @@ -6,10 +6,9 @@ function add(name) { } function init() { - add('fragment'); - add('damping'); add('season'); add('feeback'); + add('rankings'); } exports.init = init; diff --git a/server/game2006service/tasks/rankings.js b/server/game2006service/tasks/rankings.js new file mode 100644 index 00000000..fa347543 --- /dev/null +++ b/server/game2006service/tasks/rankings.js @@ -0,0 +1,245 @@ +const app = require('j7/app'); +const utils = require('j7/utils'); +const constant = require('../constant'); +const Redis = require('../services/redis'); + +/** + * redis@3.1.2 + */ +const RANKING_KEY = 'game2006api:' +const LAST_IDX = 'userLastIdx' + +class Rankings { + async start() { + console.log('Rankings.start'); + while (true) { + await this.doRanking(); + // const sleepTime = 60*60*2 + const sleepTime = 60*60*2 + console.log('sleepTime:' + sleepTime, new Date(), sleepTime /60); + await utils.sleep(sleepTime * 1000); + } + + } + + async doRanking() { + //redis连接 + // Redis.connect() + try { + const {err, conn} = await app.getDbConn("GameDb20060"); + if (err){ + throw err + } + if (await Redis.exists(RANKING_KEY + "account")){ + await Redis.del(RANKING_KEY + 'account') + await this.calcRanking(); + console.log("跟新rank榜单") + }else { + await this.calcRanking(); + console.log("生成rank榜单") + } + }catch (err){ + console.log(err); + } + //关闭redis连接 + // Redis.quit() + } + + //计算用户排行榜 + async calcRanking() { + console.log("calc ranking..."); + const {err, conn} = await app.getDbConn("GameDb20060"); + if (err) { + throw err; + } + if (!err && conn) { + // 从user表中遍历所有用户,每次取1000个用户,逐步加入到排序表中 + let sorted = []; + let lastIdx = 0; + while(lastIdx>=0) { + const result = await this.getRecords(conn, lastIdx, 1000); + lastIdx = result.lastIdx; + + if (lastIdx != -1) { + this.recordsSort(sorted, result.records) + } + } + await this.pushResultToRedis(sorted); + + // let rankList = await this.pushRankingResult(sorted); + // console.log(rankList); + + //存储到redis中 + // let listJson = JSON.stringify(rankList); + // Redis.set(RANKING_KEY + "rank",listJson); + console.log("calc ranking end"); + + } + } + + //增加排名列 + async pushResultToRedis(sorted) { + let rankingList = []; + await utils.serial( + sorted, + (value , index) =>{ + value.ranking = index+1 + Redis.hset(RANKING_KEY + "ranking",value.ranking,value.account_id) + Redis.hset(RANKING_KEY + "account",value.account_id,value.ranking) + } + ) + } + + + + + + + + //跟新用户榜单 + async updateRankRecords(conn,data){ + if (!data || data.length == 0){ + return + } + for (let item of data) { + const {err, row} = await conn.execQueryOne( + 'select idx,account_id,channel,convert(`name` using utf8) as name,head_id,head_frame,rank,history_best_rank,score,history_best_score,createtime, score_modifytime from t_user where account_id = ? and score_modifytime > ?', + [ + item.account_id, + item.score_modifytime, + ] + ); + if (err){ + throw err + } + if (row){ + item.idx = row.idx + item.account_id = row.account_id + item.channel = row.channel + item.name = row.name + item.head_id = row.head_id + item.head_frame = row.head_frame + item.rank = row.rank + item.history_best_rank = row.history_best_rank + item.score = row.score + item.history_best_score = row.history_best_score + item.score_modifytime = row.score_modifytime + } + } + let sorted = []; + this.recordsSort(sorted,data) + let rankList = await this.pushRankingResult(sorted); + //更新redis中榜单 + let newListJson = JSON.stringify(rankList); + Redis.set(RANKING_KEY + "rank",newListJson); + } + + //获取数据库中最后一个自增id + async getDbLastIdx (conn){ + const {err, row} = await conn.execQueryOne( + "select max(idx) as last_idx from t_user" + ) + if (err) { + throw err; + } + return row.last_idx; + } + + //追加新用户到榜单记录中 + async pushNewUserRecords(conn,lastIdx,data){ + while(lastIdx>=0) { + const result = await this.getRecords(conn, lastIdx, 1000); + lastIdx = result.lastIdx; + if (lastIdx != -1) { + this.recordsSort(data, result.records) + } + } + console.log("LAST_IDX : ",lastIdx) + let rankList = await this.pushRankingResult(data); + //存储到redis中 + let listJson = JSON.stringify(rankList); + Redis.set(RANKING_KEY + "rank",listJson); + } + + + + //获取用户记录 + async getRecords(conn, lastIdx, limit) { + const {err, rows} = await conn.execQuery( + 'select idx,account_id,channel,convert(`name` using utf8) as name,head_id,head_frame,rank,history_best_rank,score,history_best_score,createtime, score_modifytime from t_user where idx > ? order by idx LIMIT ?', + [ + lastIdx, + limit + ] + ); + if (err) { + throw err; + } + if (rows.length==0) { + return { + lastIdx: -1, + records: [] + }; + } + // Redis.set(LAST_IDX,rows[rows.length-1].idx) + return { + lastIdx: rows[rows.length-1].idx, + records: rows + }; + } + + //给记录按分数排序 + recordsSort(sorted, records) { + // 根据分数加入到排序表中 + for (let element of records) { + if (element.score>0) { + if (sorted.length >= 20){ + if (element.score >= sorted[sorted.length - 1].score){ + sorted.pop() + this._recordsSort(sorted,element) + } + } else { + this._recordsSort(sorted,element) + } + } + } + // if (sorted.length>10000) { + // sorted.length = 10000; + // } + } + + _recordsSort(sorted,element){ + sorted.push(element); + sorted.sort(function(a,b) { + let r = b.score - a.score; + if (r==0) { + r = a.score_modifytime - b.score_modifytime; + } + if (r==0) { + r = a.idx - b.idx; + } + return r; + }); + } + + //增加排名列 + async pushRankingResult(sorted) { + let rankingList = []; + await utils.serial( + sorted, + (value , index) =>{ + value.ranking = index+1 + rankingList.push(value) + } + ) + return rankingList; + } +} + + + +function init() { + (new Rankings()).start(); +} + +exports.init = init; \ No newline at end of file diff --git a/webapp/bootstrap/constant.php b/webapp/bootstrap/constant.php index 41cbd5e2..dd1aad4d 100644 --- a/webapp/bootstrap/constant.php +++ b/webapp/bootstrap/constant.php @@ -1,7 +1,7 @@ userInfo['rank']); $nextRankMeta = mt\Rank::getNextRankById($this->userInfo['rank']); if (!$nextRankMeta){ @@ -87,17 +89,14 @@ class SeasonController extends BaseAuthedController { 'season_end_time' => strtotime($this->currRankSeasonMeta['end_time']), 'season_reward' => Season::seasonReward($rankMeta) ); - $r = $this->_getRedis($this->rankingUnid); - $rankList = $this->readRankingList($r,$this->rankingUnid); $list = array(); - if (!$rankList){ - $rankList = $this->checkRankingList(); - $this->saveRankingList($r,$this->rankingUnid,$rankList); - } - foreach ($rankList as $v){ - if ($this->userInfo['rank'] == $v['rank'] && count($list) < 20){ - array_push($list,$v); + if ($r->exists(RANKING_KEY.$this->rankingUnid)){ + $users = User::getListByRank($this->userInfo['rank']); + foreach ($users as $user){ + $user['ranking'] = $r->hget(RANKING_KEY.$this->rankingUnid,$user['account_id']); + $userDto = $this->_getRewardByRanking($user); + array_push($list,$userDto); } } $info['season_rank'] = $list; @@ -111,16 +110,13 @@ class SeasonController extends BaseAuthedController { die(); } $r = $this->_getRedis($this->rankingUnid); - $rankList = $this->readRankingList($r,$this->rankingUnid); $list = array(); - if (!$rankList){ - $rankList = $this->checkRankingList(); - $this->saveRankingList($r,$this->rankingUnid,$rankList); - } - - foreach ($rankList as $v){ - if ($rank_param == $v['rank'] && count($list) < 20){ - array_push($list,$v); + if ($r->exists(RANKING_KEY.$this->rankingUnid)){ + $users = User::getListByRank($rank_param); + foreach ($users as $user){ + $user['ranking'] = $r->hget(RANKING_KEY.$this->rankingUnid,$user['account_id']); + $userDto = $this->_getRewardByRanking($user); + array_push($list,$userDto); } } $this->_rspData(['info'=>$list]); @@ -265,9 +261,54 @@ class SeasonController extends BaseAuthedController { return $info; } - private function checkRankingList(){ + private function _getRewardByRanking($user){ + if (!$user){ + return ; + } + $rewardParamMeta = \mt\Parameter::getByName('rank_ring_reward'); + $rewardParamMetaValue = $rewardParamMeta ? $rewardParamMeta['param_value'] : ''; + $rewardList = explode('|',$rewardParamMetaValue); + + $userDto = User::getUserByRankMess($user); + $userDto['ranking'] = $user['ranking']; + if ($userDto['score'] >= $this->starshine){ + $userDto['echelonTopX'] = \services\FormulaService::echelonTopX($user['ranking']); + $userDto['standardTopX'] = \services\FormulaService::standardTopX($user['ranking'],$userDto['echelonTopX']); + switch ($user['ranking']){ + case 1:$userDto['ring_item_id'] = $rewardList[0];break; + case 2:$userDto['ring_item_id'] = $rewardList[1];break; + case 3:$userDto['ring_item_id'] = $rewardList[2];break; + default : $userDto['ring_item_id'] = 0; + } + }else{ + $userDto['ring_item_id'] = 0; + } + //当月排位赛最大分配金额 + $maxSum = \services\FormulaService::calcCECMaxSum(); + + //排位赛预计分配额 + $expected_CEC_Sum = 0; + $kingCount = User::getKingCount($this->starshine); + for ($i=1;$i<=$kingCount;$i++){ + $echelonTopX = \services\FormulaService::echelonTopX($i); + $expected_CEC_Sum += \services\FormulaService::calcCECTopXSum($i,$echelonTopX,$kingCount); + } + //排位赛金额% + $rankAmountPre = min($maxSum/($expected_CEC_Sum+1),1); //MIN(当月排位赛最大分配额/(排位赛预计分配额+1),1) + + if ($userDto['score'] >= $this->starshine && + $userDto['ranking'] <= 10000 ){ + $userDto['rewardCEC'] = $userDto['standardTopX']*$rankAmountPre; + }else{ + $userDto['rewardCEC'] = 0; + } + return $userDto; + } + + + private function checkRankingList($users){ $rankList = array(); - $users = User::orderBy(User::allUser()); +// $users = User::orderBy(User::allUser()); $rewardParamMeta = \mt\Parameter::getByName('rank_ring_reward'); $rewardParamMetaValue = $rewardParamMeta ? $rewardParamMeta['param_value'] : ''; $rewardList = explode('|',$rewardParamMetaValue); diff --git a/webapp/models/User.php b/webapp/models/User.php index 9b6c0ad8..07784cc0 100644 --- a/webapp/models/User.php +++ b/webapp/models/User.php @@ -307,61 +307,37 @@ class User extends BaseModel { 'head_id' => $row['head_id'], 'head_frame' => $row['head_frame'], 'rank' => $row['rank'], - 'history_best_rank' => $row['history_best_rank'], +// 'history_best_rank' => $row['history_best_rank'], 'score' => $row['score'], - 'history_best_score' => $row['history_best_score'], +// 'history_best_score' => $row['history_best_score'], //排位场数 'rank_num' => $gameTimes, ); return $toDto; } - public static function orderBy($users){ - $len = count($users); - if ($len<=1){ - return $users; + + public static function getListByRank($rank){ + $field = "idx,account_id,channel,name,head_id,head_frame,rank,score"; + $sql = "select {$field} from t_user where rank=:rank order by score desc limit 20 "; + $whereKv = array( + "rank" => $rank, + ); + $rows = myself()->_getSelfMysql()->execQuery($sql,$whereKv); + + if (!$rows){ + $rows = array(); } - for ($i = 0; $i < $len - 1; $i++) { - for ($j = $i + 1; $j < $len; $j++) { - if ($users[$i]['score'] < $users[$j]['score']) { - $tmp = $users[$i]; - $users[$i] = $users[$j]; - $users[$j] = $tmp; - } - } - } - for ($i = 0; $i < $len - 1; $i++) { - for ($j = $i + 1; $j < $len; $j++) { - if ($users[$i]['score'] == $users[$j]['score'] && - $users[$i]['score_modifytime'] > $users[$j]['score_modifytime']) { - $tmp = $users[$i]; - $users[$i] = $users[$j]; - $users[$j] = $tmp; - } - } - } - for ($i = 0; $i < $len - 1; $i++) { - for ($j = $i + 1; $j < $len; $j++) { - if ($users[$i]['score'] == $users[$j]['score'] && - $users[$i]['score_modifytime'] == $users[$j]['score_modifytime'] && - $users[$i]['idx'] < $users[$j]['idx'] - ) { - $tmp = $users[$i]; - $users[$i] = $users[$j]; - $users[$j] = $tmp; - } - } - } - return $users; + return $rows; + } - public static function allUser(){ - $list = SqlHelper::ormSelect( - myself()->_getSelfMysql(), - 't_user', - array( - ) + public static function getKingCount($starshine){ + $sql = "select count(*) as `count` from t_user where score>:score "; + $whereKv = array( + "score" => $starshine, ); - return $list ? $list: array(); + $count = myself()->_getSelfMysql()->execQueryOne($sql,$whereKv); + return $count?$count['count']:0; } }