1
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** MVC (Model-View-Controller) with module-based multi-application architecture, built on ThinkPHP 5.x framework and FastAdmin 1.6.1 admin framework.
|
||||
|
||||
**Key Characteristics:**
|
||||
- **Multi-module structure**: `admin` (backend management), `index` (frontend user portal), `api` (RESTful API), `common` (shared code)
|
||||
- **Trait-based CRUD inheritance**: Backend controllers inherit `index/add/edit/del/multi/recyclebin/destroy/restore` from `app\admin\library\traits\Backend`
|
||||
- **RBAC (Role-Based Access Control)**: Admin permissions via admin → auth_group → auth_group_access → auth_rule chain; user permissions via user → user_group → user_rule chain
|
||||
- **Addon/plugin architecture**: Extensible system via `addons/` directory with lifecycle hooks
|
||||
- **Dual Auth systems**: `app\admin\library\Auth` (session-based admin auth) and `app\common\library\Auth` (token-based user auth)
|
||||
|
||||
## Modules
|
||||
|
||||
The application is organized into 4 ThinkPHP modules under `application/`:
|
||||
|
||||
### Admin Module (`application/admin/`)
|
||||
- Purpose: Backend management panel for administrators
|
||||
- Contains: Admin CRUD controllers, RBAC management, system config, user management, lottery data management (History, Num), command execution interface
|
||||
- Entry: `public/index.php` → module `admin`
|
||||
- Controllers: 18 controllers across 4 subdirectories
|
||||
|
||||
### Index Module (`application/index/`)
|
||||
- Purpose: Frontend user-facing website
|
||||
- Contains: Public homepage, user login/register/profile, ajax endpoints, lottery data scraping
|
||||
- Controllers: `Index.php` (lottery scraping + homepage), `User.php` (member center), `Ajax.php` (frontend async operations)
|
||||
|
||||
### API Module (`application/api/`)
|
||||
- Purpose: RESTful API endpoints for mobile/third-party clients
|
||||
- Contains: User auth, registration, profile, token management, SMS/EMS verification
|
||||
- Controllers: `Index.php`, `User.php`, `Common.php`, `Demo.php`, `Ems.php`, `Sms.php`, `Token.php`, `Validate.php`
|
||||
|
||||
### Common Module (`application/common/`)
|
||||
- Purpose: Shared code across all modules
|
||||
- Contains: Base controllers, shared models, libraries (Auth, Upload, Email, Token drivers), exceptions
|
||||
|
||||
## Controller Inheritance Chain
|
||||
|
||||
### Backend (Admin Controllers)
|
||||
```
|
||||
think\Controller (ThinkPHP base)
|
||||
└── app\common\controller\Backend (base backend controller)
|
||||
└── app\admin\library\traits\Backend (trait: CRUD methods)
|
||||
└── app\admin\controller\* (all admin controllers)
|
||||
```
|
||||
|
||||
**`app\common\controller\Backend`** (`application/common/controller/Backend.php`):
|
||||
- Properties: `$noNeedLogin`, `$noNeedRight`, `$layout`, `$auth`, `$model`, `$searchFields`, `$relationSearch`, `$dataLimit`, `$dataLimitField`, `$dataLimitFieldAutoFill`, `$modelValidate`, `$modelSceneValidate`, `$multiFields`, `$selectpageFields`, `$excludeFields`, `$importHeadType`
|
||||
- `_initialize()`: IP check → Auth instance → login verification → permission check → breadcrumb setup → layout → language loading → view config assignment
|
||||
- `buildparams()`: Constructs WHERE conditions from GET params (search, filter, op, sort, order, offset, limit) with support for LIKE, IN, BETWEEN, RANGE, FIND_IN_SET, NULL operators
|
||||
- `selectpage()`: Universal select/dropdown search with tree support
|
||||
- `loadlang()`: Loads language file for current controller
|
||||
- `assignconfig()`: Merges config into view
|
||||
- `getDataLimitAdminIds()`: Returns admin IDs for data scoping based on `$dataLimit` setting
|
||||
|
||||
**`app\admin\library\traits\Backend`** (`application/admin/library/traits/Backend.php`):
|
||||
- `index()`: List with pagination, JSON response for AJAX
|
||||
- `add()`: Create with validation, transaction support
|
||||
- `edit()`: Update with validation, data limit check
|
||||
- `del()`: Soft delete (batch supported)
|
||||
- `recyclebin()`: View soft-deleted records
|
||||
- `destroy()`: Permanent delete from recycle bin
|
||||
- `restore()`: Restore from recycle bin
|
||||
- `multi()`: Batch update specified fields
|
||||
- `import()`: Excel/CSV import with PhpSpreadsheet
|
||||
|
||||
### Frontend (Index Controllers)
|
||||
```
|
||||
think\Controller (ThinkPHP base)
|
||||
└── app\common\controller\Frontend (base frontend controller)
|
||||
└── app\index\controller\* (all frontend controllers)
|
||||
```
|
||||
|
||||
**`app\common\controller\Frontend`** (`application/common/controller/Frontend.php`):
|
||||
- `_initialize()`: Input filtering → IP check → layout → Auth init → token-based login check → user assignment → site config → language loading
|
||||
- Auth uses `app\common\library\Auth` (token-based)
|
||||
- `loadlang()`: Loads language file for current controller
|
||||
- `assignconfig()`: Merges config into view
|
||||
|
||||
### API Controllers
|
||||
```
|
||||
(no ThinkPHP Controller base)
|
||||
└── app\common\controller\Api (base API controller, not extending think\Controller)
|
||||
└── app\api\controller\* (all API controllers)
|
||||
```
|
||||
|
||||
**`app\common\controller\Api`** (`application/common/controller/Api.php`):
|
||||
- Does NOT extend `think\Controller` — manual constructor pattern
|
||||
- Properties: `$noNeedLogin`, `$noNeedRight`, `$auth`, `$responseType`, `$failException`, `$batchValidate`
|
||||
- `_initialize()`: CORS check → IP check → input filtering → token auth → permission check → language loading
|
||||
- `success()` / `error()`: Standard API response format `{code, msg, time, data}`
|
||||
- `result()`: Unified response with HTTP status code mapping
|
||||
- `validate()`: Data validation with fail-exception mode
|
||||
- `beforeAction()`: Before-action hook support
|
||||
|
||||
## RBAC Auth System
|
||||
|
||||
### Admin RBAC
|
||||
```
|
||||
fa_admin (管理员)
|
||||
└── fa_auth_group_access (管理员-角色关联, N:N)
|
||||
└── fa_auth_group (角色组)
|
||||
└── rules (权限规则ID列表, 逗号分隔)
|
||||
└── fa_auth_rule (权限规则)
|
||||
```
|
||||
|
||||
**Key classes:**
|
||||
- `app\admin\library\Auth` extends `fast\Auth` — admin authentication and authorization
|
||||
- `app\admin\model\Admin` — admin user model
|
||||
- `app\admin\model\AuthGroup` — role group model
|
||||
- `app\admin\model\AuthGroupAccess` — admin-role pivot model
|
||||
- `app\admin\model\AuthRule` — permission rule model
|
||||
- `app\admin\model\AdminLog` — admin operation log model
|
||||
|
||||
**Auth flow:**
|
||||
1. Login: `Admin::login()` → verifies password (MD5 double-hash with salt: `md5(md5(password) . salt)`) → sets Session + token + keeplogin cookie
|
||||
2. Permission check: `Auth::check($path)` → reads user's group rules → checks if controller/action path is in allowed rules
|
||||
3. Super admin: `Auth::isSuperAdmin()` → returns true if rule list contains `*`
|
||||
4. Data scoping: `Backend::$dataLimit` supports `auth` (group-scoped) and `personal` (user-scoped) data filtering via `getDataLimitAdminIds()`
|
||||
5. Session management: `safecode` validation ensures re-login on credential changes; supports single-session mode (`fastadmin.login_unique`) and IP check mode (`fastadmin.loginip_check`)
|
||||
6. Auto-login: `Auth::autologin()` checks `keeplogin` cookie with time-limited key validation
|
||||
|
||||
**Key Auth methods:**
|
||||
- `login($username, $password, $keeptime)` — admin login
|
||||
- `logout()` — clear session and token
|
||||
- `check($name, $uid, $relation, $mode)` — permission check
|
||||
- `match($arr)` — check if current action matches whitelist
|
||||
- `isLogin()` — session + safecode validation
|
||||
- `isSuperAdmin()` — check for `*` in rules
|
||||
- `getSidebar($params, $fixedPage)` — generate left/top navigation menu
|
||||
- `getBreadCrumb($path)` — breadcrumb navigation
|
||||
- `getChildrenAdminIds($withself)` — scoped admin IDs
|
||||
- `getChildrenGroupIds($withself)` — scoped group IDs
|
||||
|
||||
### User RBAC (Frontend)
|
||||
```
|
||||
fa_user (会员)
|
||||
└── group_id → fa_user_group (会员组)
|
||||
└── rules → fa_user_rule (会员权限规则)
|
||||
```
|
||||
|
||||
**Key classes:**
|
||||
- `app\common\library\Auth` — user authentication (token-based, singleton pattern)
|
||||
- `app\common\model\User` — user model with money/score log hooks
|
||||
- `app\common\model\UserGroup` — user group model
|
||||
- `app\common\model\UserRule` — user rule model
|
||||
- Token storage: `app\common\library\Token` with MySQL or Redis drivers
|
||||
|
||||
**Auth flow:**
|
||||
1. Token init: `Auth::init($token)` → Token::get() → load user → set `_logined` flag
|
||||
2. Login: `Auth::login($account, $password)` → finds user by email/mobile/username → verifies password → `direct($user_id)`
|
||||
3. Token management: UUID token stored in Token model (or Redis) with configurable TTL (`keeptime = 2592000` = 30 days)
|
||||
4. Password encryption: `md5(md5(password) . salt)` — same algorithm as admin
|
||||
|
||||
**Key Auth methods:**
|
||||
- `instance($options)` — singleton getter
|
||||
- `init($token)` — initialize from token
|
||||
- `login($account, $password)` — user login
|
||||
- `register($username, $password, $email, $mobile, $extend)` — user registration
|
||||
- `logout()` — delete token
|
||||
- `changepwd($newpassword, $oldpassword, $ignoreoldpassword)` — change password
|
||||
- `direct($user_id)` — direct login (bypass password check)
|
||||
- `check($path, $module)` — permission check
|
||||
- `delete($user_id)` — delete user and clear tokens
|
||||
|
||||
## MVC Patterns
|
||||
|
||||
### Model Pattern
|
||||
- All models extend `think\Model`
|
||||
- Timestamps: `autoWriteTimestamp = 'int'` with custom field names (`createtime`, `updatetime`)
|
||||
- Attribute mutators: `getXxxAttr()` and `setXxxAttr()` for data transformation
|
||||
- Model events: `self::init()` with `self::beforeWrite`, `self::afterInsert` hooks
|
||||
- Relations: `belongsTo`, `hasMany` using ThinkPHP ORM
|
||||
- Appended attributes: `$append = ['field_text']` for computed/display fields
|
||||
- Enum lists: `getStatusList()`, `getGenderList()` for select/radio options
|
||||
- Hidden fields: `$hidden = ['password', 'salt']` for sensitive data
|
||||
|
||||
### View Pattern
|
||||
- Template engine: ThinkPHP template with `{}` syntax
|
||||
- Layout: `application/admin/view/layout/default.html` includes `common/meta`, `common/script`, `common/header`, `common/menu`
|
||||
- Admin views: `build_heading()` generates toolbar/buttons, `{:__()}` for i18n
|
||||
- View config injection via `$this->view->assign()` and `$this->assignconfig()`
|
||||
- Layout template set via `$this->layout` property in controllers
|
||||
- Frontend layout: `application/index/view/layout/default.html`
|
||||
|
||||
### Validation Pattern
|
||||
- Validators extend `think\Validate`
|
||||
- Rules defined in `$rule` array (require, unique, regex, email, length, etc.)
|
||||
- Scenes: `$scene = ['add' => [...], 'edit' => [...]]` for context-specific validation
|
||||
- Constructor customization for i18n field labels
|
||||
- Model-level validation: `Backend::$modelValidate` and `$modelSceneValidate` toggle automatic validation on add/edit
|
||||
|
||||
## Addon Architecture
|
||||
|
||||
**Directory:** `addons/`
|
||||
|
||||
**Plugin lifecycle:**
|
||||
- Install: `Service::install($name)` → downloads, extracts, runs SQL
|
||||
- Uninstall: `Service::uninstall($name)` → removes files, optionally drops tables
|
||||
- Enable/Disable: `Service::enable($name)` / `Service::disable($name)`
|
||||
- Upgrade: `Service::upgrade($name)` → downloads new version, runs migration
|
||||
- Configure: `get_addon_config($name)` / `set_addon_fullconfig($name, $config)`
|
||||
|
||||
**Addon structure:**
|
||||
```
|
||||
addons/{name}/
|
||||
├── info.ini # Plugin metadata
|
||||
├── {Name}.php # Main class extends \think\addons\Addon
|
||||
├── config.php # Plugin configuration schema
|
||||
├── config.html # Config view (optional, custom config UI)
|
||||
├── controller/ # Plugin controllers
|
||||
├── model/ # Plugin models
|
||||
├── view/ # Plugin views
|
||||
└── install.sql # Installation SQL
|
||||
```
|
||||
|
||||
**Admin addon controller** (`application/admin/controller/Addon.php`):
|
||||
- Manages plugin list, install/uninstall, enable/disable, config, upgrade
|
||||
- Restricted to super admin for destructive operations (install, uninstall, local, upgrade, authorization, testdata)
|
||||
- Communicates with FastAdmin marketplace API (`fastadmin.api_url`)
|
||||
- Methods: `index()`, `config($name)`, `install()`, `uninstall()`, `state()`, `local()`, `upgrade()`, `testdata()`, `downloaded()`, `isbuy()`, `authorization()`, `get_table_list()`
|
||||
|
||||
**Admin commands for addons:**
|
||||
- `application/admin/command/Crud.php` — one-click CRUD generation from table
|
||||
- `application/admin/command/Menu.php` — menu generation from controllers
|
||||
- `application/admin/command/Min.php` — JS/CSS minification
|
||||
- `application/admin/command/Api.php` — API documentation generation
|
||||
- `application/admin/command/Install.php` — installation wizard
|
||||
- `application/admin/command/Addon.php` — addon-related stubs and operations
|
||||
|
||||
**Hook system:** ThinkPHP Hook integration
|
||||
- `admin_login_after`, `admin_logout_after`, `admin_nologin`, `admin_nopermission`
|
||||
- `user_login_successed`, `user_register_successed`, `user_logout_successed`, `user_delete_successed`
|
||||
- `upload_config_init`, `config_init`, `wipecache_after`
|
||||
- `upload_delete`, `admin_sidebar_begin`
|
||||
- `admin_login_init`
|
||||
|
||||
## Request Flow
|
||||
|
||||
### Admin Request
|
||||
```
|
||||
public/index.php
|
||||
→ define APP_PATH → check install.lock → require thinkphp/start.php
|
||||
→ ThinkPHP dispatch (module/controller/action)
|
||||
→ app\admin\controller\{Controller}
|
||||
→ parent::_initialize() (app\common\controller\Backend)
|
||||
→ check_ip_allowed()
|
||||
→ Auth::instance() → login check → permission check
|
||||
→ loadlang() → view config assignment
|
||||
→ action method (index/add/edit/del from trait or custom)
|
||||
→ model operations
|
||||
→ $this->view->fetch() or json()
|
||||
```
|
||||
|
||||
### Frontend Request
|
||||
```
|
||||
public/index.php
|
||||
→ ThinkPHP dispatch
|
||||
→ app\index\controller\{Controller}
|
||||
→ parent::_initialize() (app\common\controller\Frontend)
|
||||
→ input filter → Auth init (token-based)
|
||||
→ loadlang() → view config
|
||||
→ action method → view->fetch()
|
||||
```
|
||||
|
||||
### API Request
|
||||
```
|
||||
public/index.php
|
||||
→ ThinkPHP dispatch
|
||||
→ app\api\controller\{Controller}
|
||||
→ __construct() → _initialize() (app\common\controller\Api)
|
||||
→ CORS → IP check → token init → permission check
|
||||
→ action method → $this->success() / $this->error()
|
||||
→ result() → JSON response {code, msg, time, data}
|
||||
```
|
||||
|
||||
## Data Flow: Lottery Feature (History, Num)
|
||||
|
||||
### History Model
|
||||
- Table: `fa_history` (`application/admin/model/History.php`)
|
||||
- Fields: `id`, `expect` (期号), `openTime` (开奖时间), `num1`~`num7` (7个开奖号码)
|
||||
- No timestamps: `autoWriteTimestamp = false`, `createTime = false`, `updateTime = false`, `deleteTime = false`
|
||||
- No validation rules defined in `application/admin/validate/History.php`
|
||||
|
||||
### Num Model
|
||||
- Table: `fa_num` (`application/admin/model/Num.php`)
|
||||
- Fields: `num`, `color` (波色映射 — wave color mapping for lottery numbers)
|
||||
- No timestamps: `autoWriteTimestamp = false`
|
||||
|
||||
### Data Scraping Flow (`application/index/controller/Index.php::get_history()`)
|
||||
1. User visits `index/index/get_history` (no auth required: `$noNeedLogin = '*'`)
|
||||
2. Controller creates `\GuzzleHttp\Client` instance
|
||||
3. GET request to `https://history.macaumarksix.com/history/macaujc2/y/2026` (Macau lottery history API)
|
||||
4. Parses JSON response, extracts `data` array
|
||||
5. For each item: splits `openCode` by comma into `num1`~`num7`
|
||||
6. Checks if `expect` already exists in `fa_history` via `Db::name('history')->where('expect', $item['expect'])->find()`
|
||||
7. If new: `Db::name('history')->insert($insert_data)`; if exists: `Db::name('history')->where('expect', $item['expect'])->update($insert_data)`
|
||||
8. Returns success/failure JSON via `$this->success()` / `$this->error()`
|
||||
|
||||
### Admin History Management (`application/admin/controller/History.php`)
|
||||
- Extends `Backend`, uses standard CRUD from trait
|
||||
- Model: `app\admin\model\History`
|
||||
- View: `application/admin/view/history/index.html` — list only, add/edit buttons hidden in template
|
||||
- No custom methods — relies entirely on inherited CRUD
|
||||
|
||||
### Admin Num Query (`application/admin/controller/Num.php`)
|
||||
- Extends `Backend`
|
||||
- Model: `app\admin\model\Num`
|
||||
- Custom method: `getColorMap()` — returns num→color mapping for frontend display as JSON
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
### Auth (Dual System)
|
||||
- `app\admin\library\Auth` — session-based admin auth extending `fast\Auth` (base RBAC class from `fast` namespace)
|
||||
- `app\common\library\Auth` — token-based user auth (singleton), no ThinkPHP Controller dependency
|
||||
|
||||
### Tree
|
||||
- `fast\Tree` — hierarchical data structure for menus, categories, rules
|
||||
- Methods: `init()`, `getTreeList()`, `getTreeArray()`, `getChildrenIds()`, `getParentsIds()`, `getTreeMenu()`, `getChildren()`
|
||||
|
||||
### Token Drivers
|
||||
- `app\common\library\Token` — token storage abstraction
|
||||
- `app\common\library\token\driver\Mysql` — MySQL-backed token storage
|
||||
- `app\common\library\token\driver\Redis` — Redis-backed token storage
|
||||
|
||||
### Upload
|
||||
- `app\common\library\Upload` — file upload with chunked upload support
|
||||
- Integrates with `app\common\model\Attachment` for metadata tracking
|
||||
- Exception: `app\common\exception\UploadException`
|
||||
|
||||
### Date Utility
|
||||
- `fast\Date` — date manipulation utility (e.g., `Date::unixtime('day', -6)`)
|
||||
|
||||
### Random Utility
|
||||
- `fast\Random` — random string/UUID generation (used for salt, tokens)
|
||||
|
||||
## Entry Points
|
||||
|
||||
**`public/index.php`** — Main entry point for all modules
|
||||
- Defines `APP_PATH`
|
||||
- Checks `application/admin/command/Install/install.lock` for installation status
|
||||
- Redirects to `install.php` if not installed
|
||||
- Loads `thinkphp/start.php` for framework bootstrap
|
||||
|
||||
**`think`** — CLI entry point for ThinkPHP commands
|
||||
- Used for CRUD generation, menu generation, API docs, minification, addon management
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Strategy:** Exception-based with user-friendly error pages
|
||||
|
||||
**Patterns:**
|
||||
- `$this->error($msg)` / `$this->success($msg)` — controller-level response helpers
|
||||
- `try/catch` with `Db::startTrans()` / `Db::commit()` / `Db::rollback()` for transaction safety
|
||||
- `ValidateException` — model validation failures
|
||||
- `PDOException` — database errors
|
||||
- `UploadException` — upload failures
|
||||
- `AddonException` — plugin operation failures with structured error data
|
||||
- `HttpResponseException` — API response termination
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
**Logging:** `AdminLog::record()` auto-logs admin operations (title, content, URL, IP, user agent); filtered by `ignoreRegex` to skip selectpage/index actions; `AdminLog::setTitle()` for custom titles; behavior hook via `app\admin\behavior\AdminLog`
|
||||
|
||||
**Validation:** ThinkPHP Validate with rule-based validation, scene support, custom messages; model-level and controller-level validation modes
|
||||
|
||||
**Authentication:** Dual auth system — session-based for admin, token-based for users/API; IP allowlist check via `check_ip_allowed()`; CSRF tokens for forms
|
||||
|
||||
**Internationalization:** Language files per module under `lang/zh-cn/` and `lang/en/`; `__()` function for translation; controller auto-loads matching lang file in `_initialize()`; admin loads `zh-cn` as default
|
||||
|
||||
**IP Filtering:** `check_ip_allowed()` called in all base controllers — reads `fastadmin.ip_blacklist` / `fastadmin.ip_whitelist` config
|
||||
|
||||
**Caching:** File-based cache (ThinkPHP default); menu cache via `cache("__menu__")`; template caching enabled; addon list cache via `Cache::get("onlineaddons")`
|
||||
|
||||
---
|
||||
|
||||
*Architecture analysis: 2026-04-21*
|
||||
@@ -0,0 +1,630 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### S1. Password Hashing Uses Weak Double-MD5 (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `application/common/library/Auth.php` line 490-492
|
||||
- `application/admin/library/Auth.php` line 146-148
|
||||
|
||||
**Issue:** Both frontend and admin password hashing use double-MD5 with salt:
|
||||
```php
|
||||
// application/common/library/Auth.php:490-492
|
||||
public function getEncryptPassword($password, $salt = '')
|
||||
{
|
||||
return md5(md5($password) . $salt);
|
||||
}
|
||||
```
|
||||
MD5 is cryptographically broken. The inner hash is unsalted, making rainbow table attacks viable. MD5 can be brute-forced at billions of hashes per second on commodity hardware. The salt is only 6 characters (`Random::alnum()` typically produces short strings).
|
||||
|
||||
**Impact:** If the database is compromised, all user passwords can be cracked rapidly.
|
||||
|
||||
**Fix approach:** Migrate to `password_hash()` with `PASSWORD_BCRYPT` or `PASSWORD_ARGON2ID`. Add a migration script to re-hash passwords on next successful login.
|
||||
|
||||
---
|
||||
|
||||
### S2. SQL Injection via Raw Queries in Admin Controllers (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `application/admin/command/Crud.php` lines 440, 444, 462, 466
|
||||
- `application/admin/controller/general/Config.php` line 293
|
||||
- `application/admin/controller/Dashboard.php` line 47
|
||||
- `application/admin/controller/Command.php` line 39
|
||||
- `extend/fast/Auth.php` line 160
|
||||
|
||||
**Issue:** Multiple raw SQL queries interpolate variables without parameterization:
|
||||
```php
|
||||
// application/admin/command/Crud.php:440
|
||||
$modelTableInfo = $dbconnect->query("SHOW TABLE STATUS LIKE '{$modelTableName}'", [], true);
|
||||
|
||||
// application/admin/controller/general/Config.php:293
|
||||
$tableList = \think\Db::query("SELECT `TABLE_NAME` AS `name`,`TABLE_COMMENT` AS `title` FROM `information_schema`.`TABLES` where `TABLE_SCHEMA` = '{$dbname}';");
|
||||
|
||||
// extend/fast/Auth.php:160
|
||||
->where("aga.uid='{$uid}' and ag.status='normal'")
|
||||
```
|
||||
The `Crud.php` command receives table names via CLI arguments (`--table=`) which could be manipulated. The `Auth.php` interpolates `$uid` directly into the WHERE clause.
|
||||
|
||||
**Impact:** An attacker with admin access could inject malicious SQL through crafted table names in CRUD generation. The Auth SQL injection is more concerning if `$uid` can ever be user-controlled.
|
||||
|
||||
**Fix approach:** Use parameterized queries. Validate table names against `/^[a-z0-9_]+$/i` before embedding in SQL. Replace string interpolation in Auth.php with `->where('aga.uid', $uid)->where('ag.status', 'normal')`.
|
||||
|
||||
---
|
||||
|
||||
### S3. HTTP Method Spoofing Enabled (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` line 109
|
||||
|
||||
**Issue:** The config `'var_method' => '_method'` allows clients to spoof HTTP methods via POST parameter. An attacker can send `_method=DELETE` in a POST request to bypass CSRF protections that only check POST requests.
|
||||
|
||||
**Impact:** CSRF bypass via method override on state-changing operations.
|
||||
|
||||
**Fix approach:** Set `'var_method' => ''` to disable method spoofing, or ensure all state-changing operations require explicit CSRF token validation regardless of HTTP method.
|
||||
|
||||
---
|
||||
|
||||
### S4. Cookie Security Flags Not Set (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` lines 216-231
|
||||
|
||||
**Issue:** Session cookies lack security flags:
|
||||
```php
|
||||
'cookie' => [
|
||||
'secure' => false, // Not enforced
|
||||
'httponly' => '', // Empty string = disabled
|
||||
],
|
||||
```
|
||||
|
||||
**Impact:** Session cookies vulnerable to interception on HTTP connections and theft via XSS attacks.
|
||||
|
||||
**Fix approach:** Set `'secure' => true` (when HTTPS is available) and `'httponly' => true`.
|
||||
|
||||
---
|
||||
|
||||
### S5. Token Key Hardcoded in Config (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` line 264
|
||||
|
||||
**Issue:** The token encryption key is hardcoded directly in the config file:
|
||||
```php
|
||||
'token' => [
|
||||
'key' => '3byNV4KupeZAvl60sdr2COjDYUmqwPJW', // line 264
|
||||
],
|
||||
```
|
||||
This value is committed to git and visible to anyone with repository access. If leaked, an attacker can forge valid tokens.
|
||||
|
||||
**Impact:** Token forgery if the key is exposed.
|
||||
|
||||
**Fix approach:** Move to environment variable via `Env::get('token.key')` and regenerate the key.
|
||||
|
||||
---
|
||||
|
||||
### S6. External API Scraping Without Data Validation (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `application/index/controller/Index.php` lines 20-58
|
||||
|
||||
**Issue:** The `get_history()` method fetches lottery data from `https://history.macaumarksix.com` and writes directly to the database with zero validation:
|
||||
```php
|
||||
public function get_history()
|
||||
{
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$res = $client->request('GET', 'https://history.macaumarksix.com/history/macaujc2/y/2026');
|
||||
// ...
|
||||
foreach ($data as $item) {
|
||||
$insert_data['expect'] = $item['expect'];
|
||||
$insert_data['num1'] = $openCode[0];
|
||||
// ... direct DB insert without any validation
|
||||
Db::name('history')->insert($insert_data);
|
||||
}
|
||||
}
|
||||
```
|
||||
No validation of: response data structure, value ranges (lottery numbers should be 1-49), data types, or expected format of `openCode`. The year `2026` is hardcoded.
|
||||
|
||||
**Impact:** If the external API is compromised or returns malformed data, the database gets corrupted with invalid lottery records. The hardcoded year requires annual manual updates.
|
||||
|
||||
**Fix approach:** Add data validation (type checks, range validation, schema verification), make the year parameter configurable, and add error handling for API structure changes.
|
||||
|
||||
---
|
||||
|
||||
### S7. External Scraping Has No Reliability Safeguards (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/index/controller/Index.php`
|
||||
|
||||
**Issue:** No retry logic, timeout configuration, or circuit breaker for the external API call. No rate limiting on the `get_history` endpoint - any user can trigger the scrape repeatedly. No Guzzle timeout is configured.
|
||||
|
||||
**Impact:** External API downtime causes unhandled exceptions. Repeated calls could trigger rate limiting or IP bans on the source API.
|
||||
|
||||
**Fix approach:** Add Guzzle timeout config, implement retry logic with exponential backoff, add rate limiting to the endpoint, and cache results.
|
||||
|
||||
---
|
||||
|
||||
### S8. Login CAPTCHA Disabled by Default (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` line 277
|
||||
|
||||
**Issue:** `'login_captcha' => false` disables login CAPTCHA.
|
||||
|
||||
**Impact:** Without login CAPTCHA, the application is vulnerable to brute-force password attacks and credential stuffing. The 10-attempts-per-day lockout is the only defense.
|
||||
|
||||
**Fix approach:** Set `'login_captcha' => true` in production.
|
||||
|
||||
---
|
||||
|
||||
### S9. Upload File Type Check Relies on Client-Supplied MIME (LOW-MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/common/library/Upload.php` lines 87-98, 106-120
|
||||
|
||||
**Issue:** The upload check uses `$this->fileInfo['type']` which comes from the client's `Content-Type` header. PHP's `$_FILES['type']` is set by the browser and can be easily spoofed.
|
||||
|
||||
**Impact:** An attacker could potentially upload files with disguised MIME types if the suffix check has gaps.
|
||||
|
||||
**Fix approach:** Use `finfo_open()` / `finfo_file()` to detect actual file type from content bytes.
|
||||
|
||||
---
|
||||
|
||||
### S10. Suspicious PHP File in Public Directory (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `public/ByZjtVrKok.php` (1250 bytes, untracked in git)
|
||||
|
||||
**Issue:** A PHP file with a random-looking name exists in the public web root. Any PHP file in `public/` is directly executable via HTTP request.
|
||||
|
||||
**Impact:** This could be an unauthorized backdoor, test file, or admin script. If it contains administrative functionality, anyone who discovers the URL could execute it.
|
||||
|
||||
**Fix approach:** Audit the file contents immediately. If not needed, delete it. If it serves a purpose, move it outside the public directory.
|
||||
|
||||
---
|
||||
|
||||
### S11. No CSRF Protection on API Endpoints (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/api/controller/User.php`
|
||||
- `application/api/controller/Sms.php`
|
||||
- `application/api/controller/Ems.php`
|
||||
|
||||
**Issue:** API controller methods do not use CSRF token validation. While APIs typically use token-based auth, browser-initiated API calls from authenticated sessions are vulnerable to CSRF.
|
||||
|
||||
**Impact:** An authenticated user visiting a malicious page could have API calls triggered on their behalf (e.g., sending SMS codes, changing account settings).
|
||||
|
||||
**Fix approach:** For browser-initiated API calls that modify state, require CSRF token validation. For pure API usage, ensure token-based auth is mandatory.
|
||||
|
||||
---
|
||||
|
||||
### S12. 0777 File Permissions (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/common.php` line 121
|
||||
- `application/admin/command/Addon.php` line 271
|
||||
|
||||
**Issue:** `@chmod($file, 0777)` sets world-writable permissions on created files.
|
||||
|
||||
**Impact:** On shared hosting, any user/process can modify these files, potentially injecting malicious code.
|
||||
|
||||
**Fix approach:** Use `0755` for directories and `0644` for files.
|
||||
|
||||
---
|
||||
|
||||
### S13. Deprecated mcrypt Fallback (LOW)
|
||||
|
||||
**Files:**
|
||||
- `application/common/library/Security.php` lines 438-439
|
||||
|
||||
**Issue:** References `mcrypt_create_iv()` and `MCRYPT_DEV_URANDOM` — the mcrypt extension was removed in PHP 7.2. The project requires PHP >= 7.4.
|
||||
|
||||
**Impact:** Dead code that will never execute but creates confusion and maintenance debt.
|
||||
|
||||
**Fix approach:** Remove the mcrypt fallback. `random_bytes()` is sufficient for PHP 7.4+.
|
||||
|
||||
---
|
||||
|
||||
### S14. exec() Calls with User-Influenced Input (MEDIUM)
|
||||
|
||||
**Files:**
|
||||
- `application/admin/command/Crud.php` lines 580, 1042, 1232
|
||||
|
||||
**Issue:** Shell commands are constructed with interpolated variables:
|
||||
```php
|
||||
exec("php think menu -c {$controllerUrl} -d 1 -f 1");
|
||||
exec("php think crud -t {$relation['relationTableName']} ...");
|
||||
```
|
||||
|
||||
**Impact:** If input variables can be controlled by non-admin users, command injection is possible.
|
||||
|
||||
**Fix approach:** Use `escapeshellarg()` on all variables interpolated into shell commands.
|
||||
|
||||
---
|
||||
|
||||
## Maintainability
|
||||
|
||||
### M1. Massive CRUD Command File (1795 lines)
|
||||
|
||||
**Files:**
|
||||
- `application/admin/command/Crud.php` (1795 lines)
|
||||
|
||||
**Issue:** This single file handles table analysis, code generation for models, controllers, views, validation, language packs, menu generation, and relation handling. It violates the Single Responsibility Principle.
|
||||
|
||||
**Impact:** Difficult to maintain, test, and extend.
|
||||
|
||||
**Fix approach:** Split into separate classes: TableAnalyzer, ModelGenerator, ControllerGenerator, ViewGenerator, MenuGenerator.
|
||||
|
||||
---
|
||||
|
||||
### M2. Duplicated Code Across Base Controllers
|
||||
|
||||
**Files:**
|
||||
- `application/common/controller/Api.php` lines 318-329, 153-160
|
||||
- `application/common/controller/Backend.php` lines 600-613, 237-244
|
||||
- `application/common/controller/Frontend.php` lines 149-161, 128-135
|
||||
|
||||
**Issue:** The `token()` and `loadlang()` methods are identically duplicated across all three base controllers.
|
||||
|
||||
**Impact:** Changes must be applied in three places.
|
||||
|
||||
**Fix approach:** Move to a shared trait.
|
||||
|
||||
---
|
||||
|
||||
### M3. Tightly Coupled Backend Traits
|
||||
|
||||
**Files:**
|
||||
- `application/admin/library/traits/Backend.php` (481 lines)
|
||||
- `application/common/controller/Backend.php` (614 lines)
|
||||
|
||||
**Issue:** The Backend trait injects massive functionality (index, add, edit, del, recyclebin, restore, destroy, multi, import, selectpage) into every admin controller. The trait and base controller are deeply intertwined.
|
||||
|
||||
**Impact:** All admin controllers carry full CRUD weight. Customizing one method requires overriding the entire trait method.
|
||||
|
||||
**Fix approach:** Split into smaller, focused traits (CrudTrait, ImportTrait, RecycleBinTrait).
|
||||
|
||||
---
|
||||
|
||||
### M4. No Service Layer
|
||||
|
||||
**Files:** All controllers
|
||||
|
||||
**Issue:** Business logic is embedded directly in controllers. `application/index/controller/Index.php` handles external API fetching, data parsing, and database insertion all in one method.
|
||||
|
||||
**Impact:** Controllers are difficult to test. Logic cannot be reused across entry points.
|
||||
|
||||
**Fix approach:** Introduce a service layer (e.g., `LotteryDataService`, `UserAuthService`).
|
||||
|
||||
---
|
||||
|
||||
### M5. composer.lock Ignored in Version Control
|
||||
|
||||
**Files:**
|
||||
- `.gitignore` line 11
|
||||
|
||||
**Issue:** `composer.lock` is gitignored, meaning dependency versions are not pinned.
|
||||
|
||||
**Impact:** Different environments install different dependency versions, leading to inconsistent behavior and potential security vulnerabilities.
|
||||
|
||||
**Fix approach:** Remove `composer.lock` from `.gitignore` and commit it.
|
||||
|
||||
---
|
||||
|
||||
## Performance
|
||||
|
||||
### P1. N+1 Query Pattern in Data Scraping (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `application/index/controller/Index.php` lines 28-45
|
||||
|
||||
**Issue:** For each item from the external API, a separate SELECT + INSERT/UPDATE is executed:
|
||||
```php
|
||||
foreach ($data as $item) {
|
||||
$exist = Db::name('history')->where('expect', $item['expect'])->find();
|
||||
if (!$exist) {
|
||||
Db::name('history')->insert($insert_data);
|
||||
} else {
|
||||
Db::name('history')->where('expect', $item['expect'])->update($insert_data);
|
||||
}
|
||||
}
|
||||
```
|
||||
This generates 2N queries for N records.
|
||||
|
||||
**Impact:** For large datasets, significant database load and slow execution.
|
||||
|
||||
**Fix approach:** Use `INSERT ... ON DUPLICATE KEY UPDATE` for single-query upsert, or batch operations.
|
||||
|
||||
---
|
||||
|
||||
### P2. File-Based Cache Only
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` lines 187-197
|
||||
|
||||
**Issue:** Only file-based caching configured. Token storage defaults to MySQL.
|
||||
|
||||
**Impact:** File I/O is slower than memory-based caching, especially under concurrent access. Token validation on every request adds database load.
|
||||
|
||||
**Fix approach:** Configure Redis for caching and token storage in production.
|
||||
|
||||
---
|
||||
|
||||
### P3. Dashboard Runs Heavy Queries Without Caching
|
||||
|
||||
**Files:**
|
||||
- `application/admin/controller/Dashboard.php` lines 24-82
|
||||
|
||||
**Issue:** The dashboard executes ~10+ aggregate queries on every page load with no caching:
|
||||
```php
|
||||
$totaluser = User::count();
|
||||
$todayusersignup = User::whereTime('jointime', 'today')->count();
|
||||
$sevendau = User::whereTime('jointime|logintime|prevtime', '-7 days')->count();
|
||||
$dbTableList = Db::query("SHOW TABLE STATUS");
|
||||
```
|
||||
|
||||
**Impact:** Dashboard becomes slower as user base grows.
|
||||
|
||||
**Fix approach:** Cache dashboard statistics with a short TTL (e.g., 5 minutes).
|
||||
|
||||
---
|
||||
|
||||
### P4. No Frontend Asset Build Pipeline
|
||||
|
||||
**Files:**
|
||||
- `Gruntfile.js`
|
||||
- `public/assets/js/backend/command.js`
|
||||
- `public/assets/js/backend/history.js`
|
||||
|
||||
**Issue:** While a Gruntfile.js exists, there is no evidence of automated build pipeline. JavaScript files are loaded individually per controller.
|
||||
|
||||
**Impact:** Slow page load times due to multiple HTTP requests.
|
||||
|
||||
**Fix approach:** Implement asset bundling and minification in the deployment pipeline.
|
||||
|
||||
---
|
||||
|
||||
## Reliability
|
||||
|
||||
### R1. Silent Exception Swallowing in Financial Operations (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `application/common/model/User.php` lines 93-111, 119-137
|
||||
|
||||
**Issue:** The `money()` and `score()` methods catch exceptions but only roll back without logging or returning error:
|
||||
```php
|
||||
public static function money($money, $user_id, $memo)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
// ... money operation
|
||||
Db::commit();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
// No logging, no return value - silent failure
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** Failed money/score operations silently fail. Financial data integrity is at risk.
|
||||
|
||||
**Fix approach:** Log the exception, throw or return error indicator. Add audit trail for financial operations.
|
||||
|
||||
---
|
||||
|
||||
### R2. Empty Catch Block in Dashboard
|
||||
|
||||
**Files:**
|
||||
- `application/admin/controller/Dashboard.php` lines 26-30
|
||||
|
||||
**Issue:** Exception caught and completely ignored:
|
||||
```php
|
||||
try {
|
||||
\think\Db::execute("SET @@sql_mode='';");
|
||||
} catch (\Exception $e) {
|
||||
// Empty
|
||||
}
|
||||
```
|
||||
|
||||
**Fix approach:** Log the exception at minimum.
|
||||
|
||||
---
|
||||
|
||||
### R3. No Structured Logging
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` lines 168-177
|
||||
|
||||
**Issue:** Logging configured minimally with empty level array. No log rotation, no error tracking service.
|
||||
|
||||
**Impact:** Difficult to debug production issues. Log files can grow unbounded.
|
||||
|
||||
**Fix approach:** Configure log levels, add log rotation, integrate with error tracking (e.g., Sentry).
|
||||
|
||||
---
|
||||
|
||||
### R4. No Validation on History Model
|
||||
|
||||
**Files:**
|
||||
- `application/admin/controller/History.php`
|
||||
- `application/admin/model/History.php`
|
||||
- `application/admin/validate/History.php` (exists but not referenced)
|
||||
|
||||
**Issue:** The History controller has no custom validation. The model has no validation rules. A validate file exists (`application/admin/validate/History.php`) but is not referenced in the controller.
|
||||
|
||||
**Impact:** Malformed lottery data can be stored through the admin panel.
|
||||
|
||||
**Fix approach:** Enable model validation in the controller and define rules for lottery data fields.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### A1. ThinkPHP 5.x is Outdated
|
||||
|
||||
**Files:**
|
||||
- `composer.json` line 19
|
||||
- `thinkphp/` directory
|
||||
|
||||
**Issue:** Uses `topthink/framework: dev-master` (ThinkPHP 5.x fork). ThinkPHP 5 is EOL; current stable is 8.x. The dev-master branch from Gitee is a maintained fork but diverges from the official framework.
|
||||
|
||||
**Impact:** No official security patches. Dependency compatibility issues.
|
||||
|
||||
**Fix approach:** Monitor the FastAdmin fork for security updates. Plan migration to ThinkPHP 6+ when feasible.
|
||||
|
||||
---
|
||||
|
||||
### A2. Dependency Version Risks
|
||||
|
||||
**Files:**
|
||||
- `composer.json`
|
||||
|
||||
**Issue:** Unstable and outdated dependencies:
|
||||
```json
|
||||
"topthink/framework": "dev-master", // Unstable branch
|
||||
"topthink/think-queue": "1.1.6", // Very old
|
||||
"topthink/think-captcha": "^1.0.9", // TP5-era
|
||||
```
|
||||
|
||||
**Impact:** `dev-master` can introduce breaking changes. Old versions may contain known vulnerabilities.
|
||||
|
||||
**Fix approach:** Pin stable versions. Run `composer audit` regularly.
|
||||
|
||||
---
|
||||
|
||||
### A3. Framework Code Committed to Repository
|
||||
|
||||
**Files:**
|
||||
- `thinkphp/` directory
|
||||
- `.gitignore` line 2
|
||||
|
||||
**Issue:** The entire ThinkPHP framework source is committed to the repository instead of being managed purely through Composer.
|
||||
|
||||
**Impact:** Repository bloat. Difficulty tracking framework upgrades.
|
||||
|
||||
**Fix approach:** Remove `thinkphp/` from the repository and manage solely through Composer.
|
||||
|
||||
---
|
||||
|
||||
### A4. No API Versioning
|
||||
|
||||
**Files:**
|
||||
- `application/api/controller/` (all files)
|
||||
|
||||
**Issue:** API endpoints have no version prefix. Any breaking change affects all clients immediately.
|
||||
|
||||
**Impact:** Inability to make breaking API changes without disrupting existing clients.
|
||||
|
||||
**Fix approach:** Add API versioning (e.g., `/api/v1/`) using ThinkPHP routes.
|
||||
|
||||
---
|
||||
|
||||
## Domain-Specific
|
||||
|
||||
### D1. Lottery Data Accuracy Not Guaranteed (HIGH)
|
||||
|
||||
**Files:**
|
||||
- `application/index/controller/Index.php` lines 20-58
|
||||
|
||||
**Issue:** Lottery data sourced from a single third-party API with no verification, backup source, or data integrity checks. Raw `openCode` string is split by comma and mapped directly without range validation.
|
||||
|
||||
**Impact:** If the external source provides incorrect data, the system propagates errors silently.
|
||||
|
||||
**Fix approach:** Add range validation (numbers 1-49), implement backup data sources, and maintain an audit log.
|
||||
|
||||
---
|
||||
|
||||
### D2. Data Validation Gaps for Lottery Records
|
||||
|
||||
**Files:**
|
||||
- `sql/macaujc_history.sql`
|
||||
- `application/admin/model/History.php`
|
||||
- `application/admin/validate/History.php`
|
||||
|
||||
**Issue:** The History model has no validation rules. The SQL schema has loose VARCHAR constraints without format validation.
|
||||
|
||||
**Impact:** Invalid lottery records can be stored (negative numbers, out-of-range values, malformed dates).
|
||||
|
||||
**Fix approach:** Add validation rules to the History model. Implement data normalization accessors.
|
||||
|
||||
---
|
||||
|
||||
### D3. Hardcoded Year in Scraping Endpoint
|
||||
|
||||
**Files:**
|
||||
- `application/index/controller/Index.php` line 23
|
||||
|
||||
**Issue:** The API URL hardcodes the year `2026`:
|
||||
```php
|
||||
$res = $client->request('GET', 'https://history.macaumarksix.com/history/macaujc2/y/2026');
|
||||
```
|
||||
|
||||
**Impact:** Requires annual manual update. After January 1st of a new year, new data won't be fetched.
|
||||
|
||||
**Fix approach:** Make the year dynamic with `date('Y')` or accept it as a parameter.
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
### T1. Zero Automated Tests
|
||||
|
||||
**Files:** Entire application codebase
|
||||
|
||||
**Issue:** No test files exist anywhere in the application directory. No `phpunit.xml` configuration.
|
||||
|
||||
**Risk:** High
|
||||
|
||||
**Impact:** No automated regression testing. Code changes carry high risk of introducing bugs. Cannot safely refactor.
|
||||
|
||||
**Fix approach:** Set up PHPUnit. Start with unit tests for Auth library, then API endpoints, then lottery data handling.
|
||||
|
||||
---
|
||||
|
||||
## Additional Concerns
|
||||
|
||||
### AC1. No .env.example Template
|
||||
|
||||
**Files:**
|
||||
- `.gitignore` line 15
|
||||
- `.env` (present)
|
||||
|
||||
**Issue:** `.env` is gitignored but there is no `.env.example` template.
|
||||
|
||||
**Impact:** New developers cannot set up the environment without guessing variable names.
|
||||
|
||||
**Fix approach:** Create `.env.example` with all required variables documented.
|
||||
|
||||
---
|
||||
|
||||
### AC2. Debug Mode Default Risk
|
||||
|
||||
**Files:**
|
||||
- `application/config.php` line 21
|
||||
|
||||
**Issue:** `'app_debug' => Env::get('app.debug', false)` defaults to `false`, but if `.env` is misconfigured in production with `debug = true`, full stack traces are exposed.
|
||||
|
||||
**Impact:** Detailed error messages with file paths and SQL queries exposed to end users.
|
||||
|
||||
**Fix approach:** Explicitly set `app_debug=false` in production config. Never rely on defaults.
|
||||
|
||||
---
|
||||
|
||||
### AC3. Install Script Redirect Logic Still Present
|
||||
|
||||
**Files:**
|
||||
- `public/index.php` lines 16-19
|
||||
|
||||
**Issue:** The entry point checks for `install.lock` and redirects to `./install.php` if missing. Although `public/install.php` has been deleted, the redirect logic remains.
|
||||
|
||||
**Impact:** If someone restores `install.php`, the installation wizard becomes publicly accessible.
|
||||
|
||||
**Fix approach:** Remove the redirect logic from `public/index.php` after installation is confirmed complete.
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-04-21*
|
||||
@@ -0,0 +1,599 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- Controllers: PascalCase, directory structure mirrors URL path. Example: `application/admin/controller/user/User.php` -> `/admin/user/user`
|
||||
- Models: PascalCase in parallel directory structure. Example: `application/admin/model/User.php`
|
||||
- Validates: PascalCase. Example: `application/admin/validate/User.php`
|
||||
- Libraries: PascalCase. Example: `application/admin/library/Auth.php`, `application/common/library/Upload.php`
|
||||
- Traits: PascalCase. Example: `application/admin/library/traits/Backend.php`
|
||||
- Behaviors: PascalCase. Example: `application/admin/behavior/AdminLog.php`
|
||||
- Language files: lowercase `zh-cn.php`, mirroring controller directory structure
|
||||
- View templates: lowercase `.html`, matching action names. Example: `application/admin/view/user/user/index.html`
|
||||
- JS files: lowercase, mirroring controller path. Example: `public/assets/js/backend/user/user.js`
|
||||
- CSS: lowercase `.css` with `.min.css` variants
|
||||
- Less: lowercase `.less` (source files in `public/assets/less/`)
|
||||
- Common helper functions: snake_case. Example: `build_select()`, `cdnurl()`, `datetime()`, `letter_avatar()`
|
||||
|
||||
**Functions/Methods:**
|
||||
- Controller action methods: lowercase with underscores. Examples: `index()`, `add()`, `edit()`, `del()`, `recyclebin()`, `get_field_list()`, `get_controller_list()`
|
||||
- Controller protected methods: camelCase. Examples: `buildparams()`, `selectpage()`, `getDataLimitAdminIds()`, `loadlang()`, `assignconfig()`
|
||||
- Model accessors/mutators: ThinkPHP convention `get{FieldName}Attr()` / `set{FieldName}Attr()`. Example: `getPrevtimeTextAttr()`, `setJointimeAttr()`, `getAvatarAttr()`, `setBirthdayAttr()`
|
||||
- Model list methods: PascalCase `getStatusList()`, `getGenderList()`
|
||||
- Model static methods: camelCase. Example: `User::money()`, `User::score()`, `User::nextlevel()`
|
||||
- Library methods: camelCase. Example: `Auth::login()`, `Auth::getUserinfo()`, `Upload::init()`
|
||||
- Global functions: snake_case, wrapped in `if (!function_exists('...'))`. Example: `__()`, `format_bytes()`, `check_cors_request()`, `xss_clean()`
|
||||
- Addon methods: camelCase following ThinkPHP convention
|
||||
|
||||
**Variables:**
|
||||
- Controller properties: camelCase. Examples: `$noNeedLogin`, `$model`, `$searchFields`, `$relationSearch`, `$dataLimit`
|
||||
- Library private properties: underscore prefix + camelCase. Examples: `$_error`, `$_logined`, `$_user`, `$_token` (in `application/common/library/Auth.php`)
|
||||
- Library protected properties: camelCase, no prefix. Examples: `$keeptime`, `$requestUri`, `$allowFields`
|
||||
- Local variables: camelCase. Examples: `$tableList`, `$fieldlist`, `$insert_data`, `$changedata`
|
||||
- Database fields: lowercase with underscores. Examples: `createtime`, `updatetime`, `group_id`, `loginip`
|
||||
- Request input: via `$this->request->post('row/a')` (array), `$this->request->request('keyField')` (single)
|
||||
|
||||
**Types/Namespaces:**
|
||||
- Namespace convention: lowercase `app\admin\controller`, `app\common\model`, `app\api\library`
|
||||
- Class names: PascalCase. Example: `class User extends Backend`
|
||||
- Addon namespace: `addons\{name}\` mapped via PSR-4 in `composer.json`
|
||||
- Fast tools namespace: `fast\` maps to `extend/fast/` directory
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- 4-space indentation throughout (no tabs)
|
||||
- Opening brace on same line for classes/functions: `class User extends Backend\n{`
|
||||
- Opening brace on next line for control structures in some files, same line in others (inconsistent)
|
||||
- Closing PHP tag `?>` omitted at end of files
|
||||
- Files start with `<?php` followed by a blank line
|
||||
- Blank line after namespace declaration
|
||||
- Blank line after last `use` statement before class declaration
|
||||
|
||||
**PHP Version:** PHP >= 7.4.0 (per `composer.json`)
|
||||
- Union types in catch blocks: `catch (ValidateException|PDOException|Exception $e)`
|
||||
- Null coalescing: `$value ?? ''`
|
||||
- Match expressions: Not used
|
||||
- Arrow functions: Not used
|
||||
- Typed properties: Not used
|
||||
|
||||
**Linting/Formatting Tools:**
|
||||
- No project-level `.php-cs-fixer.php`, `phpcs.xml`, `.editorconfig`, or `biome.json`
|
||||
- No ESLint or stylelint configured
|
||||
- PhpStorm `.idea/` directory present with PHP 7.4 language level configured
|
||||
- Static analysis tools (PHPStan, PHPCS, MessDetector) configured in IDE but not activated (`transferred=true`)
|
||||
|
||||
## Import Organization
|
||||
|
||||
**Order in PHP files:**
|
||||
1. `namespace app\admin\controller;`
|
||||
2. `use` statements for app classes: `use app\common\controller\Backend;`
|
||||
3. `use` statements for ThinkPHP classes: `use think\Db;`, `use think\Config;`, `use think\Exception;`
|
||||
4. `use` statements for vendor/third-party classes: `use fast\Tree;`
|
||||
5. No grouping separators between use statement categories
|
||||
|
||||
**Example from `application/admin/controller/user/User.php`:**
|
||||
```php
|
||||
namespace app\admin\controller\user;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use app\common\library\Auth;
|
||||
```
|
||||
|
||||
**Example from `application/common/controller/Backend.php`:**
|
||||
```php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\admin\library\Auth;
|
||||
use think\Config;
|
||||
use think\Controller;
|
||||
use think\Hook;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\Session;
|
||||
use fast\Tree;
|
||||
use think\Validate;
|
||||
```
|
||||
|
||||
**Path Aliases:**
|
||||
- No PSR-4 path aliases configured beyond `addons\\` -> `addons/` in `composer.json`
|
||||
- ThinkPHP autoload handles `app\` namespace mapping automatically
|
||||
- `fast\` namespace handled by ThinkPHP custom autoloader -> `extend/fast/`
|
||||
|
||||
## ThinkPHP Conventions
|
||||
|
||||
**Model Table Naming:**
|
||||
- `$name` property defines the table name without prefix. Example: `protected $name = 'user';` maps to `{prefix}user`
|
||||
- Table prefix configured in `application/config.php` database section
|
||||
- All models extend `think\Model`
|
||||
|
||||
**Timestamp Convention:**
|
||||
- Auto timestamp type: `protected $autoWriteTimestamp = 'int';` (Unix integer timestamps)
|
||||
- Create field name: `protected $createTime = 'createtime';`
|
||||
- Update field name: `protected $updateTime = 'updatetime';`
|
||||
|
||||
**Controller Initialization:**
|
||||
- `_initialize()` method called by ThinkPHP before each action
|
||||
- Always calls `parent::_initialize()` first
|
||||
- Sets `$this->model` instance in `_initialize()`
|
||||
- Assigns view data: `$this->view->assign("statusList", ...)`
|
||||
|
||||
**Magic Model Methods:**
|
||||
- Dynamic finders via `@method` PHPDoc annotations
|
||||
- `getBy{Field}()` auto-generated by ThinkPHP for any column
|
||||
- Example: `@method static mixed getByUsername($str)` in `application/common/model/User.php`
|
||||
- Usage: `\app\common\model\User::getByMobile($mobile)`
|
||||
|
||||
**AJAX Detection Pattern:**
|
||||
```php
|
||||
if ($this->request->isAjax()) {
|
||||
// Return JSON response
|
||||
return json(['total' => $list->total(), 'rows' => $list->items()]);
|
||||
}
|
||||
return $this->view->fetch();
|
||||
```
|
||||
|
||||
## FastAdmin Patterns
|
||||
|
||||
**Backend Base Class (`app\common\controller\Backend`):**
|
||||
- All admin controllers extend this class
|
||||
- Provides via trait `app\admin\library\traits\Backend`:
|
||||
- `index()` - List with pagination and filtering
|
||||
- `add()` - Create record
|
||||
- `edit($ids)` - Update record
|
||||
- `del($ids)` - Soft delete
|
||||
- `destroy($ids)` - Hard delete (from recycle bin)
|
||||
- `restore($ids)` - Restore from recycle bin
|
||||
- `multi($ids)` - Batch update
|
||||
- `recyclebin()` - Recycle bin list
|
||||
- `import()` - Excel/CSV import
|
||||
- `selectpage()` - SelectPage dropdown data
|
||||
- Key configurable properties:
|
||||
- `$noNeedLogin = []` - Methods skipping authentication entirely
|
||||
- `$noNeedRight = []` - Methods skipping permission check (still need login)
|
||||
- `$model = null` - Associated model instance
|
||||
- `$searchFields = 'id'` - Fields for quick search
|
||||
- `$relationSearch = false` - Whether to use table alias in queries
|
||||
- `$dataLimit = false` - Data scope: `auth`/`personal`/`false`
|
||||
- `$dataLimitField = 'admin_id'` - Field for data restriction
|
||||
- `$dataLimitFieldAutoFill = true` - Auto-fill restriction field
|
||||
- `$modelValidate = false` - Enable model-level validation
|
||||
- `$modelSceneValidate = false` - Enable scene-based validation
|
||||
- `$multiFields = 'status'` - Fields allowed in batch operations
|
||||
- `$selectpageFields = '*'` - Fields shown in SelectPage
|
||||
- `$excludeFields = ""` - Fields to exclude from form submission
|
||||
- `$importHeadType = 'comment'` - Import header type: `comment`/`name`
|
||||
- `$layout = 'default'` - Template layout name
|
||||
|
||||
**Standard CRUD Controller Pattern:**
|
||||
```php
|
||||
/**
|
||||
* 会员管理
|
||||
*
|
||||
* @icon fa fa-user
|
||||
*/
|
||||
class User extends Backend
|
||||
{
|
||||
protected $relationSearch = true;
|
||||
protected $searchFields = 'id,username,nickname';
|
||||
|
||||
/**
|
||||
* @var \app\admin\model\User
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\User;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CRUD Generation:**
|
||||
- Via `php think crud` command
|
||||
- Flags: `--table`, `--controller`, `--model`, `--fields`, `--force`, `--delete`, `--menu`
|
||||
- Extended flags: `--setcheckboxsuffix`, `--enumradiosuffix`, `--imagefield`, `--filefield`, etc.
|
||||
|
||||
**Auth Patterns:**
|
||||
- Admin auth: `app\admin\library\Auth` - backend admin user authentication
|
||||
- User auth: `app\common\library\Auth` - frontend/member user authentication
|
||||
- Both use singleton: `Auth::instance()`
|
||||
- Permission check: `$this->auth->check($path)` where `$path = 'controller/action'`
|
||||
- Login check: `$this->auth->isLogin()`
|
||||
- Super admin check: `$this->auth->isSuperAdmin()`
|
||||
|
||||
**API Controller Pattern (`app\common\controller\Api`):**
|
||||
- API controllers extend this (NOT `think\Controller`)
|
||||
- Does NOT extend `think\Controller` - standalone class with `__construct()`
|
||||
- Response format: `{'code': 1|0, 'msg': '', 'time': <timestamp>, 'data': ...}`
|
||||
- Success: `$this->success($msg, $data)` (code=1, HTTP 200)
|
||||
- Error: `$this->error($msg, $data, $httpCode)` (code=0)
|
||||
- HTTP status codes: 401 for unauth, 403 for forbidden
|
||||
- API annotations for doc generation: `@ApiMethod(POST)`, `@ApiParams(name="...", type="string", required=true, description="...")`
|
||||
- Default request filter: `trim,strip_tags,htmlspecialchars`
|
||||
|
||||
**Frontend Controller Pattern (`app\common\controller\Frontend`):**
|
||||
- Index module controllers extend this
|
||||
- Similar to Backend but for public-facing pages
|
||||
- Uses `app\common\library\Auth` for member auth
|
||||
- Layout can be empty string for no layout: `protected $layout = '';`
|
||||
|
||||
## Language File Structure
|
||||
|
||||
**Directory Layout:**
|
||||
```
|
||||
application/{module}/lang/zh-cn.php # Module-level translations
|
||||
application/{module}/lang/zh-cn/controller.php # Controller translations
|
||||
application/{module}/lang/zh-cn/sub/controller.php # Nested controller translations
|
||||
application/{module}/lang/{locale}/controller.php # Other locales
|
||||
```
|
||||
|
||||
**Observed locales:**
|
||||
- `zh-cn` (Simplified Chinese) - primary language for all modules
|
||||
- `en` - only exists for `application/index/lang/en/index.php`
|
||||
|
||||
**File Format:**
|
||||
```php
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Group_id' => '组别ID',
|
||||
'Username' => '用户名',
|
||||
'Leave password blank if dont want to change' => '不修改密码请留空',
|
||||
];
|
||||
```
|
||||
|
||||
**Usage Pattern:**
|
||||
- `__('Key')` - Simple translation
|
||||
- `__('Key %s', $value)` - Parameterized translation
|
||||
- Keys use PascalCase for field names (matching DB column names)
|
||||
- Keys use sentence case for messages
|
||||
- Language auto-loaded by controller in `loadlang()` method
|
||||
- Language detection: `$this->request->langset()` with regex validation, defaults to `zh-cn`
|
||||
|
||||
## Template Syntax
|
||||
|
||||
**Template Engine:** ThinkPHP built-in template engine
|
||||
**File Extension:** `.html`
|
||||
|
||||
**Template Tags Used:**
|
||||
|
||||
| Tag | Example |
|
||||
|-----|---------|
|
||||
| Output | `{$variable}`, `{$var\|htmlentities}`, `{$var\|default='default'}` |
|
||||
| Function | `{:__('Dashboard')}`, `{:build_heading()}`, `{:build_toolbar('refresh,edit,del')}` |
|
||||
| Condition | `{if condition="$auth->check('dashboard')"}`, `{if !IS_DIALOG}`, `{/if}` |
|
||||
| If-else shorthand | `{:defined('IS_DIALOG') && IS_DIALOG ? 'is-dialog' : ''}` |
|
||||
| Loop | `{foreach $breadcrumb as $vo}`, `{/foreach}` |
|
||||
| Include | `{include file="common/meta" /}` |
|
||||
| Layout placeholder | `{__CONTENT__}` |
|
||||
| ThinkPHP config | `{$Think.config.fastadmin.breadcrumb}` |
|
||||
| Inline PHP | `{:$auth->check('user/user/multi')?'':'hide'}` |
|
||||
|
||||
**Layout Structure:**
|
||||
```
|
||||
application/{module}/view/layout/default.html # Layout wrapper with {__CONTENT__}
|
||||
application/{module}/view/{controller}/index.html # Page content
|
||||
application/{module}/view/common/meta.html # Shared <head> section
|
||||
application/{module}/view/common/script.html # Shared JS loading
|
||||
application/{module}/view/common/header.html # Header fragment
|
||||
application/{module}/view/common/menu.html # Sidebar menu
|
||||
application/{module}/view/common/control.html # Control bar fragment
|
||||
```
|
||||
|
||||
**CSS Framework:** Bootstrap 3 with AdminLTE theme
|
||||
**Common Classes:**
|
||||
- Layout: `.panel`, `.panel-default`, `.panel-intro`, `.panel-heading`, `.panel-body`
|
||||
- Table: `.table`, `.table-striped`, `.table-bordered`, `.table-hover`, `.table-nowrap`
|
||||
- Buttons: `.btn`, `.btn-primary`, `.btn-success`, `.btn-danger`, `.btn-info`, `.btn-xs`
|
||||
- Form: `.form-control`, `.selectpicker`, `.form-group`, `.control-label`
|
||||
- FastAdmin custom: `.btn-dialog`, `.btn-addtabs`, `.btn-ajax`, `.btn-click`, `.searchit`
|
||||
|
||||
## JS AMD Module Pattern (RequireJS)
|
||||
|
||||
**Module Definition Pattern:**
|
||||
```javascript
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
var Controller = {
|
||||
index: function () {
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'user/user/index',
|
||||
add_url: 'user/user/add',
|
||||
edit_url: 'user/user/edit',
|
||||
del_url: 'user/user/del',
|
||||
multi_url: 'user/user/multi',
|
||||
table: 'user',
|
||||
}
|
||||
});
|
||||
var table = $("#table");
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'user.id',
|
||||
columns: [[
|
||||
{checkbox: true},
|
||||
{field: 'id', title: __('Id'), sortable: true},
|
||||
{field: 'operate', title: __('Operate'), table: table,
|
||||
events: Table.api.events.operate,
|
||||
formatter: Table.api.formatter.operate}
|
||||
]]
|
||||
});
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
},
|
||||
api: {
|
||||
bindevent: function () {
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
```
|
||||
|
||||
**RequireJS Configuration Files:**
|
||||
- `public/assets/js/require-backend.js` - Backend module config (minified as `require-backend.min.js`)
|
||||
- `public/assets/js/require-frontend.js` - Frontend module config (minified)
|
||||
- `public/assets/js/require-table.js` - Bootstrap-table wrapper module
|
||||
- `public/assets/js/require-form.js` - Form handling module
|
||||
- `public/assets/js/require-upload.js` - Upload module
|
||||
|
||||
**Core JS Modules:**
|
||||
- `Fast` / `Fast.api` - Core API: `ajax()`, `open()`, `cdnurl()`, `fixurl()`, `selectedids()`
|
||||
- `Backend` - Admin: sidebar badges, tab management, dialog/_ajax handlers
|
||||
- `Frontend` - Frontend: captcha sending, touch swipe for sidebar
|
||||
- `Table` - Bootstrap-table wrapper: `api.init()`, `api.bindevent()`, formatters, events
|
||||
- `Form` - Form validation and submission: `api.bindevent()`
|
||||
- `Template` - art-template JS template engine
|
||||
- `Moment` - Date/time formatting (with `moment/locale/zh-cn`)
|
||||
|
||||
**Global Objects (set on `window`):**
|
||||
- `window.Backend` - Backend namespace
|
||||
- `window.Frontend` - Frontend namespace
|
||||
- `window.Config` - Server-rendered config object
|
||||
- `window.Toastr` - Toastr notification library
|
||||
- `window.Layer` - Layer popup library (layui-based)
|
||||
- `window.Table` - (via require) Table module
|
||||
- `window.Form` - (via require) Form module
|
||||
- `window.Template` - Template engine
|
||||
- `window.Moment` - Moment.js
|
||||
|
||||
**Controller JS File Convention:**
|
||||
- Location: `public/assets/js/backend/{controller_path}.js`
|
||||
- Standard methods: `Controller.index()`, `Controller.add()`, `Controller.edit()`
|
||||
- Sub-controller: `public/assets/js/backend/auth/admin.js`
|
||||
- Each controller JS is loaded dynamically based on `Config.jsname`
|
||||
|
||||
**Button/Action Patterns in JS:**
|
||||
- `.btn-dialog` / `.dialogit` - Opens URL in Layer dialog
|
||||
- `.btn-addtabs` / `.addtabsit` - Opens URL in new tab
|
||||
- `.btn-ajax` / `.ajaxit` - Sends AJAX request
|
||||
- `.btn-click` / `.clickit` - Custom click handler
|
||||
- `.searchit` - Triggers table search with field/value
|
||||
|
||||
**Table Formatter Patterns:**
|
||||
- `Table.api.formatter.image` / `.images` - Image display
|
||||
- `Table.api.formatter.datetime` - Date formatting
|
||||
- `Table.api.formatter.status` - Status badge
|
||||
- `Table.api.formatter.normal` - Generic label with color
|
||||
- `Table.api.formatter.search` - Clickable search link
|
||||
- `Table.api.formatter.operate` - Action buttons (edit/del)
|
||||
- `Table.api.formatter.toggle` - Toggle switch
|
||||
- `Table.api.formatter.flag` / `.label` - Multi-value flags
|
||||
|
||||
## CSS Class Naming
|
||||
|
||||
**Convention:** Bootstrap 3 + AdminLTE + FastAdmin extensions
|
||||
**CSS Source:** Less files compiled to CSS
|
||||
- `public/assets/less/backend.less` -> `public/assets/css/backend.css`
|
||||
- `public/assets/less/frontend.less` -> `public/assets/css/frontend.css`
|
||||
- `public/assets/less/bootstrap.less` -> `public/assets/css/bootstrap.css`
|
||||
- `public/assets/css/fastadmin.css` - FastAdmin base overrides
|
||||
- `public/assets/css/index.css` - Index page styles
|
||||
- `public/assets/css/user.css` - User center styles
|
||||
|
||||
**Common Panel Pattern:**
|
||||
```html
|
||||
<div class="panel panel-default panel-intro">
|
||||
{:build_heading()}
|
||||
<div class="panel-body">
|
||||
<div id="toolbar" class="toolbar">
|
||||
{:build_toolbar('refresh,edit,del')}
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
||||
data-operate-edit="{:$auth->check('user/user/edit')}"
|
||||
data-operate-del="{:$auth->check('user/user/del')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Controller Layer:**
|
||||
- `$this->error($message)` - Returns error, terminates execution
|
||||
- `$this->success($message, $data)` - Returns success response
|
||||
- Backend: throws `HttpResponseException` internally
|
||||
- API: sets HTTP status codes (401/403) via header
|
||||
|
||||
**Transaction Pattern:**
|
||||
```php
|
||||
Db::startTrans();
|
||||
try {
|
||||
// database operations
|
||||
Db::commit();
|
||||
} catch (ValidateException|PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result === false) {
|
||||
$this->error(__('No rows were inserted'));
|
||||
}
|
||||
$this->success();
|
||||
```
|
||||
|
||||
**Model Event Hooks:**
|
||||
```php
|
||||
protected static function init()
|
||||
{
|
||||
self::beforeWrite(function ($row) {
|
||||
$changed = $row->getChangedData();
|
||||
if (isset($changed['password'])) {
|
||||
$salt = \fast\Random::alnum();
|
||||
$row->password = \app\common\library\Auth::instance()->getEncryptPassword($changed['password'], $salt);
|
||||
$row->salt = $salt;
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
**Framework:** ThinkPHP built-in logging
|
||||
- Configurable driver (file, etc.) in `application/config.php`
|
||||
- Custom log library: `application/common/library/Log.php`
|
||||
- Admin log behavior: `application/admin/behavior/AdminLog.php` - auto-logs admin actions to database
|
||||
|
||||
## Comments
|
||||
|
||||
**JSDoc/TSDoc Pattern:**
|
||||
- All public/protected methods have PHPDoc with Chinese descriptions
|
||||
- Controller methods: brief action description
|
||||
- API methods: `@ApiMethod` + `@ApiParams` annotations for API doc generation
|
||||
|
||||
**Controller Class Doc:**
|
||||
```php
|
||||
/**
|
||||
* 会员管理
|
||||
*
|
||||
* @icon fa fa-user
|
||||
*/
|
||||
class User extends Backend
|
||||
```
|
||||
|
||||
**Method Doc:**
|
||||
```php
|
||||
/**
|
||||
* 会员登录
|
||||
*
|
||||
* @ApiMethod (POST)
|
||||
* @ApiParams (name="account", type="string", required=true, description="账号")
|
||||
* @ApiParams (name="password", type="string", required=true, description="密码")
|
||||
*/
|
||||
public function login()
|
||||
```
|
||||
|
||||
**Model Doc:**
|
||||
```php
|
||||
/**
|
||||
* 会员模型
|
||||
* @method static mixed getByUsername($str) 通过用户名查询用户
|
||||
* @method static mixed getByNickname($str) 通过昵称查询用户
|
||||
*/
|
||||
class User extends Model
|
||||
```
|
||||
|
||||
**Function Doc:**
|
||||
```php
|
||||
/**
|
||||
* 将字节转换为可读文本
|
||||
* @param int $size 大小
|
||||
* @param string $delimiter 分隔符
|
||||
* @param int $precision 小数位数
|
||||
* @return string
|
||||
*/
|
||||
function format_bytes($size, $delimiter = '', $precision = 2)
|
||||
```
|
||||
|
||||
## Function Design
|
||||
|
||||
**Size:**
|
||||
- Controller action methods: 10-30 lines (when extending Backend, most logic is in trait)
|
||||
- Backend trait methods: 30-80 lines (`index`, `add`, `edit`, `del`)
|
||||
- Import method: ~130 lines (`application/admin/library/traits/Backend.php`)
|
||||
- Library methods: 10-50 lines
|
||||
- Global functions: 5-30 lines
|
||||
|
||||
**Parameters:**
|
||||
- Primary key as method parameter: `edit($ids = null)`, `del($ids = "")`
|
||||
- POST form data: `$this->request->post('row/a')` (returns associative array)
|
||||
- Query params: `$this->request->request('key')`, `$this->request->get('filter')`
|
||||
|
||||
**Return Values:**
|
||||
- AJAX: `json(['total' => N, 'rows' => [...]])`
|
||||
- Non-AJAX: `$this->view->fetch()`
|
||||
- API: Always JSON via `$this->success()` / `$this->error()`
|
||||
|
||||
## Module Design
|
||||
|
||||
**PHP Exports:**
|
||||
- No explicit exports; PSR-4 autoloading handles class loading
|
||||
- Language files: `return [...]` array
|
||||
- Config files: `return [...]` array
|
||||
|
||||
**JS Exports:**
|
||||
- AMD `define()` with `return Controller;` pattern
|
||||
- No ES modules, no CommonJS
|
||||
|
||||
**No Barrel Files:** Direct imports throughout, no index/re-export files
|
||||
|
||||
**Addon Structure:**
|
||||
```
|
||||
addons/{addon_name}/
|
||||
├── config.php # Addon configuration (returns array)
|
||||
├── Command.php # Main addon class (extends \fast\Addons)
|
||||
├── controller/Index.php # Addon controller
|
||||
├── library/Output.php # Addon library
|
||||
└── info.ini # Addon metadata
|
||||
```
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New Admin Feature (CRUD):**
|
||||
- Controller: `application/admin/controller/{module}/{Name}.php`
|
||||
- Model: `application/admin/model/{Name}.php`
|
||||
- Validate: `application/admin/validate/{Name}.php`
|
||||
- Language: `application/admin/lang/zh-cn/{module}/{name}.php`
|
||||
- View: `application/admin/view/{module}/{name}/index.html`, `add.html`, `edit.html`
|
||||
- JS: `public/assets/js/backend/{module}/{name}.js`
|
||||
- Or auto-generate: `php think crud --table={table} --controller={name}`
|
||||
|
||||
**New API Endpoint:**
|
||||
- Controller: `application/api/controller/{Name}.php` (extends `app\common\controller\Api`)
|
||||
- Response via `$this->success()` / `$this->error()`
|
||||
- Add `@ApiMethod` and `@ApiParams` annotations for doc generation
|
||||
|
||||
**New Common Library:**
|
||||
- Location: `application/common/library/{Name}.php`
|
||||
- Use singleton `instance()` pattern if needed
|
||||
|
||||
**New Global Function:**
|
||||
- Location: `application/common.php`
|
||||
- Wrap in `if (!function_exists('name')) { ... }`
|
||||
|
||||
**New Module-Level Function:**
|
||||
- Location: `application/{module}/common.php`
|
||||
|
||||
**New Model:**
|
||||
- Shared model: `application/common/model/{Name}.php`
|
||||
- Admin-only model: `application/admin/model/{Name}.php`
|
||||
- Extend `think\Model`, set `$name`, `$autoWriteTimestamp`, `$createTime`, `$updateTime`
|
||||
|
||||
**New Validate:**
|
||||
- Location: `application/admin/validate/{Name}.php`
|
||||
- Extend `think\Validate`, define `$rule`, `$field`, `$scene`
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-04-21*
|
||||
@@ -0,0 +1,252 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**Lottery Data Scraping:**
|
||||
- URL: `https://history.macaumarksix.com/history/macaujc2/y/{year}` (e.g., `2026`)
|
||||
- Purpose: Fetching Macau Mark Six lottery historical results
|
||||
- Client: `guzzlehttp/guzzle` ^7.10
|
||||
- Integration point: `D:\code\php\amlhc\application\index\controller\Index.php` method `get_history()` (lines 20-58)
|
||||
- Data flow: Scraped JSON response contains `expect` (period number), `openTime`, `openCode` (comma-separated numbers) -> parsed and upserted into `fa_history` table
|
||||
|
||||
**FastAdmin Official API:**
|
||||
- URL: `https://api.fastadmin.net` (`application/config.php` `fastadmin.api_url`)
|
||||
- Purpose: Plugin marketplace, version checks, addon updates
|
||||
|
||||
**WeChat (EasyWeChat SDK):**
|
||||
- Package: `overtrue/wechat` ^4.6
|
||||
- Purpose: WeChat OAuth login, messaging
|
||||
- Integration point: Addon-level, managed via addon configuration
|
||||
|
||||
## Data Storage
|
||||
|
||||
**Databases:**
|
||||
- **MySQL** - Primary database
|
||||
- Connection via env vars: `database.hostname`, `database.database`, `database.username`, `database.password`, `database.hostport` (`D:\code\php\amlhc\application\database.php`)
|
||||
- Charset: `utf8mb4` (configurable via `database.charset`)
|
||||
- Table prefix: `fa_` (configurable via `database.prefix`)
|
||||
- PDO driver required (`ext-pdo`)
|
||||
- Single server mode by default (`deploy: 0`), supports master-slave replication
|
||||
- Key tables: `fa_admin`, `fa_auth_group`, `fa_auth_rule`, `fa_user`, `fa_attachment`, `fa_history`, `fa_num`, `fa_command`
|
||||
|
||||
**Caching:**
|
||||
- **File-based cache** - Default cache driver (`application/config.php` `cache.type => File`, path: `CACHE_PATH`)
|
||||
- **Redis** - Used for queue system (`D:\code\php\amlhc\application\extra\queue.php`)
|
||||
- Host: `127.0.0.1`, Port: `6379`
|
||||
- Password: empty by default
|
||||
- Database: `0` (select)
|
||||
- Persistent connection: disabled
|
||||
- Expire: `0` (no expiration on tasks)
|
||||
- **Token storage** - MySQL-backed (`application/config.php` `token.type => Mysql`)
|
||||
- **Menu cache** - Uses ThinkPHP cache with key `"__menu__"` (`D:\code\php\amlhc\application\admin\library\Auth.php` line 461)
|
||||
- Session supports Redis/memcache drivers but defaults to file-based
|
||||
|
||||
**File Storage:**
|
||||
- **Local filesystem** - Default upload storage
|
||||
- Upload URL: `ajax/upload` (`D:\code\php\amlhc\application\extra\upload.php`)
|
||||
- Upload path pattern: `/uploads/{year}{mon}{day}/{filemd5}{.suffix}`
|
||||
- Max upload size: 10MB
|
||||
- Allowed types: `jpg,png,bmp,jpeg,gif,webp,zip,rar,wav,mp4,mp3,webm`
|
||||
- CDN support available via `cdnurl` config (empty by default)
|
||||
- Chunked upload support available (disabled by default, chunk size: 2MB)
|
||||
- Upload handled by: `D:\code\php\amlhc\application\api\controller\Common.php` `upload()` method with `app\common\library\Upload` class
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**Backend Admin Auth:**
|
||||
- Class: `D:\code\php\amlhc\application\admin\library\Auth.php` (extends `fast\Auth`)
|
||||
- Password hashing: `md5(md5(password) . salt)` (double MD5 with salt)
|
||||
- Session-based: Stores admin data in `Session::get('admin')`
|
||||
- Role-based access control (RBAC): Admin -> AuthGroup -> AuthRule hierarchy
|
||||
- Features:
|
||||
- Login retry limit: 10 attempts, 1-day cooldown (`fastadmin.login_failure_retry`)
|
||||
- IP change detection enabled (`fastadmin.loginip_check: true`)
|
||||
- Unique login option available (`fastadmin.login_unique: false` by default)
|
||||
- Safe code validation: MD5-based checksum of username + partial password + token key
|
||||
- Auto-login via `keeplogin` cookie with time-limited key
|
||||
- Tables: `fa_admin`, `fa_auth_group`, `fa_auth_group_access`, `fa_auth_rule`
|
||||
|
||||
**Frontend User Auth:**
|
||||
- Class: `D:\code\php\amlhc\application\common\library\Auth.php`
|
||||
- Token-based: UUID tokens stored in MySQL token table
|
||||
- Token default lifetime: 2,592,000 seconds (30 days)
|
||||
- Password hashing: Same double MD5 + salt as admin
|
||||
- Features:
|
||||
- Login by username, email, or mobile
|
||||
- User groups and rules (`fa_user_group`, `fa_user_rule`)
|
||||
- Score and money log tracking (`fa_money_log`, `fa_score_log`)
|
||||
- Hook events: `user_init_successed`, `user_register_successed`, `user_login_successed`, `user_logout_successed`, `user_changepwd_successed`, `user_delete_successed`
|
||||
- Tables: `fa_user`, `fa_user_group`, `fa_user_rule`
|
||||
|
||||
**API Auth:**
|
||||
- Token passed via `HTTP_TOKEN` header, `token` POST param, or Cookie
|
||||
- Controller base: `D:\code\php\amlhc\application\common\controller\Api.php`
|
||||
- HTTP 401 for unauthorized, 403 for forbidden
|
||||
- CORS handling via `check_cors_request()`
|
||||
|
||||
**Captcha:**
|
||||
- ThinkPHP captcha (`topthink/think-captcha` ^1.0.9) - Image-based, 4 characters, size 130x40
|
||||
- Text captcha - For user registration (`fastadmin.user_register_captcha: text`)
|
||||
- Login captcha: disabled by default (`fastadmin.login_captcha: false`)
|
||||
- Generated via: `D:\code\php\amlhc\application\api\controller\Common.php` `captcha()` method (large format: 350x150)
|
||||
|
||||
## Queue System
|
||||
|
||||
**Think-Queue (Redis-backed):**
|
||||
- Package: `topthink/think-queue` 1.1.6
|
||||
- Connector: Redis (`D:\code\php\amlhc\application\extra\queue.php`)
|
||||
- Default queue: `default`
|
||||
- Config: `application/extra/queue.php`
|
||||
- Redis host: `127.0.0.1:6379`
|
||||
- No password by default
|
||||
- Persistent connection: disabled
|
||||
- Task expire: `0` (no expiration)
|
||||
- CLI: `php think queue:work` / `php think queue:listen` for processing
|
||||
|
||||
## Addon/Plugin System
|
||||
|
||||
**FastAdmin Addons:**
|
||||
- Package: `fastadminnet/fastadmin-addons` ~1.4.0
|
||||
- Location: `addons/` directory
|
||||
- Config: `D:\code\php\amlhc\application\extra\addons.php`
|
||||
- Autoload: `false` (manual loading)
|
||||
- Hooks: empty by default (configured per addon)
|
||||
- Routes: empty by default (configured per addon)
|
||||
- PSR-4 autoload: `addons\` -> `addons/` (`composer.json`)
|
||||
- Addon lifecycle: `install()`, `uninstall()`, `enable()`, `disable()` methods
|
||||
- Example addon: `D:\code\php\amlhc\addons\command\Command.php`
|
||||
- Installs menu entries via `Menu::create()`
|
||||
- Deletes menu on uninstall via `Menu::delete()`
|
||||
- Enable/disable toggles menu visibility
|
||||
- Pure mode: removes `application/`, `public/`, `assets/` from addon packages when enabled (`fastadmin.addon_pure_mode: true`)
|
||||
- Unknown source addons: blocked by default (`fastadmin.unknownsources: false`)
|
||||
- Backup global files on addon enable/disable: enabled (`fastadmin.backup_global_files: true`)
|
||||
- CLI: `php think addon` for addon management
|
||||
- Admin controller: `D:\code\php\amlhc\application\admin\controller\Addon.php`
|
||||
|
||||
## ThinkPHP Hooks & Behaviors
|
||||
|
||||
**Hook Integration Points:**
|
||||
- `upload_config_init` - Called when upload config is initialized (`Backend.php`, `Frontend.php`, `Api.php`)
|
||||
- `config_init` - Called after config assembly (`Backend.php`, `Frontend.php`)
|
||||
- `admin_nologin` - Fired when admin access is denied due to no login (`Backend.php` line 145)
|
||||
- `admin_nopermission` - Fired when admin access is denied due to no permission (`Backend.php` line 158)
|
||||
- `admin_sidebar_begin` - Fired before sidebar rendering (`Auth.php` line 429)
|
||||
- `user_init_successed` - Fired on successful frontend user init (`common/library/Auth.php` line 115)
|
||||
- `user_register_successed` - Fired on user registration (`common/library/Auth.php` line 194)
|
||||
- `user_login_successed` - Fired on user login (`common/library/Auth.php` line 334)
|
||||
- `user_logout_successed` - Fired on user logout (`common/library/Auth.php` line 256)
|
||||
- `user_changepwd_successed` - Fired on password change (`common/library/Auth.php` line 283)
|
||||
- `user_delete_successed` - Fired on user deletion (`common/library/Auth.php` line 474)
|
||||
|
||||
**Tags/Behaviors:** Configured in `application/tags.php` with `addon_begin` behavior hook
|
||||
|
||||
## Email
|
||||
|
||||
**Mailer:**
|
||||
- Package: `fastadminnet/fastadmin-mailer` ^2.0.0
|
||||
- SMTP Configuration (`D:\code\php\amlhc\application\extra\site.php`):
|
||||
- Type: `1` (SMTP)
|
||||
- Host: `smtp.qq.com`
|
||||
- Port: `465` (SSL)
|
||||
- Verification type: `2` (SSL/TLS)
|
||||
- Username/password: configured via admin panel (empty by default)
|
||||
- Mail from address: configured via admin panel
|
||||
- Used for: email verification, password reset, notifications
|
||||
- Config groups: `basic`, `email`, `dictionary`, `user`, `example`
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- None configured
|
||||
|
||||
**Logs:**
|
||||
- File-based logging (`application/config.php` `log.type => File`, path: `LOG_PATH` typically `runtime/log/`)
|
||||
- Level: empty array (logs all levels by default)
|
||||
- Auto-record admin logs enabled (`fastadmin.auto_record_log: true`)
|
||||
|
||||
**Debug/Trace:**
|
||||
- App debug mode: configurable via `app.debug` env var (default: `false`)
|
||||
- App trace: configurable via `app.trace` env var (default: `false`)
|
||||
- SQL explain: disabled by default
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- Self-hosted PHP deployment
|
||||
- Web server entry: `D:\code\php\amlhc\public\index.php`
|
||||
- Router compatibility: `D:\code\php\amlhc\public\router.php` for PHP built-in server
|
||||
- Admin entry: formerly `public/admin.php` (deleted per git status)
|
||||
- Install script: formerly `public/install.php` (deleted per git status)
|
||||
|
||||
**CI Pipeline:**
|
||||
- Not detected
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required env vars** (via `think\Env` in config files):
|
||||
```
|
||||
[app]
|
||||
debug = false
|
||||
trace = false
|
||||
|
||||
[database]
|
||||
hostname = 127.0.0.1
|
||||
database = fastadmin
|
||||
username = root
|
||||
password = (configured)
|
||||
hostport = (configured)
|
||||
prefix = fa_
|
||||
charset = utf8mb4
|
||||
debug = false
|
||||
```
|
||||
|
||||
**Secrets location:**
|
||||
- `.env` file (present, not committed)
|
||||
- Database credentials in env vars
|
||||
- SMTP credentials in admin-configurable site settings (`application/extra/site.php`)
|
||||
- WeChat app credentials managed via WeChat addon
|
||||
- Token key: hardcoded in `application/config.php` `token.key`
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- Not detected in base configuration
|
||||
- Addons may register their own webhook endpoints
|
||||
|
||||
**Outgoing:**
|
||||
- FastAdmin API calls to `https://api.fastadmin.net` for addon marketplace
|
||||
- Lottery data scraping to `https://history.macaumarksix.com` (Guzzle HTTP GET)
|
||||
- Email sending via SMTP (qq.com)
|
||||
|
||||
## Internationalization
|
||||
|
||||
**Supported Languages:**
|
||||
- `zh-cn` (Simplified Chinese) - Default
|
||||
- `en` (English) (`application/config.php` `allow_lang_list`)
|
||||
- Multi-language: disabled by default (`lang_switch_on: false`)
|
||||
- Language files in `application/*/lang/zh-cn/`
|
||||
- Language loading per controller in base classes (`loadlang()` method)
|
||||
- Recent additions: `D:\code\php\amlhc\application\admin\lang\zh-cn\command.php`, `D:\code\php\amlhc\application\admin\lang\zh-cn\history.php`
|
||||
|
||||
## CORS
|
||||
|
||||
**Allowed Origins:**
|
||||
- `localhost`, `127.0.0.1` (`application/config.php` `fastadmin.cors_request_domain`)
|
||||
- Configurable via `fastadmin.cors_request_domain`
|
||||
- API module sets CORS headers in `D:\code\php\amlhc\application\api\controller\Common.php` `_initialize()` (line 26-28): exposes `__token__` header for cross-origin token retrieval
|
||||
|
||||
## Upload Integration
|
||||
|
||||
**Upload Flow:**
|
||||
1. Client uploads to `ajax/upload` (index module) or `api/common/upload` (API module)
|
||||
2. `app\common\library\Upload` class handles validation and storage
|
||||
3. Files stored in `public/uploads/{year}{mon}{day}/{filemd5}{.suffix}`
|
||||
4. Attachment record created in `fa_attachment` table via `app\common\model\Attachment`
|
||||
5. CDN URL returned if `cdnurl` is configured
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-04-21*
|
||||
@@ -0,0 +1,178 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- PHP >= 7.4 - Server-side application code (all `application/` and `addons/`)
|
||||
- JavaScript (ES5) - Frontend client code (`public/assets/js/`)
|
||||
|
||||
**Secondary:**
|
||||
- HTML/Think Template - View templates (`application/*/view/`)
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- PHP >= 7.4.0 (required by `composer.json`)
|
||||
- Required extensions: `ext-json`, `ext-curl`, `ext-pdo`, `ext-bcmath`
|
||||
|
||||
**Package Manager:**
|
||||
- Composer - PHP dependency management; lockfile `composer.lock` present
|
||||
- npm - Frontend dependency management; `node_modules/` present
|
||||
- Lockfiles: `composer.lock` (present), `package-lock.json` (not detected)
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- ThinkPHP 5.x (dev-master from `https://gitee.com/fastadminnet/framework.git`) - PHP MVC framework, the foundation of the entire application
|
||||
- FastAdmin 1.6.1 - Admin backend framework built on ThinkPHP + Bootstrap; actual internal version `1.6.2.20260323` (from `application/config.php` `fastadmin.version`)
|
||||
|
||||
**Frontend:**
|
||||
- RequireJS 2.x - AMD module loader for JavaScript (`public/assets/js/require-backend.js`, `require-frontend.js`)
|
||||
- Bootstrap 3.4.1 (via `fastadmin-bootstrap`) - UI component framework
|
||||
- jQuery 3.7.1 - DOM manipulation and AJAX
|
||||
- AdminLTE - Admin dashboard theme (referenced in `require-backend.js` paths)
|
||||
|
||||
**Testing:**
|
||||
- Not detected - No test framework configured (no PHPUnit, no `tests/` directory)
|
||||
|
||||
**Build/Dev:**
|
||||
- Grunt 1.5.3 - Task runner for frontend asset build
|
||||
- requirejs optimizer (r.js) - JS/CSS minification via custom `application/admin/command/Min/r`
|
||||
- uglify - JavaScript minification
|
||||
- parse-config-file + jsonminify - RequireJS config parsing during build
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- `topthink/framework` dev-master - ThinkPHP core framework (Gitee mirror)
|
||||
- `topthink/think-captcha` ^1.0.9 - CAPTCHA image generation
|
||||
- `topthink/think-queue` 1.1.6 - Redis-backed job queue system
|
||||
- `topthink/think-helper` ^1.0.7 - ThinkPHP utility helpers
|
||||
- `fastadminnet/fastadmin-addons` ~1.4.0 - Plugin/addon system
|
||||
- `fastadminnet/fastadmin-mailer` ^2.0.0 - Email sending
|
||||
|
||||
**Infrastructure:**
|
||||
- `guzzlehttp/guzzle` ^7.10 - HTTP client for external API requests (used in `application/index/controller/Index.php` to scrape `macaumarksix.com`)
|
||||
- `overtrue/pinyin` ^3.0 - Chinese Pinyin conversion
|
||||
- `overtrue/wechat` ^4.6 - WeChat SDK integration
|
||||
- `phpoffice/phpspreadsheet` ^1.29.1 - Excel/CSV import-export (used in `application/admin/library/traits/Backend.php` `import()` method)
|
||||
|
||||
**Frontend Libraries (via npm):**
|
||||
- `fastadmin-bootstraptable` ^1.11.12 - Data table with search/sort/pagination
|
||||
- `fastadmin-layer` ^3.5.6 - Modal/overlay dialogs
|
||||
- `fastadmin-selectpage` ^1.1.1 - Select with autocomplete
|
||||
- `fastadmin-nicevalidator` ^1.1.6 - Form validation
|
||||
- `eonasdan-bootstrap-datetimepicker` ^4.17.49 - Date/time picker
|
||||
- `bootstrap-daterangepicker` ~2.1.25 - Date range picker
|
||||
- `bootstrap-select` ^1.13.18 - Enhanced select dropdown
|
||||
- `jstree` ~3.3.2 - Tree view component
|
||||
- `font-awesome` ^4.6.1 - Icon font
|
||||
- `moment` ^2.10 - Date manipulation
|
||||
- `art-template` (via fastadmin-arttemplate) ^3.1.4 - Template engine
|
||||
- `toastr` ~2.1.3 - Notification toasts
|
||||
- `jquery-slimscroll` ~1.3.8 - Custom scrollbar
|
||||
- `jquery.cookie` ~1.4.1 - Cookie utility
|
||||
- `sortablejs` ^1.12.0 - Drag and drop sorting
|
||||
- `fastadmin-dragsort` ^1.0.5 - Drag sort plugin
|
||||
- `fastadmin-addtabs` ^1.0.8 - Multi-tab navigation
|
||||
- `fastadmin-citypicker` ^1.3.6 - City selector
|
||||
- `fastadmin-cxselect` ^1.4.0 - Cascading select
|
||||
- `bootstrap-slider` ^11.0.2 - Range slider
|
||||
- `tableexport.jquery.plugin` ^1.20 - Table export to Excel/CSV/PDF
|
||||
- `require-css` ~0.1.8 - CSS loading via RequireJS
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- Configuration via PHP arrays in `application/config.php`, `application/database.php`, `application/extra/*.php`
|
||||
- Environment variable override via `think\Env` class (e.g., `Env::get('database.hostname', '127.0.0.1')`)
|
||||
- `.env` file present for environment-specific overrides (secrets not inspected)
|
||||
- Key configs: `site.php` (site settings, email SMTP), `upload.php` (file upload rules), `queue.php` (Redis connection), `addons.php` (plugin hooks/routes)
|
||||
|
||||
**Build:**
|
||||
- `Gruntfile.js` - Build orchestration
|
||||
- Build tasks: `deploy` (copy libs from node_modules to `public/assets/libs/`), `frontend:js`, `backend:js`, `frontend:css`, `backend:css` (RequireJS r.js optimization)
|
||||
- Default task: `grunt` = `['deploy', 'frontend:js', 'backend:js', 'frontend:css', 'backend:css']`
|
||||
- Output: `public/assets/js/require-backend.min.js`, `require-frontend.min.js`, etc.
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- PHP >= 7.4 with extensions: json, curl, pdo, bcmath
|
||||
- MySQL database (utf8mb4 charset)
|
||||
- Redis (for queue system)
|
||||
- Node.js + npm (for frontend dependencies and Grunt build)
|
||||
- Composer (for PHP dependencies)
|
||||
- `.env` file configured with database credentials
|
||||
|
||||
**Production:**
|
||||
- Self-hosted PHP deployment (no Docker detected)
|
||||
- Apache or Nginx web server (with URL rewriting for ThinkPHP PATH_INFO)
|
||||
- MySQL database with `fa_` table prefix
|
||||
- Redis for queue processing (`think-queue`)
|
||||
- `public/` directory as web root
|
||||
- `application/admin/command/Install/install.lock` present - indicates installation completed
|
||||
|
||||
## Module Structure
|
||||
|
||||
The application uses ThinkPHP multi-module architecture:
|
||||
|
||||
| Module | Purpose | Location |
|
||||
|--------|---------|----------|
|
||||
| `admin` | Backend admin panel | `application/admin/` |
|
||||
| `index` | Frontend website | `application/index/` |
|
||||
| `api` | REST API endpoints | `application/api/` |
|
||||
| `common` | Shared code | `application/common/` |
|
||||
|
||||
## Recently Added Components
|
||||
|
||||
**Num Controller/Model** - New "数字波色" (number color/wave) feature:
|
||||
- Controller: `D:\code\php\amlhc\application\admin\controller\Num.php` - Returns number-to-color mapping via `getColorMap()` API endpoint
|
||||
- Model: `D:\code\php\amlhc\application\admin\model\Num.php` - Simple model for `fa_num` table, no timestamp fields
|
||||
- Used by `history.js` frontend to render colored number balls in lottery result tables
|
||||
|
||||
**Command Controller/Model** - Online CLI command management:
|
||||
- Controller: `D:\code\php\amlhc\application\admin\controller\Command.php` - CRUD for CLI commands (crud/menu/min/api generation and execution)
|
||||
- Model: `D:\code\php\amlhc\application\admin\model\Command.php` - Tracks command execution history with integer timestamps, status tracking
|
||||
- Validate: `D:\code\php\amlhc\application\admin\validate\Command.php` - Empty validation rules
|
||||
- Addon: `D:\code\php\amlhc\addons\command\` - Plugin wrapper with menu installation via `addons\command\Command.php`
|
||||
- Output library: `D:\code\php\amlhc\addons\command\library\Output.php` - Extends `\think\console\Output` to capture command output
|
||||
- Frontend: `D:\code\php\amlhc\public\assets\js\backend\command.js` - Complex UI with dynamic form, table field selection, relation config
|
||||
|
||||
**History Controller/Model** - Lottery history records:
|
||||
- Controller: `D:\code\php\amlhc\application\admin\controller\History.php` - Standard CRUD (inherits Backend trait)
|
||||
- Model: `D:\code\php\amlhc\application\admin\model\History.php` - Simple model for `fa_history` table, no timestamp fields
|
||||
- Validate: `D:\code\php\amlhc\application\admin\validate\History.php` - Empty validation rules
|
||||
- Frontend: `D:\code\php\amlhc\public\assets\js\backend\history.js` - Custom colored ball rendering, loads color map from Num API at `num/getColorMap`
|
||||
- Index controller: `D:\code\php\amlhc\application\index\controller\Index.php` - `get_history()` scrapes `https://history.macaumarksix.com/history/macaujc2/y/2026` using Guzzle
|
||||
|
||||
**SQL Schema:** `D:\code\php\amlhc\sql\macaujc_history.sql` - Defines `macaujc_history` table with full lottery record fields (expect, open_code, wave, zodiac, odd_even, big_small, etc.)
|
||||
|
||||
## CLI Commands
|
||||
|
||||
Registered in `application/command.php`:
|
||||
- `Crud` - Code generator for CRUD operations (`app\admin\command\Crud`)
|
||||
- `Menu` - Menu generator (`app\admin\command\Menu`)
|
||||
- `Install` - Installation wizard (`app\admin\command\Install`)
|
||||
- `Min` - Asset minification (`app\admin\command\Min`)
|
||||
- `Addon` - Addon management (`app\admin\command\Addon`)
|
||||
- `Api` - API documentation generator (`app\admin\command\Api`)
|
||||
|
||||
## Custom Extensions (extend/fast/)
|
||||
|
||||
Located in `extend/fast/`:
|
||||
- `Auth.php` - Authentication and permission library
|
||||
- `Date.php` - Date/time utilities
|
||||
- `Form.php` - Form builder/generator (largest utility)
|
||||
- `Http.php` - HTTP request utilities
|
||||
- `Pinyin.php` - Chinese pinyin wrapper
|
||||
- `Random.php` - Random string generation
|
||||
- `Rsa.php` - RSA encryption utilities
|
||||
- `Tree.php` - Tree data structure utilities
|
||||
- `Version.php` - Version comparison utilities
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-04-21*
|
||||
@@ -0,0 +1,625 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
D:\code\php\amlhc\
|
||||
├── application/ # ThinkPHP application code
|
||||
│ ├── admin/ # Backend admin module
|
||||
│ │ ├── behavior/ # Admin behavior hooks
|
||||
│ │ ├── command/ # CLI commands
|
||||
│ │ │ ├── Addon.php # Addon command
|
||||
│ │ │ ├── Api.php # API doc generator
|
||||
│ │ │ │ ├── lang/zh-cn.php
|
||||
│ │ │ │ └── library/ # Builder.php, Extractor.php
|
||||
│ │ │ ├── Crud.php # CRUD code generator
|
||||
│ │ │ │ └── stubs/ # Template stubs
|
||||
│ │ │ ├── Install/ # Installation wizard
|
||||
│ │ │ │ ├── install.lock
|
||||
│ │ │ │ └── zh-cn.php
|
||||
│ │ │ ├── Menu.php # Menu generator
|
||||
│ │ │ └── Min.php # JS/CSS minifier
|
||||
│ │ ├── controller/ # Admin controllers (18 total)
|
||||
│ │ │ ├── Addon.php # Plugin management
|
||||
│ │ │ ├── Ajax.php # Shared AJAX endpoints
|
||||
│ │ │ ├── Category.php # Category management
|
||||
│ │ │ ├── Command.php # Online command execution
|
||||
│ │ │ ├── Dashboard.php # Dashboard statistics
|
||||
│ │ │ ├── History.php # Lottery history management
|
||||
│ │ │ ├── Index.php # Admin login/home/logout
|
||||
│ │ │ ├── Num.php # Lottery number/color mapping
|
||||
│ │ │ ├── auth/
|
||||
│ │ │ │ ├── Admin.php # Admin user management
|
||||
│ │ │ │ ├── Adminlog.php # Admin operation log
|
||||
│ │ │ │ ├── Group.php # Role group management
|
||||
│ │ │ │ └── Rule.php # Permission rule management
|
||||
│ │ │ ├── general/
|
||||
│ │ │ │ ├── Attachment.php # File attachment management
|
||||
│ │ │ │ ├── Config.php # System configuration
|
||||
│ │ │ │ └── Profile.php # Admin profile management
|
||||
│ │ │ └── user/
|
||||
│ │ │ ├── Group.php # User group management
|
||||
│ │ │ ├── Rule.php # User rule management
|
||||
│ │ │ └── User.php # Member management
|
||||
│ │ ├── lang/zh-cn/ # Admin language (17 files)
|
||||
│ │ │ ├── addon.php # Addon language
|
||||
│ │ │ ├── ajax.php # Ajax language
|
||||
│ │ │ ├── category.php # Category language
|
||||
│ │ │ ├── command.php # Command language
|
||||
│ │ │ ├── config.php # Config language
|
||||
│ │ │ ├── dashboard.php # Dashboard language
|
||||
│ │ │ ├── history.php # History language
|
||||
│ │ │ ├── index.php # Login/home language
|
||||
│ │ │ ├── auth/
|
||||
│ │ │ │ ├── admin.php # Admin management language
|
||||
│ │ │ │ ├── group.php # Group management language
|
||||
│ │ │ │ └── rule.php # Rule management language
|
||||
│ │ │ ├── general/
|
||||
│ │ │ │ ├── attachment.php # Attachment language
|
||||
│ │ │ │ ├── config.php # Config language
|
||||
│ │ │ │ └── profile.php # Profile language
|
||||
│ │ │ └── user/
|
||||
│ │ │ ├── group.php # User group language
|
||||
│ │ │ ├── rule.php # User rule language
|
||||
│ │ │ └── user.php # User management language
|
||||
│ │ ├── library/ # Admin-specific libraries
|
||||
│ │ │ └── traits/
|
||||
│ │ │ └── Backend.php # CRUD trait (index/add/edit/del/etc.)
|
||||
│ │ │ └── Auth.php # Admin auth (extends fast\Auth)
|
||||
│ │ ├── model/ # Admin models (11 files)
|
||||
│ │ │ ├── Admin.php # Admin user model
|
||||
│ │ │ ├── AdminLog.php # Admin log model
|
||||
│ │ │ ├── AuthGroup.php # Auth group model
|
||||
│ │ │ ├── AuthGroupAccess.php # Admin-group pivot model
|
||||
│ │ │ ├── AuthRule.php # Auth rule model
|
||||
│ │ │ ├── Command.php # Command execution log model
|
||||
│ │ │ ├── History.php # Lottery history model
|
||||
│ │ │ ├── Num.php # Lottery number model
|
||||
│ │ │ ├── User.php # Admin-side user model (with hooks)
|
||||
│ │ │ ├── UserGroup.php # User group model
|
||||
│ │ │ └── UserRule.php # User rule model
|
||||
│ │ ├── validate/ # Admin validators (8 files)
|
||||
│ │ │ ├── Admin.php # Admin user validation
|
||||
│ │ │ ├── AuthRule.php # Auth rule validation
|
||||
│ │ │ ├── Category.php # Category validation
|
||||
│ │ │ ├── Command.php # Command validation
|
||||
│ │ │ ├── History.php # History validation (empty rules)
|
||||
│ │ │ ├── User.php # User validation
|
||||
│ │ │ ├── UserGroup.php # User group validation
|
||||
│ │ │ └── UserRule.php # User rule validation
|
||||
│ │ └── view/ # Admin view templates (48 HTML files)
|
||||
│ │ ├── addon/ # Addon views
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── config.html
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── auth/
|
||||
│ │ │ ├── admin/
|
||||
│ │ │ │ ├── add.html
|
||||
│ │ │ │ ├── edit.html
|
||||
│ │ │ │ └── index.html
|
||||
│ │ │ ├── adminlog/
|
||||
│ │ │ │ ├── detail.html
|
||||
│ │ │ │ └── index.html
|
||||
│ │ │ ├── group/
|
||||
│ │ │ │ ├── add.html
|
||||
│ │ │ │ ├── edit.html
|
||||
│ │ │ │ └── index.html
|
||||
│ │ │ └── rule/
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── edit.html
|
||||
│ │ │ ├── index.html
|
||||
│ │ │ └── tpl.html
|
||||
│ │ ├── category/
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── edit.html
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── command/
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── detail.html
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── common/ # Shared admin partials
|
||||
│ │ │ ├── control.html
|
||||
│ │ │ ├── header.html
|
||||
│ │ │ ├── menu.html
|
||||
│ │ │ ├── meta.html
|
||||
│ │ │ └── script.html
|
||||
│ │ ├── dashboard/
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── general/
|
||||
│ │ │ ├── attachment/
|
||||
│ │ │ │ ├── add.html
|
||||
│ │ │ │ ├── edit.html
|
||||
│ │ │ │ ├── index.html
|
||||
│ │ │ │ └── select.html
|
||||
│ │ │ └── config/
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── history/
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── edit.html
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── index/
|
||||
│ │ │ ├── index.html # Admin home page
|
||||
│ │ │ └── login.html # Admin login page
|
||||
│ │ ├── layout/
|
||||
│ │ │ └── default.html # Main admin layout
|
||||
│ │ └── user/
|
||||
│ │ ├── group/
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── edit.html
|
||||
│ │ │ └── index.html
|
||||
│ │ ├── rule/
|
||||
│ │ │ ├── add.html
|
||||
│ │ │ ├── edit.html
|
||||
│ │ │ └── index.html
|
||||
│ │ └── user/
|
||||
│ │ ├── edit.html
|
||||
│ │ └── index.html
|
||||
│ ├── api/ # REST API module
|
||||
│ │ ├── controller/ # API controllers (8 files)
|
||||
│ │ │ ├── Common.php # Common API (upload endpoint)
|
||||
│ │ │ ├── Demo.php # Demo API endpoints
|
||||
│ │ │ ├── Ems.php # Email verification API
|
||||
│ │ │ ├── Index.php # API homepage
|
||||
│ │ │ ├── Sms.php # SMS verification API
|
||||
│ │ │ ├── Token.php # Token management API
|
||||
│ │ │ ├── User.php # User auth/profile API
|
||||
│ │ │ └── Validate.php # Validation testing API
|
||||
│ │ ├── lang/zh-cn/ # API language packs
|
||||
│ │ └── library/ # API-specific libraries
|
||||
│ ├── common/ # Shared code across modules
|
||||
│ │ ├── behavior/ # Shared behavior hooks
|
||||
│ │ │ └── Common.php
|
||||
│ │ ├── controller/ # Base controllers (3 files)
|
||||
│ │ │ ├── Backend.php # Admin base controller
|
||||
│ │ │ ├── Frontend.php # Frontend base controller
|
||||
│ │ │ └── Api.php # API base controller
|
||||
│ │ ├── exception/ # Custom exceptions
|
||||
│ │ │ └── UploadException.php # Upload failure exception
|
||||
│ │ ├── lang/zh-cn/ # Shared language packs
|
||||
│ │ ├── library/ # Shared libraries (10 files)
|
||||
│ │ │ ├── Auth.php # User authentication (token-based)
|
||||
│ │ │ ├── Email.php # Email sending (PHPMailer)
|
||||
│ │ │ ├── Ems.php # Email verification code
|
||||
│ │ │ ├── Log.php # Logging utility
|
||||
│ │ │ ├── Menu.php # Menu generation
|
||||
│ │ │ ├── Security.php # Security utilities
|
||||
│ │ │ ├── Sms.php # SMS verification code
|
||||
│ │ │ ├── Token.php # Token storage manager
|
||||
│ │ │ ├── Upload.php # File upload handler
|
||||
│ │ │ └── token/
|
||||
│ │ │ ├── Driver.php # Token driver interface
|
||||
│ │ │ └── driver/
|
||||
│ │ │ ├── Mysql.php # MySQL token driver
|
||||
│ │ │ └── Redis.php # Redis token driver
|
||||
│ │ ├── model/ # Shared models (12 files)
|
||||
│ │ │ ├── Area.php # Province/city/area data
|
||||
│ │ │ ├── Attachment.php # File attachment model
|
||||
│ │ │ ├── Category.php # Category model
|
||||
│ │ │ ├── Config.php # System config model
|
||||
│ │ │ ├── Em s.php # Email verification log
|
||||
│ │ │ ├── MoneyLog.php # User money change log
|
||||
│ │ │ ├── ScoreLog.php # User score change log
|
||||
│ │ │ ├── Sms.php # SMS verification log
|
||||
│ │ │ ├── User.php # User model
|
||||
│ │ │ ├── UserGroup.php # User group model
|
||||
│ │ │ ├── UserRule.php # User rule model
|
||||
│ │ │ └── Version.php # Version info model
|
||||
│ │ └── view/tpl/ # Shared templates
|
||||
│ │ ├── dispatch_jump.tpl # Redirect template
|
||||
│ │ └── think_exception.tpl # Exception page template
|
||||
│ ├── index/ # Frontend (user-facing) module
|
||||
│ │ ├── controller/ # Frontend controllers (3 files)
|
||||
│ │ │ ├── Ajax.php # Frontend AJAX (lang, icon, upload)
|
||||
│ │ │ ├── Index.php # Homepage + lottery scraping
|
||||
│ │ │ └── User.php # Member center (login/register/profile)
|
||||
│ │ ├── lang/ # Frontend language packs
|
||||
│ │ │ ├── en/
|
||||
│ │ │ └── zh-cn/
|
||||
│ │ └── view/
|
||||
│ │ ├── common/
|
||||
│ │ ├── index/
|
||||
│ │ │ └── index.html # Homepage
|
||||
│ │ ├── layout/
|
||||
│ │ │ └── default.html # Frontend layout
|
||||
│ │ └── user/
|
||||
│ ├── extra/ # Extra config files
|
||||
│ │ ├── addons.php # Addon hooks and routes
|
||||
│ │ ├── queue.php # Queue configuration
|
||||
│ │ ├── site.php # Site configuration
|
||||
│ │ └── upload.php # Upload configuration
|
||||
│ ├── common.php # Global helper functions
|
||||
│ ├── config.php # Main ThinkPHP config
|
||||
│ ├── database.php # Database connection config
|
||||
│ ├── command.php # CLI command registry
|
||||
│ └── route.php # Route definitions
|
||||
├── extend/ # Custom extension classes
|
||||
│ └── fast/ # FastAdmin helper classes
|
||||
│ ├── Auth.php # RBAC permission checker
|
||||
│ ├── Date.php # Date formatting utilities
|
||||
│ ├── Form.php # Form builder
|
||||
│ ├── Http.php # HTTP client utility
|
||||
│ ├── Pinyin.php # Chinese pinyin conversion
|
||||
│ ├── Random.php # Random string generation
|
||||
│ ├── Rsa.php # RSA encryption
|
||||
│ ├── Tree.php # Tree data structure
|
||||
│ └── Version.php # Version comparison
|
||||
├── addons/ # Plugin/addon directory
|
||||
├── public/ # Web root
|
||||
│ ├── index.php # Web entry point
|
||||
│ └── assets/ # Static assets (JS, CSS, images)
|
||||
├── runtime/ # Runtime files (cache, logs, temp)
|
||||
├── think # CLI entry point
|
||||
├── thinkphp/ # ThinkPHP framework core
|
||||
├── vendor/ # Composer dependencies
|
||||
└── sql/ # SQL migration files
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
### `application/admin/` — Backend Administration
|
||||
- Purpose: Complete backend management system with RBAC
|
||||
- Contains: 18 controllers, 11 models, 8 validators, 48 view templates, 17 language files
|
||||
- Subdirectories:
|
||||
- `controller/auth/` — Admin RBAC (admin users, groups, rules, logs)
|
||||
- `controller/general/` — System utilities (attachments, config, profile)
|
||||
- `controller/user/` — Frontend user management from admin panel
|
||||
- `command/` — CLI code generators (CRUD, menu, min, api, addon)
|
||||
- `library/` — Auth class + Backend CRUD trait
|
||||
- `view/layout/default.html` — Main admin layout template
|
||||
|
||||
### `application/index/` — Frontend User Portal
|
||||
- Purpose: Public-facing website and member center
|
||||
- Contains: 3 controllers (Index, User, Ajax)
|
||||
- Custom domain files:
|
||||
- `controller/Index.php::get_history()` — Lottery data scraping from macaumarksix.com
|
||||
- Uses `\GuzzleHttp\Client` to fetch lottery results
|
||||
- Writes to `fa_history` table via raw `Db` queries (not ORM)
|
||||
|
||||
### `application/api/` — REST API
|
||||
- Purpose: API for mobile/third-party clients
|
||||
- Contains: 8 controllers, all extend `app\common\controller\Api`
|
||||
- Standard response format: `{code, msg, time, data}`
|
||||
|
||||
### `application/common/` — Shared Code
|
||||
- Purpose: Base controllers, shared models, utility libraries
|
||||
- Contains: 3 base controllers, 12 shared models, 10 libraries, 1 exception
|
||||
- Token driver pattern: pluggable MySQL or Redis storage
|
||||
|
||||
### `extend/fast/` — Framework Utilities
|
||||
- Purpose: FastAdmin core utility classes (not in composer autoload)
|
||||
- Contains: Tree (hierarchical data), Auth (RBAC base), Date, Random, Http, Form, Rsa, Pinyin, Version
|
||||
|
||||
## Admin Controller → Model → View → Validate → Lang Mapping
|
||||
|
||||
### auth/admin — 管理员管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/auth/Admin.php` |
|
||||
| Model | `application/admin/model/Admin.php` |
|
||||
| Model (pivot) | `application/admin/model/AuthGroupAccess.php` |
|
||||
| Model (group) | `application/admin/model/AuthGroup.php` |
|
||||
| View (list) | `application/admin/view/auth/admin/index.html` |
|
||||
| View (add) | `application/admin/view/auth/admin/add.html` |
|
||||
| View (edit) | `application/admin/view/auth/admin/edit.html` |
|
||||
| Validate | `application/admin/validate/Admin.php` |
|
||||
| Lang | `application/admin/lang/zh-cn/auth/admin.php` |
|
||||
|
||||
### auth/adminlog — 管理员日志
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/auth/Adminlog.php` |
|
||||
| Model | `application/admin/model/AdminLog.php` |
|
||||
| View (list) | `application/admin/view/auth/adminlog/index.html` |
|
||||
| View (detail) | `application/admin/view/auth/adminlog/detail.html` |
|
||||
| Validate | *(none — read-only)* |
|
||||
| Lang | *(uses auth/admin.php)* |
|
||||
|
||||
### auth/group — 角色组管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/auth/Group.php` |
|
||||
| Model | `application/admin/model/AuthGroup.php` |
|
||||
| View (list) | `application/admin/view/auth/group/index.html` |
|
||||
| View (add) | `application/admin/view/auth/group/add.html` |
|
||||
| View (edit) | `application/admin/view/auth/group/edit.html` |
|
||||
| Validate | *(none — uses inline validation)* |
|
||||
| Lang | `application/admin/lang/zh-cn/auth/group.php` |
|
||||
|
||||
### auth/rule — 权限规则管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/auth/Rule.php` |
|
||||
| Model | `application/admin/model/AuthRule.php` |
|
||||
| View (list) | `application/admin/view/auth/rule/index.html` |
|
||||
| View (add) | `application/admin/view/auth/rule/add.html` |
|
||||
| View (edit) | `application/admin/view/auth/rule/edit.html` |
|
||||
| View (template) | `application/admin/view/auth/rule/tpl.html` |
|
||||
| Validate | `application/admin/validate/AuthRule.php` |
|
||||
| Lang | `application/admin/lang/zh-cn/auth/rule.php` |
|
||||
|
||||
### general/attachment — 附件管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/general/Attachment.php` |
|
||||
| Model | `application/common/model/Attachment.php` |
|
||||
| View (list) | `application/admin/view/general/attachment/index.html` |
|
||||
| View (add) | `application/admin/view/general/attachment/add.html` |
|
||||
| View (edit) | `application/admin/view/general/attachment/edit.html` |
|
||||
| View (select) | `application/admin/view/general/attachment/select.html` |
|
||||
| Validate | *(none — uses model validation)* |
|
||||
| Lang | `application/admin/lang/zh-cn/general/attachment.php` |
|
||||
|
||||
### general/config — 系统配置
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/general/Config.php` |
|
||||
| Model | `application/common/model/Config.php` |
|
||||
| View (list) | `application/admin/view/general/config/index.html` |
|
||||
| Validate | *(none — inline validation)* |
|
||||
| Lang | `application/admin/lang/zh-cn/general/config.php` |
|
||||
|
||||
### general/profile — 个人资料
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/general/Profile.php` |
|
||||
| Model | `application/admin/model/Admin.php` + `application/admin/model/AdminLog.php` |
|
||||
| View (list) | `application/admin/view/general/profile/index.html` |
|
||||
| Validate | *(none)* |
|
||||
| Lang | `application/admin/lang/zh-cn/general/profile.php` |
|
||||
|
||||
### user/user — 会员管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/user/User.php` |
|
||||
| Model | `application/admin/model/User.php` |
|
||||
| Model (group) | `application/admin/model/UserGroup.php` |
|
||||
| View (list) | `application/admin/view/user/user/index.html` |
|
||||
| View (edit) | `application/admin/view/user/user/edit.html` |
|
||||
| Validate | `application/admin/validate/User.php` |
|
||||
| Lang | `application/admin/lang/zh-cn/user/user.php` |
|
||||
|
||||
### user/group — 会员组管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/user/Group.php` |
|
||||
| Model | `application/admin/model/UserGroup.php` |
|
||||
| View (list) | `application/admin/view/user/group/index.html` |
|
||||
| View (add) | `application/admin/view/user/group/add.html` |
|
||||
| View (edit) | `application/admin/view/user/group/edit.html` |
|
||||
| Validate | `application/admin/validate/UserGroup.php` |
|
||||
| Lang | `application/admin/lang/zh-cn/user/group.php` |
|
||||
|
||||
### user/rule — 会员规则管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/user/Rule.php` |
|
||||
| Model | `application/admin/model/UserRule.php` |
|
||||
| View (list) | `application/admin/view/user/rule/index.html` |
|
||||
| View (add) | `application/admin/view/user/rule/add.html` |
|
||||
| View (edit) | `application/admin/view/user/rule/edit.html` |
|
||||
| Validate | `application/admin/validate/UserRule.php` |
|
||||
| Lang | `application/admin/lang/zh-cn/user/rule.php` |
|
||||
|
||||
### history — 彩票历史记录
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/History.php` |
|
||||
| Model | `application/admin/model/History.php` |
|
||||
| View (list) | `application/admin/view/history/index.html` |
|
||||
| View (add) | `application/admin/view/history/add.html` |
|
||||
| View (edit) | `application/admin/view/history/edit.html` |
|
||||
| Validate | `application/admin/validate/History.php` (empty rules) |
|
||||
| Lang | `application/admin/lang/zh-cn/history.php` |
|
||||
|
||||
### num — 数字波色
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Num.php` |
|
||||
| Model | `application/admin/model/Num.php` |
|
||||
| View | *(no dedicated views — uses trait defaults)* |
|
||||
| Validate | *(none)* |
|
||||
| Lang | *(none — uses generic)* |
|
||||
|
||||
### category — 分类管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Category.php` |
|
||||
| Model | `application/common/model/Category.php` |
|
||||
| View (list) | `application/admin/view/category/index.html` |
|
||||
| View (add) | `application/admin/view/category/add.html` |
|
||||
| View (edit) | `application/admin/view/category/edit.html` |
|
||||
| Validate | `application/admin/validate/Category.php` (empty rules) |
|
||||
| Lang | `application/admin/lang/zh-cn/category.php` |
|
||||
|
||||
### command — 在线命令
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Command.php` |
|
||||
| Model | `application/admin/model/Command.php` |
|
||||
| View (list) | `application/admin/view/command/index.html` |
|
||||
| View (add) | `application/admin/view/command/add.html` |
|
||||
| View (detail) | `application/admin/view/command/detail.html` |
|
||||
| Validate | `application/admin/validate/Command.php` (empty rules) |
|
||||
| Lang | `application/admin/lang/zh-cn/command.php` |
|
||||
|
||||
### dashboard — 控制台
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Dashboard.php` |
|
||||
| Model | *(multiple — Admin, User, Attachment, Category via direct queries)* |
|
||||
| View (list) | `application/admin/view/dashboard/index.html` |
|
||||
| Validate | *(none)* |
|
||||
| Lang | `application/admin/lang/zh-cn/dashboard.php` |
|
||||
|
||||
### addon — 插件管理
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Addon.php` |
|
||||
| Model | *(none — uses Service class directly)* |
|
||||
| View (list) | `application/admin/view/addon/index.html` |
|
||||
| View (add) | `application/admin/view/addon/add.html` |
|
||||
| View (config) | `application/admin/view/addon/config.html` |
|
||||
| Validate | *(none)* |
|
||||
| Lang | `application/admin/lang/zh-cn/addon.php` |
|
||||
|
||||
### index (admin) — 后台首页/登录
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Index.php` |
|
||||
| Model | `application/admin/model/Admin.php` |
|
||||
| View (home) | `application/admin/view/index/index.html` |
|
||||
| View (login) | `application/admin/view/index/login.html` |
|
||||
| Validate | *(inline in login method)* |
|
||||
| Lang | `application/admin/lang/zh-cn/index.php` |
|
||||
|
||||
### ajax (admin) — 通用异步接口
|
||||
| Layer | File |
|
||||
|-------|------|
|
||||
| Controller | `application/admin/controller/Ajax.php` |
|
||||
| Model | *(various — Attachment, Category, Area via Db queries)* |
|
||||
| View | *(none — JSON responses only)* |
|
||||
| Validate | *(none)* |
|
||||
| Lang | `application/admin/lang/zh-cn/ajax.php` |
|
||||
|
||||
## Custom Domain Files (Lottery Feature)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `application/index/controller/Index.php` | Homepage + `get_history()` scrapes Macau lottery data from macaumarksix.com |
|
||||
| `application/admin/controller/History.php` | Admin CRUD for lottery history (read-only display) |
|
||||
| `application/admin/controller/Num.php` | Num→color mapping API via `getColorMap()` |
|
||||
| `application/admin/model/History.php` | History model: table `fa_history`, fields `expect`, `openTime`, `num1`~`num7` |
|
||||
| `application/admin/model/Num.php` | Num model: table `fa_num`, fields `num`, `color` |
|
||||
| `application/admin/validate/History.php` | Empty validator (no validation rules defined) |
|
||||
| `application/admin/view/history/index.html` | List view with add/edit buttons hidden |
|
||||
| `application/admin/view/history/add.html` | Add form template (unused) |
|
||||
| `application/admin/view/history/edit.html` | Edit form template (unused) |
|
||||
| `application/admin/lang/zh-cn/history.php` | Chinese language strings for history module |
|
||||
|
||||
## Common Models (Shared Across Modules)
|
||||
|
||||
| File | Table | Purpose |
|
||||
|------|-------|---------|
|
||||
| `application/common/model/User.php` | `fa_user` | Frontend user with money/score log hooks |
|
||||
| `application/common/model/UserGroup.php` | `fa_user_group` | User group |
|
||||
| `application/common/model/UserRule.php` | `fa_user_rule` | User permission rule |
|
||||
| `application/common/model/Category.php` | `fa_category` | Hierarchical category system |
|
||||
| `application/common/model/Config.php` | `fa_config` | System configuration |
|
||||
| `application/common/model/Attachment.php` | `fa_attachment` | File upload metadata |
|
||||
| `application/common/model/Attachment.php` | `fa_area` | Province/city/area data |
|
||||
| `application/common/model/MoneyLog.php` | `fa_money_log` | User balance change log |
|
||||
| `application/common/model/ScoreLog.php` | `fa_score_log` | User score change log |
|
||||
| `application/common/model/Ems.php` | `fa_ems` | Email verification log |
|
||||
| `application/common/model/Sms.php` | `fa_sms` | SMS verification log |
|
||||
| `application/common/model/Version.php` | `fa_version` | Version info |
|
||||
|
||||
## API Controllers (Complete List)
|
||||
|
||||
| Controller | File | Key Methods |
|
||||
|------------|------|-------------|
|
||||
| Index | `application/api/controller/Index.php` | `index()` |
|
||||
| User | `application/api/controller/User.php` | `login()`, `mobilelogin()`, `register()`, `logout()`, `profile()`, `changeemail()`, `changemobile()`, `third()`, `resetpwd()` |
|
||||
| Token | `application/api/controller/Token.php` | Token management endpoints |
|
||||
| Ems | `application/api/controller/Ems.php` | Email verification |
|
||||
| Sms | `application/api/controller/Sms.php` | SMS verification |
|
||||
| Common | `application/api/controller/Common.php` | Shared upload endpoint |
|
||||
| Demo | `application/api/controller/Demo.php` | Demo/test endpoints |
|
||||
| Validate | `application/api/controller/Validate.php` | Validation testing |
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
- Controllers: PascalCase (`AuthRule.php`, `Dashboard.php`, `History.php`)
|
||||
- Models: PascalCase (`AdminLog.php`, `UserGroup.php`, `AuthGroupAccess.php`)
|
||||
- Validators: PascalCase matching model name (`Admin.php`, `User.php`, `History.php`)
|
||||
- Libraries: PascalCase (`Upload.php`, `Auth.php`, `Email.php`)
|
||||
- Views: lowercase with `.html` (`index.html`, `add.html`, `edit.html`)
|
||||
- Language files: lowercase PHP (`user.php`, `category.php`)
|
||||
- Commands: PascalCase (`Crud.php`, `Menu.php`, `Min.php`)
|
||||
|
||||
**Directories:**
|
||||
- Controller subdirectories: lowercase (`auth/`, `general/`, `user/`)
|
||||
- View subdirectories: mirror controller structure (`view/auth/admin/`, `view/general/config/`)
|
||||
|
||||
**Namespaces:**
|
||||
- Controllers: `app\{module}\controller\{sub?}\{Name}`
|
||||
- Models: `app\admin\model\{Name}` or `app\common\model\{Name}`
|
||||
- Libraries: `app\admin\library\{Name}` or `app\common\library\{Name}`
|
||||
- Validators: `app\admin\validate\{Name}`
|
||||
- Commands: `app\admin\command\{Name}`
|
||||
- Extensions: `fast\{Name}` (PSR-4 from `extend/fast/`)
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New Admin Module (CRUD):**
|
||||
1. Controller: `application/admin/controller/{SubDir}/{Name}.php` — extend `app\common\controller\Backend`
|
||||
2. Model: `application/admin/model/{Name}.php` — extend `think\Model`, set `$name` to table name
|
||||
3. View: `application/admin/view/{subdir}/{name}/index.html` (list), `add.html`, `edit.html`
|
||||
4. Validate: `application/admin/validate/{Name}.php` — extend `think\Validate`
|
||||
5. Lang: `application/admin/lang/zh-cn/{subdir}/{name}.php`
|
||||
6. Generate menu: run `php think menu --controller={subdir/name}`
|
||||
|
||||
**New Admin Module (Custom Logic):**
|
||||
- Same as CRUD but override trait methods in controller as needed
|
||||
- Set `$model` property in `_initialize()` to link controller to model
|
||||
|
||||
**New API Endpoint:**
|
||||
- Controller: `application/api/controller/{Name}.php` — extend `app\common\controller\Api`
|
||||
- Use `$noNeedLogin = ['*']` for public endpoints
|
||||
- Return via `$this->success($data)` or `$this->error($msg)`
|
||||
|
||||
**New Frontend Page:**
|
||||
- Controller: `application/index/controller/{Name}.php` — extend `app\common\controller\Frontend`
|
||||
- View: `application/index/view/{name}/{action}.html`
|
||||
|
||||
**New Shared Model:**
|
||||
- `application/common/model/{Name}.php` — extend `think\Model`
|
||||
- Used by multiple modules (admin + index + api)
|
||||
|
||||
**New Library:**
|
||||
- `application/common/library/{Name}.php` for cross-module utilities
|
||||
- `application/admin/library/{Name}.php` for admin-only utilities
|
||||
- `extend/fast/{Name}.php` for framework-level utilities
|
||||
|
||||
**New CLI Command:**
|
||||
- `application/admin/command/{Name}.php` — extend `\think\console\Command`
|
||||
- Register in `application/command.php`
|
||||
|
||||
## Special Directories
|
||||
|
||||
**`runtime/`:**
|
||||
- Purpose: ThinkPHP runtime cache, logs, compiled templates
|
||||
- Generated: Yes (by ThinkPHP)
|
||||
- Committed: No
|
||||
|
||||
**`addons/`:**
|
||||
- Purpose: Plugin directory for FastAdmin addon system
|
||||
- Generated: Yes (when installing addons via admin panel)
|
||||
- Committed: No
|
||||
|
||||
**`vendor/`:**
|
||||
- Purpose: Composer dependencies
|
||||
- Generated: Yes (`composer install`)
|
||||
- Committed: No
|
||||
|
||||
**`extend/fast/`:**
|
||||
- Purpose: FastAdmin core utilities not distributed via composer
|
||||
- Committed: Yes (part of application)
|
||||
- Key classes: `Tree`, `Auth` (RBAC base), `Date`, `Random`, `Http`
|
||||
|
||||
**`public/assets/`:**
|
||||
- Purpose: Static frontend/backend assets (JS, CSS, images)
|
||||
- `public/assets/js/backend/` — Admin panel JavaScript (matches controller names)
|
||||
- `public/assets/js/frontend/` — Frontend JavaScript
|
||||
- `public/assets/js/backend/command.js` — Command module JS
|
||||
- `public/assets/js/backend/history.js` — History module JS
|
||||
|
||||
**`sql/`:**
|
||||
- Purpose: SQL migration/schema files
|
||||
- Contains database dumps and migration scripts
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-04-21*
|
||||
@@ -0,0 +1,284 @@
|
||||
# Testing Patterns
|
||||
|
||||
**Analysis Date:** 2026-04-21
|
||||
|
||||
## Test Framework
|
||||
|
||||
**Runner:**
|
||||
- PHPUnit exists in the project only as part of the ThinkPHP framework (`thinkphp/phpunit.xml`)
|
||||
- No project-level test runner configured
|
||||
|
||||
**Assertion Library:**
|
||||
- PHPUnit's built-in assertions (only in framework's own tests)
|
||||
|
||||
**No Project Tests Exist.** After thorough exploration of the entire codebase:
|
||||
|
||||
- `application/**/*.test.php` -- None found
|
||||
- `application/**/*.spec.php` -- None found
|
||||
- `tests/` directory -- Does not exist at project root
|
||||
- `phpunit.xml` -- Only exists at `thinkphp/phpunit.xml` (framework's own test suite)
|
||||
- `.idea/phpunit.xml` -- IDE config pointing to `thinkphp/phpunit.xml` (for framework testing only)
|
||||
|
||||
**Framework PHPUnit Config (`thinkphp/phpunit.xml`):**
|
||||
```xml
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="tests/mock.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false">
|
||||
<testsuites>
|
||||
<testsuite name="ThinkPHP Test Suite">
|
||||
<directory>./tests/thinkphp/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<listeners>
|
||||
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" />
|
||||
</listeners>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">./</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">tests</directory>
|
||||
<directory suffix=".php">vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
```
|
||||
This config is for testing the ThinkPHP framework itself, NOT the application code in `application/`.
|
||||
|
||||
**ThinkPHP Framework Tests (not application code):**
|
||||
- `thinkphp/tests/` contains ~50 test files testing framework internals
|
||||
- Covers: Cache, Config, Controller, DB, Debug, Exception, Hook, Lang, Loader, Log, Model, Paginate, Request, Response, Route, Session, Template, URL, Validate, View
|
||||
- Example: `thinkphp/tests/thinkphp/library/think/validateTest.php`
|
||||
|
||||
**Vendor Tests (not application code):**
|
||||
- `vendor/overtrue/socialite/tests/` -- OAuth provider tests
|
||||
- `vendor/easywechat-composer/easywechat-composer/tests/` -- Composer plugin tests
|
||||
- `vendor/pimple/pimple/.github/workflows/tests.yml` -- CI config
|
||||
- `vendor/phpoffice/phpspreadsheet/` -- No test files included in dist
|
||||
|
||||
## Test File Organization
|
||||
|
||||
**Current State:** No test files exist for application code.
|
||||
|
||||
**Recommended Structure (if tests were to be added):**
|
||||
```
|
||||
tests/
|
||||
├── bootstrap.php
|
||||
├── admin/
|
||||
│ ├── controller/
|
||||
│ │ └── UserTest.php
|
||||
│ ├── library/
|
||||
│ │ └── AuthTest.php
|
||||
│ └── validate/
|
||||
│ └── UserTest.php
|
||||
├── common/
|
||||
│ ├── controller/
|
||||
│ ├── library/
|
||||
│ │ ├── AuthTest.php
|
||||
│ │ └── UploadTest.php
|
||||
│ └── model/
|
||||
│ └── UserTest.php
|
||||
├── api/
|
||||
│ └── controller/
|
||||
│ └── UserTest.php
|
||||
├── extend/
|
||||
│ └── fast/
|
||||
│ └── RandomTest.php
|
||||
├── fixtures/
|
||||
│ └── database/
|
||||
└── TestCase.php
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
**No mocking framework in use.**
|
||||
|
||||
**Dependencies that would need mocking for testing:**
|
||||
- `think\Db` -- Database operations (query, startTrans, commit, rollback, name, table)
|
||||
- `think\Config` -- Configuration access (`Config::get()`, `Config::set()`)
|
||||
- `think\Request` -- HTTP request (`$this->request->post()`, `$this->request->isAjax()`)
|
||||
- `think\Session` -- Session management
|
||||
- `think\Cookie` -- Cookie operations
|
||||
- `think\Hook` -- Event/hook system
|
||||
- `think\Lang` -- Language/translation
|
||||
- `think\Loader` -- Class autoloading
|
||||
- `think\View` -- Template rendering
|
||||
- `fast\Tree` -- Tree data structure
|
||||
- `\app\common\library\Auth` -- Authentication singleton
|
||||
- `\app\admin\library\Auth` -- Admin authentication singleton
|
||||
- `GuzzleHttp\Client` -- HTTP client (used in `application/index/controller/Index.php`)
|
||||
|
||||
**Mocking Challenge:** The codebase relies heavily on ThinkPHP's singleton pattern (`Auth::instance()`) and static methods (`Db::name()`, `Config::get()`), making unit testing difficult without significant refactoring or a dedicated mocking framework like Mockery.
|
||||
|
||||
## Fixtures and Factories
|
||||
|
||||
**Current State:** No test fixtures or factories exist.
|
||||
|
||||
**Database fixtures would be needed for:**
|
||||
- User records (admin users, regular users)
|
||||
- Auth groups and rules
|
||||
- Categories and attachments
|
||||
- Config entries
|
||||
|
||||
## Coverage
|
||||
|
||||
**Current Coverage: 0%** -- No test coverage for any application code.
|
||||
|
||||
**Untested Modules (by priority):**
|
||||
|
||||
**High Priority (core business logic):**
|
||||
| Module | File | Functionality |
|
||||
|--------|------|---------------|
|
||||
| Common Auth | `application/common/library/Auth.php` | User registration, login, token management, password encryption, email/mobile verification |
|
||||
| Admin Auth | `application/admin/library/Auth.php` | Admin authentication, permission checking, breadcrumb generation |
|
||||
| Backend Trait | `application/admin/library/traits/Backend.php` | CRUD operations: index, add, edit, del, multi, import, recyclebin, destroy, restore |
|
||||
| Backend Controller | `application/common/controller/Backend.php` | `buildparams()` query building, `selectpage()` dropdown, data limit enforcement |
|
||||
| Upload | `application/common/library/Upload.php` | File upload, validation, chunked upload, image processing |
|
||||
| Token | `application/common/library/Token.php` | Token CRUD (MySQL/Redis drivers) |
|
||||
| Security | `application/common/library/Security.php` | XSS cleaning, input sanitization |
|
||||
|
||||
**Medium Priority (data operations):**
|
||||
| Module | File | Functionality |
|
||||
|--------|------|---------------|
|
||||
| User Model (common) | `application/common/model/User.php` | Money/score change logging, level calculation, avatar generation |
|
||||
| User Model (admin) | `application/admin/model/User.php` | Password hashing on change, money/score audit logging |
|
||||
| MoneyLog | `application/common/model/MoneyLog.php` | Financial transaction logging |
|
||||
| ScoreLog | `application/common/model/ScoreLog.php` | Score transaction logging |
|
||||
| User Controller | `application/admin/controller/user/User.php` | User management with avatar processing |
|
||||
| Command Controller | `application/admin/controller/Command.php` | Online command generation and execution |
|
||||
| Validators | `application/admin/validate/*.php` | Data validation rules for all entities |
|
||||
|
||||
**Low Priority (utilities):**
|
||||
| Module | File |
|
||||
|--------|------|
|
||||
| Global Helpers | `application/common.php` (20+ functions) |
|
||||
| Admin Helpers | `application/admin/common.php` |
|
||||
| Random | `extend/fast/Random.php` |
|
||||
| Date | `extend/fast/Date.php` |
|
||||
| Tree | `extend/fast/Tree.php` |
|
||||
| Rsa | `extend/fast/Rsa.php` |
|
||||
| Form | `extend/fast/Form.php` |
|
||||
| Http | `extend/fast/Http.php` |
|
||||
|
||||
## Code Quality Tools
|
||||
|
||||
**Static Analysis:**
|
||||
- No PHPStan, Psalm, or PHPMD configured at project level
|
||||
- PhpStorm `.idea/` directory contains transferred (inactive) configurations for PHPCS, PHPStan, and MessDetector
|
||||
- No `.php-cs-fixer.php`, `phpcs.xml`, `phpmd.xml`, or `phpstan.neon` files
|
||||
|
||||
**Linting:**
|
||||
- No ESLint, Prettier, or stylelint for frontend code
|
||||
- `package.json` exists with Grunt build tasks only (minification, no linting)
|
||||
|
||||
**CI/CD:**
|
||||
- No `.github/` directory (no GitHub Actions)
|
||||
- No `.gitlab-ci.yml`
|
||||
- No `Jenkinsfile`
|
||||
- No `.travis.yml`
|
||||
- No CI pipeline of any kind
|
||||
|
||||
**Build Tools:**
|
||||
- Grunt for CSS/JS minification (`public/assets/js/*.min.js`, `public/assets/css/*.min.css`)
|
||||
- `application/admin/command/Min.php` for asset minification
|
||||
- `application/admin/command/Crud.php` for code generation
|
||||
- `application/admin/command/Api.php` for API documentation generation
|
||||
- `application/admin/command/Menu.php` for menu generation
|
||||
- `application/admin/command/Install.php` for installation
|
||||
|
||||
**Composer Scripts:** None defined in `composer.json`
|
||||
|
||||
## Test Types
|
||||
|
||||
**Unit Tests:** Not used. No unit test files exist for any application code.
|
||||
|
||||
**Integration Tests:** Not used. No integration tests exist.
|
||||
|
||||
**E2E Tests:** Not used. No end-to-end testing framework (Selenium, Cypress, etc.) configured.
|
||||
|
||||
**Feature Tests:** Not used.
|
||||
|
||||
## Why No Tests
|
||||
|
||||
1. **FastAdmin Nature** -- This is a rapid development admin scaffold. FastAdmin projects prioritize speed over test coverage by design.
|
||||
2. **No `require-dev`** -- `composer.json` has no testing-related dev dependencies (no PHPUnit, Mockery, etc.)
|
||||
3. **No `scripts.test`** -- `composer.json` defines no test scripts
|
||||
4. **No CI/CD** -- No continuous integration pipeline to enforce test execution
|
||||
5. **Tight Coupling** -- Heavy reliance on ThinkPHP singletons and static methods makes unit testing difficult without significant refactoring
|
||||
6. **Framework Philosophy** -- ThinkPHP 5.x ecosystem does not emphasize testing as a first-class concern
|
||||
|
||||
## Adding Tests: Recommended Setup
|
||||
|
||||
**Step 1: Add dev dependencies to `composer.json`:**
|
||||
```json
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"mockery/mockery": "^1.6"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Create `phpunit.xml` at project root:**
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
stopOnFailure="false">
|
||||
<testsuites>
|
||||
<testsuite name="Application Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
```
|
||||
|
||||
**Step 3: Create `tests/bootstrap.php`:**
|
||||
```php
|
||||
<?php
|
||||
// Load ThinkPHP bootstrap
|
||||
define('APP_PATH', __DIR__ . '/../application/');
|
||||
define('ROOT_PATH', __DIR__ . '/../');
|
||||
define('RUNTIME_PATH', ROOT_PATH . 'runtime/');
|
||||
require __DIR__ . '/../thinkphp/base.php';
|
||||
|
||||
// Set testing config
|
||||
\think\Config::set('app_debug', false);
|
||||
```
|
||||
|
||||
**Step 4: Add composer script:**
|
||||
```json
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
}
|
||||
```
|
||||
|
||||
**Run Commands (after setup):**
|
||||
```bash
|
||||
composer install --dev # Install dev dependencies
|
||||
vendor/bin/phpunit # Run all tests
|
||||
vendor/bin/phpunit --coverage-html coverage/ # Generate coverage report
|
||||
composer test # Run via composer script
|
||||
```
|
||||
|
||||
## Testing Challenges Specific to This Codebase
|
||||
|
||||
1. **Singleton Auth** -- `Auth::instance()` is called directly throughout, requiring careful mock setup or refactoring to dependency injection
|
||||
2. **Static Db Calls** -- `Db::name()`, `Db::query()`, `Db::startTrans()` used everywhere instead of injected connections
|
||||
3. **Global Functions** -- `__()`, `cdnurl()`, etc. depend on ThinkPHP runtime being bootstrapped
|
||||
4. **Request Context** -- Controllers depend on `$this->request` populated by framework routing
|
||||
5. **Session/Cookie** -- Many operations depend on session/cookie state
|
||||
6. **Config Dependency** -- Heavy use of `Config::get()` makes isolation testing difficult
|
||||
7. **View Rendering** -- Some tests would need to verify HTML output from templates
|
||||
|
||||
---
|
||||
|
||||
*Testing analysis: 2026-04-21*
|
||||
Reference in New Issue
Block a user