From f5b804fb57dfe9966a64a031cca2c87e9264a10f Mon Sep 17 00:00:00 2001 From: wikiboss Date: Thu, 17 Sep 2026 16:37:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(commission):=20=E4=BF=AE=E5=A4=8D=E5=88=86?= =?UTF-8?q?=E9=94=80=E5=95=86=E8=BA=AB=E4=BB=BD=E7=AD=89=E7=BA=A7=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E5=BC=82=E5=B8=B8=E5=B9=B6=E5=81=A5=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E7=BC=93=E5=AD=98=E6=9D=83=E9=99=90=E4=BF=9D=E9=9A=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/shopro/commission/Level.php | 81 +++--------- .../common/view/tpl/think_exception.tpl | 7 - scripts/deploy.sh | 1 + tests/verify_commission_level_spec.js | 124 ++++++++++++++++++ 4 files changed, 143 insertions(+), 70 deletions(-) create mode 100644 tests/verify_commission_level_spec.js diff --git a/application/admin/controller/shopro/commission/Level.php b/application/admin/controller/shopro/commission/Level.php index 842abe7..42edee6 100644 --- a/application/admin/controller/shopro/commission/Level.php +++ b/application/admin/controller/shopro/commission/Level.php @@ -9,35 +9,14 @@ use think\Db; class Level extends Common { - protected $noNeedLogin = ['index', 'select']; protected $noNeedRight = ['index', 'select']; protected $model = null; public function _initialize() { - try { - if (input('test_admin')) { - $admin = \app\admin\model\Admin::get(1); - if ($admin) { - \think\Session::set('admin', $admin->toArray()); - } - } - parent::_initialize(); - $this->model = new LevelModel(); - } catch (\think\exception\HttpResponseException $e) { - throw $e; - } catch (\Throwable $e) { - header('Content-Type: application/json; charset=utf-8'); - echo json_encode([ - 'code' => 500, - 'msg' => 'INIT_ERROR: ' . $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'trace' => explode("\n", $e->getTraceAsString()) - ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); - exit; - } + parent::_initialize(); + $this->model = new LevelModel(); } /** @@ -45,49 +24,25 @@ class Level extends Common */ public function index() { - if (input('read_err_log')) { - $rules = Db::name('auth_rule')->where('name', 'like', '%commission/level%')->select(); - $admins = Db::name('admin')->field('id,username,nickname,status')->select(); - echo json_encode([ - 'rules' => $rules, - 'admins' => $admins, - ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); - exit; + if (!$this->request->isAjax()) { + return $this->view->fetch(); } - try { - if (!$this->request->isAjax()) { - return $this->view->fetch(); - } - - $defaultLevel = $this->model->find(1); - if (!$defaultLevel) { - $this->model->save([ - 'name' => '默认等级', - 'level' => 1, - 'commission_rules' => [ - 'commission_1' => '0.00', - 'commission_2' => '0.00', - 'commission_3' => '0.00' - ] - ]); - } - $list = $this->model->sheepFilter()->select(); - - $this->success('全部等级', null, $list); - } catch (\think\exception\HttpResponseException $e) { - throw $e; - } catch (\Throwable $e) { - header('Content-Type: application/json; charset=utf-8'); - echo json_encode([ - 'code' => 500, - 'msg' => 'INDEX_ERROR: ' . $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'trace' => explode("\n", $e->getTraceAsString()) - ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); - exit; + $defaultLevel = $this->model->find(1); + if (!$defaultLevel) { + $this->model->save([ + 'name' => '默认等级', + 'level' => 1, + 'commission_rules' => [ + 'commission_1' => '0.00', + 'commission_2' => '0.00', + 'commission_3' => '0.00' + ] + ]); } + $list = $this->model->sheepFilter()->select(); + + $this->success('全部等级', null, $list); } /** diff --git a/application/common/view/tpl/think_exception.tpl b/application/common/view/tpl/think_exception.tpl index 232f353..33967c9 100755 --- a/application/common/view/tpl/think_exception.tpl +++ b/application/common/view/tpl/think_exception.tpl @@ -83,13 +83,6 @@ $langSet == 'en' && $lang = array_combine(array_keys($lang), array_keys($lang));
- -
- Exception: getMessage())?>
- File: getFile())?>:getLine()?>
-
getTraceAsString())?>
-
-
diff --git a/scripts/deploy.sh b/scripts/deploy.sh index f642817..064b3b6 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -144,6 +144,7 @@ fi log "清理运行时缓存..." rm -rf "${PROJECT_DIR}/runtime/cache/"* 2>/dev/null || true rm -rf "${PROJECT_DIR}/runtime/temp/"* 2>/dev/null || true +mkdir -p "${PROJECT_DIR}/runtime/cache" "${PROJECT_DIR}/runtime/temp" "${PROJECT_DIR}/runtime/log" chown -R ${WEB_USER}:${WEB_USER} "${PROJECT_DIR}/runtime" 2>/dev/null || true chmod -R 777 "${PROJECT_DIR}/runtime" 2>/dev/null || true diff --git a/tests/verify_commission_level_spec.js b/tests/verify_commission_level_spec.js new file mode 100644 index 0000000..4158851 --- /dev/null +++ b/tests/verify_commission_level_spec.js @@ -0,0 +1,124 @@ +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +let passed = 0; +let failed = 0; + +function assert(condition, testName, detail = '') { + if (condition) { + console.log(` [PASS] ${testName}`); + passed++; + } else { + console.error(` [FAIL] ${testName} - ${detail}`); + failed++; + } +} + +console.log("========================================================"); +console.log(" 身份等级 (shopro/commission/level) 页面与权限 TDD 测试"); +console.log("========================================================\n"); + +// 1. 验证 Level.php 控制器存在与安全性 +const levelPhpPath = path.resolve(__dirname, '../application/admin/controller/shopro/commission/Level.php'); +assert(fs.existsSync(levelPhpPath), 'Level.php 控制器文件存在'); + +const levelPhpContent = fs.readFileSync(levelPhpPath, 'utf-8'); + +// 1.1 验证无临时 $noNeedLogin 遗留(后台安全保障) +assert(!levelPhpContent.includes('protected $noNeedLogin'), '无临时 $noNeedLogin 遗留(必须验证登录态)'); + +// 1.2 验证 $noNeedRight 包含 index 和 select(允许登录管理员正常进入列表) +const hasNoNeedRightIndex = /protected\s+\$noNeedRight\s*=\s*\[.*'index'.*\];/s.test(levelPhpContent); +const hasNoNeedRightSelect = /protected\s+\$noNeedRight\s*=\s*\[.*'select'.*\];/s.test(levelPhpContent); +assert(hasNoNeedRightIndex && hasNoNeedRightSelect, '$noNeedRight 必须包含 index 和 select', '缺少 index 或 select'); + +// 1.3 验证移除所有临时调试探针代码 +assert(!levelPhpContent.includes('test_admin'), '已彻底清除 test_admin 模拟登录调试代码'); +assert(!levelPhpContent.includes('read_err_log'), '已彻底清除 read_err_log 日志探测代码'); +assert(!levelPhpContent.includes('INIT_ERROR'), '已彻底清除临时 INIT_ERROR 诊断代码'); +assert(!levelPhpContent.includes('INDEX_ERROR'), '已彻底清除临时 INDEX_ERROR 诊断代码'); + +// 1.4 验证核心方法完整性 +const requiredMethods = ['_initialize', 'index', 'add', 'edit', 'detail', 'delete', 'select']; +requiredMethods.forEach(method => { + assert(levelPhpContent.includes(`public function ${method}`), `包含动作方法: ${method}`); +}); + +// 2. 验证前端 JS (level.js) 语法与逻辑完整性 +const levelJsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/commission/level.js'); +assert(fs.existsSync(levelJsPath), 'level.js 前端脚本存在'); + +const levelJsContent = fs.readFileSync(levelJsPath, 'utf-8'); + +let jsSyntaxOk = false; +let jsSyntaxError = ''; +try { + new vm.Script(levelJsContent, { filename: 'level.js' }); + jsSyntaxOk = true; +} catch (e) { + jsSyntaxError = e.message; +} +assert(jsSyntaxOk, 'level.js 语法无 SyntaxError', jsSyntaxError); + +// 2.1 模拟执行并检查 Controller 结构 +const dummyDefine = function (deps, factory) { + const $ = () => ({}); + const Backend = { api: {} }; + const Table = {}; + const Form = {}; + return factory($, undefined, Backend, Table, Form); +}; + +let moduleLoaded = false; +let controllerObj = null; +try { + const sandbox = { + define: dummyDefine, + console: console, + location: { search: '?ref=addtabs' }, + window: {}, + Fast: { api: { ajax: () => {}, open: () => {}, fixurl: (u) => u } }, + Vue: { + ref: (v) => ({ value: v }), + reactive: (o) => o, + onMounted: (cb) => cb(), + }, + }; + vm.createContext(sandbox); + controllerObj = vm.runInContext(levelJsContent, sandbox); + moduleLoaded = !!(controllerObj && controllerObj.index); +} catch (e) { + console.error("level.js 模拟加载异常:", e.message); +} +assert(moduleLoaded, 'level.js 包含 Controller.index 动作'); + +// 3. 验证视图模板存在且无语法残缺 +const levelHtmlPath = path.resolve(__dirname, '../application/admin/view/shopro/commission/level/index.html'); +assert(fs.existsSync(levelHtmlPath), 'index.html 视图模板文件存在'); +const levelHtmlContent = fs.readFileSync(levelHtmlPath, 'utf-8'); +assert(levelHtmlContent.includes('身份等级'), '模板包含身份等级标题'); +assert(levelHtmlContent.includes('commission_rules?.commission_1'), '模板具备佣金比例安全可选链展示'); +assert(levelHtmlContent.includes('state.makerBonus'), '模板具备创客进阶基数展示'); + +// 4. 验证 deploy.sh 对 runtime 目录的权限保障 +const deployShPath = path.resolve(__dirname, '../scripts/deploy.sh'); +assert(fs.existsSync(deployShPath), 'deploy.sh 部署脚本存在'); +const deployShContent = fs.readFileSync(deployShPath, 'utf-8'); +assert(deployShContent.includes('chown -R ${WEB_USER}:${WEB_USER} "${PROJECT_DIR}/runtime"'), '部署脚本包含 runtime 目录所有权赋予 www 用户'); +assert(deployShContent.includes('chmod -R 777 "${PROJECT_DIR}/runtime"') || deployShContent.includes('chmod -R 775 "${PROJECT_DIR}/runtime"'), '部署脚本包含 runtime 目录写权限'); + +// 5. 验证生产异常模板 think_exception.tpl 干净无调试代码泄露 +const exceptionTplPath = path.resolve(__dirname, '../application/common/view/tpl/think_exception.tpl'); +assert(fs.existsSync(exceptionTplPath), 'think_exception.tpl 存在'); +const exceptionTplContent = fs.readFileSync(exceptionTplPath, 'utf-8'); +assert(!exceptionTplContent.includes('$exception instanceof \\Throwable'), 'think_exception.tpl 已恢复干净,无临时注入调试代码'); + +console.log(`\n测试统计: 通过: ${passed}, 失败: ${failed}`); +if (failed > 0) { + console.error(" 自动化测试未全部通过,需要继续修复!"); + process.exit(1); +} else { + console.log(" 全部自动化测试通过!"); + process.exit(0); +}