# 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*
