方案一:原生 API 接口(JSON 輸出、統一規範)
公共輔助函數 (Common/function.php)
// 成功返回
function returnApiSuccess($msg = '', $data = []) {
exit(json_encode([
'flag' => 'Success',
'msg' => $msg,
'data' => $data,
], JSON_UNESCAPED_UNICODE));
}
// 失敗返回
function returnApiError($msg = '') {
exit(json_encode([
'flag' => 'Error',
'msg' => $msg,
], JSON_UNESCAPED_UNICODE));
}
// 系統繁忙
function returnApiErrorExample() {
returnApiError('當前系統繁忙,請稍後重試!');
}
// 參數校驗 (POST)
function checkDataPost($required = '') {
if ($required) {
foreach (explode(',', $required) as $field) {
if (!isset($_POST[$field]) || $_POST[$field] === '') {
returnApiError("$field 不能為空");
}
}
}
$data = I('post.');
unset($data['_URL_'], $data['token']);
return $data;
}
// 參數校驗 (GET)
function checkDataGet($required = '') {
if ($required) {
foreach (explode(',', $required) as $field) {
if (!isset($_GET[$field]) || $_GET[$field] === '') {
returnApiError("$field 不能為空");
}
}
}
$data = I('get.');
unset($data['_URL_'], $data['token']);
return $data;
}
控制器示例
class ApiController extends Controller {
// 獲取單個發布信息
public function getReleaseInfo() {
checkDataPost('id'); // 必傳 id
$where['id'] = I('post.id');
$field = 'id,fruit_name,high_price,low_price,address,size,weight,fruit_pic,remark';
$info = D('Release')->field($field)->where($where)->find();
if (!$info) returnApiError('未找到信息');
// 截取備註
$info['remark'] = mb_substr($info['remark'], 0, 49, 'utf-8') . '...';
// 圖片路徑拼接完整 URL
$info['fruit_pic'] = array_filter(explode(',', $info['fruit_pic']));
foreach ($info['fruit_pic'] as &$pic) {
$pic = 'https://' . $_SERVER['HTTP_HOST'] . '/Uploads/Release/' . $pic;
}
returnApiSuccess('', $info);
}
}
App 端接收示例 (JSON 解碼後)
{
"flag": "Success",
"msg": "",
"data": {
"id": "2",
"fruit_name": "蘋果",
"high_price": "8.0",
"low_price": "5.0",
"address": "天津小白樓水果市場",
"size": "2.0",
"weight": "2.0",
"remark": "急需...",
"fruit_pic": [
"https://fruit.example.com/Uploads/Release/201508/xxx.png",
"https://fruit.example.com/Uploads/Release/201508/yyy.jpg"
]
}
}
方案二:移動端自動切換模板主題(偽原生、開發效率高)
1. 移動設備檢測函數 (Common/common.php)
function is_mobile() {
// 1. 標準頭
if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true;
if (isset($_SERVER['HTTP_CLIENT']) && $_SERVER['HTTP_CLIENT'] === 'PhoneClient') return true;
if (isset($_SERVER['HTTP_VIA']) && stripos($_SERVER['HTTP_VIA'], 'wap') !== false) return true;
// 2. User-Agent 關鍵字
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$keywords = ['nokia','sony','ericsson','mot','samsung','htc','sgh','lg','sharp','sie-',
'philips','panasonic','alcatel','lenovo','iphone','ipod','blackberry',
'meizu','android','netfront','symbian','ucweb','windowsce','palm',
'operamini','operamobi','openwave','nexusone','cldc','midp','wap','mobile'];
if (preg_match('/(' . implode('|', $keywords) . ')/i', $_SERVER['HTTP_USER_AGENT'])) {
return true;
}
}
// 3. Accept 頭判斷
if (isset($_SERVER['HTTP_ACCEPT'])) {
$accept = $_SERVER['HTTP_ACCEPT'];
if (strpos($accept, 'vnd.wap.wml') !== false &&
(strpos($accept, 'text/html') === false ||
strpos($accept, 'vnd.wap.wml') < strpos($accept, 'text/html'))) {
return true;
}
}
return false;
}
2. 公共控制器基類 (Lib/Action/CommonAction.class.php)
class CommonAction extends Action {
protected function _initialize() {
if (is_mobile()) {
// 切換到 Mobile 主題
C('DEFAULT_THEME', 'Mobile');
// 可選:設置移動端專用佈局
// $this->layout = 'Layout/mobile';
}
// 其他通用初始化:權限檢查、菜單數據等
}
}
3. 目錄結構
Tpl/
├── default/ # PC 模板
│ ├── Index/
│ │ └── index.html
│ └── Layout.html
└── Mobile/ # 移動端模板
├── Index/
│ └── index.html
└── Layout.html
4. 控制器繼承
class IndexAction extends CommonAction {
public function index() {
$this->display(); // 自動渲染 Tpl/Mobile/Index/index.html (移動端) 或 Tpl/default/...
}
}
方案對比與選擇建議
| 維度 | 方案一:原生 API | 方案二:模板切換 |
| 架構模式 | 前後端分離 | 服務端渲染 (SSR) |
| App 開發 | 原生/Flutter/React Native 直接調用 | WebView/H5 混合開發 |
| 性能 | 極佳 (僅傳數據) | 一般 (傳 HTML+CSS+JS) |
| SEO | 需額外 SSR/預渲染 | 天然友好 |
| 開發效率 | 需雙端協作 | 單端模板複用 PC 邏輯 |
| 維護成本 | 接口文檔、版本管理 | 模板同步修改 |
| 推薦場景 | 成熟 App、多端複用、高併發 | 快速上線、H5 為主、資源有限 |
進階:API 版本控制與簽名驗證
// 基礎控制器增加簽名驗證
class ApiBase extends Controller {
protected function _initialize() {
// 1. 版本控制
$version = I('get.v', '1.0');
define('API_VERSION', $version);
// 2. 簽名驗證 (簡化版)
$params = $_GET;
unset($params['sign']);
ksort($params);
$signStr = http_build_query($params) . '&key=' . C('API_SECRET');
if (md5($signStr) !== I('get.sign')) {
returnApiError('簽名驗證失敗');
}
// 3. 限流 (Redis)
$ip = get_client_ip();
$key = "api_limit:$ip:" . date('YmdH');
$count = S($key) ?: 0;
if ($count > 1000) returnApiError('請求過於頻繁');
S($key, $count + 1, 3600);
}
}