This commit is contained in:
2026-04-21 23:01:55 +08:00
commit 08e56caa72
597 changed files with 159445 additions and 0 deletions
+380
View File
@@ -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*
+630
View File
@@ -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*
+599
View File
@@ -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*
+252
View File
@@ -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*
+178
View File
@@ -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*
+625
View File
@@ -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*
+284
View File
@@ -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*