| 项目 | 详情 |
|---|---|
| 审查日期 | 2026年5月19日 |
| 审查范围 | 数据库查询、缓存策略、队列处理、同步阻塞 |
| 发现问题数 | 35个 |
| 高风险 | 10个 |
| 中风险 | 15个 |
| 低风险 | 10个 |
位置:common.php:437,464,483,510
// get_top_channel() - 每次循环执行一次查询
while ($info = $channelModel->where(['id' => $channelId])->find()) {
if ($info['level'] == 1 || ...) return $info;
$channelId = $info['parent_id'];
}
影响:
建议:使用 id_path 字段一次性查询
位置:admin/controller/Pay.php:366-376
foreach ($list as $key => $item) {
$channel = get_top_second_channel_name($list[$key]['channel_id']); // 2-5次查询
$list[$key]['union_name'] = get_union_channel($list[$key]['channel_id'])['name']; // 2-5次查询
}
影响:分页 100 条 → 每条 4-10 次查询 → 400-1000 次额外查询
建议:批量预查询 + 数组映射
位置:api/controller/Index.php:607-629
foreach ($child_list as $v) {
// 每次循环都查一次!且条件完全相同
$promoter_list = Db::name('nw_channel')
->where(['parent_id' => ['in', $child_list_ids], 'level' => 3])
->select();
}
建议:提到循环外
位置:admin/controller/Pay.php:382-409
// 同一请求中,对 cy_pay 表执行了 3 次几乎相同的聚合查询
$totalAmount = $PayModel->...->find()['total_amount'];
$todayTotalAmount = model('Pay')->...->find()['total_amount'];
$lastDayTotalAmount = model('Pay')->...->find()['total_amount'];
建议:合并为单条查询
位置:admin/controller/Pay.php:1468-1477
$pay = Db::name('cy_pay')->where(['orderid' => $orderid])->find(); // SELECT *
$members = Db::name('cy_members')->where(['id' => $userid])->find(); // SELECT *
$pyb = Db::name('nw_channel_admin')->where(['id' => $chal_id])->find(); // SELECT *
影响:连续 6 次 SELECT *,读取不必要的字段
建议:使用 ->field('id,orderid,amount') 指定字段
位置:api/controller/Index.php:564
->where(['id_path' => ['like', '%'.$qy_child_id_path.'%']])
影响:% 前缀导致索引失效,全表扫描
建议:改用前缀匹配 LIKE 'xxx%'
位置:api/controller/JhApi.php:357-362
->join('cy_members cm', 'ns.member_id = cm.id', 'left')
->join('cy_game cg', 'ns.game_id = cg.id', 'left')
->join('nw_channel nc', 'ns.channel_id = nc.id', 'left')
->join('nw_channel p1', 'nc.parent_id = p1.id', 'left')
->join('nw_channel p2', 'p1.parent_id = p2.id', 'left')
影响:nw_channel 被 JOIN 3 次
建议:使用 id_path 拆分或冗余父级名称
位置:admin/controller/FkExceptionRecords.php:191-195
$data = Db::name('fk_exception_records')
->where($where)
->order('record_id desc')
->limit(10000)
->select();
建议:使用分页或流式查询
位置:admin/controller/Pay.php:904-912
$regSql = model('MemberGameServer')->field(...)->group(...)->buildSql();
$unionSql = model('Pay')->field(...)->group(...)->union([$regSql])->buildSql();
$unionSqlNew = Db::table($unionSql . ' bb')->field(...)->group(...)->buildSql();
$list = Db::table($unionSqlNew . ' cc')->paginate(...);
建议:使用临时表或预计算汇总数据
位置:admin/controller/Setting.php:149-151
foreach ($channelList as $v) {
$hasChildren = Db::name('nw_channel')
->where(['parent_id' => $v['value'], 'status' => 1])
->count() > 0;
}
建议:使用 GROUP BY 批量查询
| 配置项 | 值 |
|---|---|
| 驱动 | complex (默认 Redis) |
| 前缀 | newsdk_ |
| Redis 过期 | 7天 |
| 文件缓存过期 | 30天 |
| Session | Redis,前缀 think_,过期 35小时 |
| 类别 | Key 模式 | 过期时间 |
|---|---|---|
| Token | {token字符串} |
15/30天 |
| 订单号 | orderid:{orderid} |
24小时 |
| 渠道缓存 | clearable_channel_list_* |
24小时-30天 |
| 游戏列表 | clearable_game_list{md5} |
300秒 |
| 配置 | clearable_get_setting:{name} |
24小时 |
| 风控名单 | risk_control:inspect_list:{type}:{value} |
7天 |
问题:查不到数据时不缓存空值
// Setting::getSetting() - 不缓存空值
$info = self::where(['name' => $name])->value('value');
if (empty($info)) {
return false; // 不缓存,下次继续查库
}
建议:缓存空值,短过期时间
问题:热点 key 无互斥锁
// Game::getAllByCondition() - 无并发保护
$result = Cache::get($cacheKey);
if (empty($result)) {
$result = Db::table(...)->select(); // 高并发下同时查库
Cache::set($cacheKey, $result, 300);
}
建议:使用互斥锁
问题:同类 key 过期时间高度集中
Cache::clear('channel_cache') 瞬间删除所有渠道缓存建议:过期时间加随机偏移
Cache::store('default')->handler()Cache::set/get存在多种分隔符混用:
token|sdk|{userid}|{gameid}(竖线)risk_control:rule:frequency:*(冒号)clearable_channel_list_(下划线)| 机制 | 用途 | 位置 |
|---|---|---|
| ThinkPHP Queue (Redis) | 支付回调异步通知 CP | extra/queue.php |
| 原生 Redis List | 打包任务、报表生成 | SubPackage.php |
| 原生 Redis ZSet | 抖音广告推送 | Tiktok.php |
| Kafka | 通用消息队列(示例阶段) | Kafka.php |
位置:Tiktok.php:22
$this->_redis->select(Env::get('redis.seelect_go', '')); // 拼写错误!
问题:配置键 redis.seelect_go 拼写错误,可能连接到错误的 DB
位置:crontab/controller/asyncCallback.php
$job->attempts() > 5位置:PayCallback.php:78-95
private function _doCallback($callback_url, $callback_params, $retry_times = 1, $sleep = 5)
// retry_times = 1,sleep 被注释掉,实际只请求一次
MakeReport.php、Tiktok.php 等直接 new \Redis()enable.auto.commit => false位置:crontab/ChannelClean.php:347-376
for($i=1; $i<=$day_limit; $i++){
// 每天单独查询一次数据库
$temp_ids = Db::table('cy_member_channel_game_rel')...
}
建议:改为单条 SQL 的 BETWEEN
| 位置 | 函数 | 问题 |
|---|---|---|
common.php:1290-1296 |
timingCurl() |
未设置任何超时 |
api/complex/Yehua.php:121-133 |
curlInit() |
未设置任何超时 |
api/controller/v2/Huge.php:175-188 |
curlJson() |
未设置任何超时 |
admin/controller/Tasks.php:814-830 |
curl 调用 | 未设置任何超时 |
crontab/DealGameChannelDivide.php:156-167 |
curl 调用 | 未设置任何超时 |
| 位置 | 超时 | 说明 |
|---|---|---|
common/library/Sms.php:533-548 |
300秒 | 短信发送不应超过 30 秒 |
common/library/jssdk.php:125-139 |
500秒 | 微信 JSSDK,荒谬超时 |
common.php:365-388 |
60秒 | 通用 HTTP 工具函数 |
home/common.php:391-406 |
60秒 | Web 控制器调用 |
位置:service/PayService.php:922-926、api/controller/v1/Pay.php:1885-1889
return $xml; // return 在 curl_close($ch) 之前
// curl 句柄永远不会关闭
| 位置 | 说明 |
|---|---|
api/controller/v1/Login.php:298 |
淘宝 IP 查询,无超时 |
common/model/CscAppeal.php:137 |
淘宝 IP 查询,无超时 |
common.php:1662 |
IP 查询,无超时 |
位置:api/common.php:14-52
CONNECTTIMEOUT=30, TIMEOUT=5 // 连接超时30秒过长,总超时仅5秒,配置反了
位置:config.php:327-345
'deploy' => 0, // 集中式
'rw_separate' => false, // 读写未分离
// 从库配置与主库相同,从库形同虚设
位置:crontab/mlbb/TaskHandle.php:52-86
do {
// 调用外部 API
usleep(10000); // 仅 10ms
} while(true); // 无限循环
位置:common.php:394-408
function makeOrderid($head = 'WL') {
if ($redis->has($cache_key)) {
return makeOrderid(); // 无递归深度保护
}
}
| 序号 | 问题 | 位置 |
|---|---|---|
| 1 | 渠道层级遍历 N+1 查询 | common.php:437 |
| 2 | Pay 列表循环内调用查询函数 | admin/controller/Pay.php:366 |
| 3 | 循环内重复查询(Bug) | api/controller/Index.php:620 |
| 4 | 大表重复聚合查询 | admin/controller/Pay.php:382 |
| 5 | 缓存穿透风险 | 全局 |
| 6 | 缓存击穿风险 | 全局 |
| 7 | 缓存雪崩风险 | 全局 |
| 8 | Redis DB 选择混乱 | Tiktok.php:22 |
| 9 | curl 无超时设置 | 5 处 |
| 10 | curl 超时过长(300-500秒) | 2 处 |
| 序号 | 问题 | 数量/位置 |
|---|---|---|
| 1 | 缺少 field() 的 SELECT * | 10+ 处 |
| 2 | LIKE '%xxx%' 前缀通配符 | 2 处 |
| 3 | 3+ 表复杂 JOIN | 多处 |
| 4 | limit(10000) 无分页 | 1 处 |
| 5 | 3 层嵌套子查询 UNION | 1 处 |
| 6 | 循环内 COUNT 查询 | 1 处 |
| 7 | Token 存储不一致 | 全局 |
| 8 | 缓存 Key 设计不规范 | 全局 |
| 9 | 异步回调重试缺陷 | asyncCallback.php |
| 10 | 同步回调重试名存实亡 | PayCallback.php |
| 11 | 原生 Redis 与框架并存 | 多处 |
| 12 | Kafka 仅示例阶段 | Kafka.php |
| 13 | 队列监控缺失 | 全局 |
| 14 | 定时任务 N+1 查询 | ChannelClean.php |
| 15 | curl 句柄泄漏 | 2 处 |
| 序号 | 问题 |
|---|---|
| 1 | file_get_contents 访问外部 URL |
| 2 | CONNECTTIMEOUT 配置不合理 |
| 3 | 数据库读写未分离 |
| 4 | 长时间运行任务无保护 |
| 5 | 递归调用无深度限制 |
| 6 | 缓存 Key 分隔符混用 |
| 7 | 直接操作 Redis handler 不一致 |
| 8 | 定时任务 usleep 过短 |
| 9 | 密钥文件每次请求读取 |
| 10 | 文件日志锁竞争 |
id_path 一次性查询redis.seelect_go → redis.select_gocurl_close 移到 return 前// ❌ 当前:循环查询
while ($info = $channelModel->where(['id' => $channelId])->find()) {
$channelId = $info['parent_id'];
}
// ✅ 建议:使用 id_path
$ids = explode(',', trim($channel->id_path, ','));
$channels = $channelModel->whereIn('id', $ids)->select();
// ❌ 当前:不缓存空值
if (empty($info)) return false;
// ✅ 建议:缓存空值
if (empty($info)) {
Cache::set($cache_key, '', 300);
return false;
}
// ✅ 建议:互斥锁
$result = Cache::get($cacheKey);
if (empty($result)) {
$lockKey = $cacheKey . ':lock';
if (Cache::set($lockKey, 1, 5)) {
$result = Db::table(...)->select();
Cache::set($cacheKey, $result, 3600);
Cache::rm($lockKey);
} else {
usleep(100000);
return $this->getData($cacheKey);
}
}
// ✅ 建议
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
审查报告生成时间:2026年5月19日