14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01 | 01 | execute | 1 |
|
true |
|
|
Purpose: 为前端弹窗提供遗漏号码数据源(per D-02: 遗漏计算在后端完成) Output: 可用的 history/missingNum AJAX 端点 + i18n 文本
<execution_context> @D:/code/php/amlhc/.claude/get-shit-done/workflows/execute-plan.md @D:/code/php/amlhc/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-omitted-number-analysis/01-RESEARCH.md @D:/code/php/amlhc/application/admin/controller/History.php @D:/code/php/amlhc/application/admin/model/History.php @D:/code/php/amlhc/application/admin/lang/zh-cn/history.phpFrom application/admin/controller/History.php:
namespace app\admin\controller;
use app\common\controller\Backend;
class History extends Backend {
protected $model = null;
public function _initialize(); // sets $this->model = new \app\admin\model\History
}
// Inherits: $this->success($msg, $data), $this->error($msg) from Backend trait
// Inherits: $this->request->isAjax(), $this->request->get() from think\Controller
From application/admin/model/History.php:
namespace app\admin\model;
use think\Model;
class History extends Model {
protected $name = 'history'; // maps to fa_history table
protected $autoWriteTimestamp = false;
// Fields: expect (int, PK), num1~num7 (int), openTime (datetime)
}
From application/admin/controller/Num.php::getColorMap() pattern:
public function getColorMap() {
$list = $this->model->field('num,color')->select();
$map = [];
foreach ($list as $item) { $map[$item['num']] = $item['color']; }
$this->success($map);
}
// Returns: {code: 1, msg: {"1":"红波","2":"蓝波",...}, data: null}
Expected response format from missingNum():
{
"code": 1,
"msg": "查询成功",
"data": [
{"num": 7, "omit": 50, "color": "绿波"},
{"num": 13, "omit": 32, "color": "红波"}
]
}
-
getMissingNumbers($periods = 10)— 公共方法,计算遗漏号码:- 使用
Db::name('history')->field('expect,num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit($periods)->select()获取最近 $periods 期数据 - 遍历结果,将出现的号码收集到
$appeared数组(key 为号码 int,value 为 true) - 遍历 1-49,找出未在
$appeared中的号码,收集到$missing数组 - 查询更多历史数据用于计算遗漏期数:
Db::name('history')->field('num1,num2,num3,num4,num5,num6,num7')->order('openTime', 'desc')->limit(500)->select() - 查询波色映射:
Db::name('num')->column('color', 'num')获取$colorMap - 对每个遗漏号码调用
calcOmitCount($num, $allHistory)计算遗漏期数 - 组装结果:
['num' => $num, 'omit' => $omitCount, 'color' => $colorMap[$num] ?? '—'] - 使用
usort($result, function($a, $b) { return $b['omit'] - $a['omit']; })按 omit 降序排序 - 返回
$result数组
- 使用
-
calcOmitCount($num, $allHistory)— 私有方法,计算某个号码的遗漏期数:- 遍历
$allHistory(已按 openTime DESC 排序),对每行检查 num1~num7 是否等于 $num - 一旦找到,返回当前索引 $idx(即该号码最后一次出现距今多少期)
- 如果遍历完都没找到,返回
count($allHistory)(表示 500 期内都未出现)
- 遍历
关键实现细节:
- 必须使用
use think\facade\Db;或\think\Db::name()来执行数据库查询(检查当前文件命名空间,如果模型已继承 think\Model,可直接用self::field(...)->select()或Db::name()) - 号码比较必须用
(int)转换,因为数据库返回的 num1~num7 是 int 类型(schema 显示为 int(11)),但要确保一致 - $periods 参数范围校验:1-100,但模型层不校验(由控制器层校验),模型只负责计算
- 波色缺失时返回
'—'字符串作为兜底
添加 use think\facade\Db; 到文件顶部(如果尚未存在)。
<acceptance_criteria>
- application/admin/model/History.php 包含 public function getMissingNumbers($periods = 10)
- application/admin/model/History.php 包含 private function calcOmitCount($num, $allHistory)
- getMissingNumbers 方法体包含 Db::name('history') 或 self:: 查询
- getMissingNumbers 方法体包含 Db::name('num') 查询波色映射
- getMissingNumbers 方法体包含 usort 按 omit 降序排序
- 方法返回数组结构为 [['num' => int, 'omit' => int, 'color' => string], ...]
- 文件顶部有 use think\facade\Db; 或使用 \think\Db:: 完整命名空间
</acceptance_criteria>
grep -c "getMissingNumbers|calcOmitCount" application/admin/model/History.php | grep "2"
History 模型有 getMissingNumbers($periods) 和 calcOmitCount($num, $allHistory) 两个方法,能查询 fa_history 表计算遗漏号码并返回按遗漏期数降序的 [{num, omit, color}] 数组
-
在类中添加权限控制属性(在 _initialize() 方法上方或下方):
// 无需额外权限检查(但仍在 admin 模块内,需要 admin 登录) protected $noNeedRight = ['missingNum']; -
添加 missingNum() 方法:
/** * 查询遗漏号码 * @return void */ public function missingNum() { if ($this->request->isAjax()) { $periods = $this->request->get('periods', 10, 'intval'); if ($periods < 1 || $periods > 100) { $this->error('期数范围必须在 1-100 之间'); } $result = $this->model->getMissingNumbers($periods); $this->success('查询成功', $result); } }
关键实现细节:
- 使用
$this->request->isAjax()判断是否为 AJAX 请求 - 使用
$this->request->get('periods', 10, 'intval')获取参数,默认值 10,强制转为 int - 参数校验:$periods < 1 或 > 100 时调用
$this->error()返回错误 - 调用
$this->model->getMissingNumbers($periods)获取结果 - 使用
$this->success('查询成功', $result)返回标准 FastAdmin 响应格式{code: 1, msg: '查询成功', data: [...]} - 方法无返回值(void),通过 $this->success/error 输出 JSON
不要使用 protected $noNeedRight = ['*'],只放开 missingNum 一个方法即可(最小权限原则)。
<acceptance_criteria>
- application/admin/controller/History.php 包含 protected $noNeedRight = ['missingNum']
- application/admin/controller/History.php 包含 public function missingNum()
- missingNum 方法体包含 $this->request->isAjax() 判断
- missingNum 方法体包含 $this->request->get('periods', 10, 'intval')
- missingNum 方法体包含 $periods < 1 || $periods > 100 范围校验
- missingNum 方法体包含 $this->model->getMissingNumbers($periods) 调用
- missingNum 方法体包含 $this->success('查询成功', $result) 响应
- missingNum 方法体包含 $this->error(...) 错误响应
</acceptance_criteria>
grep -c "missingNum|noNeedRight" application/admin/controller/History.php | awk '$1 >= 2'
History 控制器有 missingNum() 方法,接受 AJAX GET 请求,校验 periods 参数 1-100,调用模型 getMissingNumbers() 返回标准 JSON 响应
当前内容:
return [
'Expect' => '期号',
'OpenTime' => '时间',
'Num7' => '特码',
];
修改为:
return [
'Expect' => '期号',
'OpenTime' => '时间',
'Num7' => '特码',
'Missing Number Analysis' => '遗漏号码分析',
'Query Periods' => '查询期数',
'Missing' => '遗漏',
'periods' => '期',
'No missing numbers found' => '最近所有号码均出现过',
'Query failed' => '查询失败',
'Loading' => '查询中...',
];
保持与现有格式一致:单引号包裹 key 和 value,逗号结尾,缩进 4 空格。
<acceptance_criteria>
- application/admin/lang/zh-cn/history.php 包含键 'Missing Number Analysis',值为 '遗漏号码分析'
- application/admin/lang/zh-cn/history.php 包含键 'Query Periods',值为 '查询期数'
- application/admin/lang/zh-cn/history.php 包含键 'Missing',值为 '遗漏'
- application/admin/lang/zh-cn/history.php 包含键 'periods',值为 '期'
- 文件格式保持 return [ ... ]; 结构,使用单引号
</acceptance_criteria>
grep -c "Missing Number Analysis|Query Periods|Missing" application/admin/lang/zh-cn/history.php | awk '$1 >= 3'
语言文件包含遗漏号码相关的所有中文翻译键值对
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Browser → Controller (AJAX GET) | 用户输入 periods 参数,可能注入非数值 |
| Controller → Model | 内部调用,信任已建立 |
| Model → Database (fa_history, fa_num) | ORM 查询,无原始 SQL 拼接 |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-01-01 | Tampering | History::missingNum() - periods 参数 | mitigate | 使用 $this->request->get('periods', 10, 'intval') 强制类型转换 + 范围校验 `$periods < 1 |
| T-01-02 | Tampering | History::getMissingNumbers() - SQL 查询 | mitigate | 使用 ThinkPHP ORM Db::name() 参数化查询,无原始 SQL 字符串拼接 |
| T-01-03 | Elevation of Privilege | History::missingNum() 权限绕过 | mitigate | 使用 $noNeedRight = ['missingNum'](非 $noNeedLogin),admin 登录仍然必需,仅跳过权限规则检查 |
| T-01-04 | Denial of Service | 超大 periods 值导致查询慢 | mitigate | 参数上限 100,模型层 LIMIT 500 查询历史,确保 O(500*7) 时间复杂度可接受 |
| </threat_model> |
<success_criteria>
- History 模型有 getMissingNumbers() 和 calcOmitCount() 方法
- History 控制器有 missingNum() AJAX 端点,带 periods 参数校验
- 请求返回标准 FastAdmin 格式 {code: 1, msg: '查询成功', data: [{num, omit, color}, ...]}
- 结果按遗漏期数 omit 从大到小排序
- 波色映射从 fa_num 表获取,缺失时显示 '—'
- 语言文件包含遗漏号码相关 i18n 键值对
- missingNum 方法仅需 admin 登录即可访问($noNeedRight 非 $noNeedLogin) </success_criteria>