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));
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);
+}