一、基础设施
- route.php:58 条路由覆盖 /api/v1/**;:id 用 \d+ 约束、长路径优先
- api/controller/V1.php:响应适配为 {code,message,data}(FastAdmin 原生 {code,msg,time,data} 不兼容);
Authorization Bearer 鉴权;统一分页;业务公共 userInfo/ext/flow/notify/adminLog
- admin/command/FcInstall.php:19 张 fa_fc_* 表 + 种子数据,幂等可重跑
二、控制器 17 个
- 用户端 Auth/Home/Post/Member/Distribution/Message/Setting
- 后台端 AdminDashboard/AdminMerchant/AdminMember/AdminPost/AdminComment/
AdminDist/AdminOrder/AdminFinance/AdminRbac/AdminSetting
三、契约一致性
- 字段与小程序 common/mock/data.js 逐字段对齐,Mock 与真实接口可对拍
- 实现 BR-01~BR-12,错误文案与前端 mock 完全一致
- 详情联系方式强制脱敏,付费/会员方可解锁
四、验证
- tools/verify-api.mjs:63 端点契约 + 12 条业务规则 + 8 步双端闭环
- docs/05-后端实现规范.md
271 行
9.3 KiB
PHP
271 行
9.3 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use think\Db;
|
||
|
||
/**
|
||
* 后台管理端 · 系统设置(Module-ADM-SETTING)
|
||
* 路由:
|
||
* GET|POST /api/v1/admin/setting/base AS1 基础配置
|
||
* POST /api/v1/admin/setting/pay AS2 微信支付配置
|
||
* GET|POST /api/v1/admin/setting/fee AS3 费用配置
|
||
* GET /api/v1/admin/setting/areas AS4 区域列表
|
||
* POST /api/v1/admin/setting/area AS5 保存区域
|
||
* GET|POST /api/v1/admin/setting/ui AS6 站点 UI 配置
|
||
* POST /api/v1/admin/setting/ui/reset AS7 重置 UI 配置
|
||
* POST /api/v1/admin/upload AS8 文件上传
|
||
*/
|
||
class AdminSetting extends V1
|
||
{
|
||
/**
|
||
* AS1 基础配置
|
||
* GET → {appName,logo,servicePhone,aboutUs,shareTitle}
|
||
* POST 入参 {appName,logo,servicePhone,aboutUs,shareTitle} → 合并后的完整配置
|
||
*/
|
||
public function base()
|
||
{
|
||
if ($this->request->isPost()) {
|
||
$appName = trim((string)$this->input('appName', ''));
|
||
if ($appName === '') {
|
||
$this->fail('小程序名称不能为空');
|
||
}
|
||
$this->setCfg('base.appName', $appName);
|
||
$this->setCfg('base.logo', (string)$this->input('logo', $this->cfg('base.logo', '')));
|
||
$this->setCfg('base.servicePhone', (string)$this->input('servicePhone', $this->cfg('base.servicePhone', '400-000-0000')));
|
||
$this->setCfg('base.aboutUs', (string)$this->input('aboutUs', $this->cfg('base.aboutUs', '')));
|
||
$this->setCfg('base.shareTitle', (string)$this->input('shareTitle', $this->cfg('base.shareTitle', '')));
|
||
$this->adminLog('修改基础配置');
|
||
}
|
||
|
||
$this->ok($this->baseConfig());
|
||
}
|
||
|
||
/**
|
||
* AS2 微信支付配置
|
||
* POST /api/v1/admin/setting/pay 入参 {mchId,apiKey} → {configured:true}
|
||
*/
|
||
public function pay()
|
||
{
|
||
$mchId = trim((string)$this->input('mchId', ''));
|
||
$apiKey = trim((string)$this->input('apiKey', ''));
|
||
|
||
if ($mchId === '') {
|
||
$this->fail('微信支付商户号不能为空');
|
||
}
|
||
if ($apiKey === '') {
|
||
$this->fail('支付密钥不能为空');
|
||
}
|
||
|
||
$this->setCfg('pay.mchId', $mchId);
|
||
$this->setCfg('pay.apiKey', $apiKey);
|
||
$this->adminLog('修改微信支付配置');
|
||
|
||
$this->ok(['configured' => true]);
|
||
}
|
||
|
||
/**
|
||
* AS3 费用配置
|
||
* GET → {singlePost,freePostPerDay,memberMonth,memberQuarter,memberYear}
|
||
* POST 入参同字段 → 合并后配置;同时同步 fc_plan 价格
|
||
*/
|
||
public function fee()
|
||
{
|
||
if ($this->request->isPost()) {
|
||
$singlePost = (float)$this->input('singlePost', $this->cfg('fee.singlePost', 5));
|
||
if ($singlePost < 0) {
|
||
$this->fail('单条发布价格不能为负');
|
||
}
|
||
$freePostPerDay = (int)$this->input('freePostPerDay', $this->cfg('fee.freePostPerDay', 2));
|
||
$memberMonth = (float)$this->input('memberMonth', $this->cfg('fee.memberMonth', 19.9));
|
||
$memberQuarter = (float)$this->input('memberQuarter', $this->cfg('fee.memberQuarter', 49.9));
|
||
$memberYear = (float)$this->input('memberYear', $this->cfg('fee.memberYear', 168));
|
||
|
||
$this->setCfg('fee.singlePost', $singlePost);
|
||
$this->setCfg('fee.freePostPerDay', $freePostPerDay);
|
||
$this->setCfg('fee.memberMonth', $memberMonth);
|
||
$this->setCfg('fee.memberQuarter', $memberQuarter);
|
||
$this->setCfg('fee.memberYear', $memberYear);
|
||
|
||
Db::name('fc_plan')->where('plan_code', 'M1')->update(['price' => $memberMonth]);
|
||
Db::name('fc_plan')->where('plan_code', 'M3')->update(['price' => $memberQuarter]);
|
||
Db::name('fc_plan')->where('plan_code', 'M12')->update(['price' => $memberYear]);
|
||
|
||
$this->adminLog('修改费用配置');
|
||
}
|
||
|
||
$this->ok($this->feeConfig());
|
||
}
|
||
|
||
/**
|
||
* AS4 区域列表
|
||
* GET /api/v1/admin/setting/areas → 数组 [{id,name,sort,enabled}]
|
||
*/
|
||
public function areas()
|
||
{
|
||
$rows = Db::name('fc_area')->order('sort asc')->select();
|
||
|
||
$list = [];
|
||
foreach ($rows as $row) {
|
||
$list[] = [
|
||
'id' => (int)$row['id'],
|
||
'name' => (string)$row['name'],
|
||
'sort' => (int)$row['sort'],
|
||
'enabled' => (bool)$row['enabled'],
|
||
];
|
||
}
|
||
|
||
$this->ok($list);
|
||
}
|
||
|
||
/**
|
||
* AS5 保存区域
|
||
* POST /api/v1/admin/setting/area 入参 {id?,name,sort,enabled} → {id,name}
|
||
*/
|
||
public function saveArea()
|
||
{
|
||
$id = (int)$this->input('id', 0);
|
||
$name = trim((string)$this->input('name', ''));
|
||
$sort = (int)$this->input('sort', 0);
|
||
$enabled = (bool)$this->input('enabled', true);
|
||
|
||
if ($name === '') {
|
||
$this->fail('区域名称不能为空');
|
||
}
|
||
|
||
$data = [
|
||
'name' => $name,
|
||
'sort' => $sort,
|
||
'enabled' => $enabled ? 1 : 0,
|
||
];
|
||
|
||
if ($id && Db::name('fc_area')->where('id', $id)->find()) {
|
||
Db::name('fc_area')->where('id', $id)->update($data);
|
||
} else {
|
||
$id = Db::name('fc_area')->insertGetId($data);
|
||
}
|
||
|
||
$this->adminLog('保存区域 #' . $id . ' ' . $name);
|
||
|
||
$this->ok(['id' => (int)$id, 'name' => $name]);
|
||
}
|
||
|
||
/**
|
||
* AS6 站点 UI 配置(LOGO / 图标)
|
||
* GET → {siteName,logo,icons,updatedAt}
|
||
* POST 入参 {siteName?,logo?,icons?}(合并语义:只覆盖传入的 key) → 完整配置
|
||
*/
|
||
public function ui()
|
||
{
|
||
if ($this->request->isPost()) {
|
||
$siteName = $this->input('siteName', null);
|
||
if ($siteName !== null) {
|
||
$this->setCfg('ui.siteName', (string)$siteName);
|
||
}
|
||
|
||
$logo = $this->input('logo', null);
|
||
if ($logo !== null) {
|
||
$this->setCfg('ui.logo', (string)$logo);
|
||
}
|
||
|
||
$icons = $this->input('icons', null);
|
||
if ($icons !== null) {
|
||
if (is_string($icons)) {
|
||
$icons = json_decode($icons, true);
|
||
}
|
||
$icons = is_array($icons) ? $icons : (array)$icons;
|
||
$merged = (array)$this->cfg('ui.icons', []);
|
||
foreach ($icons as $k => $v) {
|
||
if (!is_string($v)) {
|
||
$this->fail('图标 ' . $k . ' 的地址必须是字符串');
|
||
}
|
||
$merged[$k] = $v;
|
||
}
|
||
$this->setCfg('ui.icons', $merged);
|
||
}
|
||
|
||
$this->setCfg('ui.updatedAt', date('c'));
|
||
$this->adminLog('修改LOGO与图标配置');
|
||
}
|
||
|
||
$this->ok($this->uiConfig());
|
||
}
|
||
|
||
/**
|
||
* AS7 重置 UI 配置
|
||
* POST /api/v1/admin/setting/ui/reset → {siteName,logo,icons,reset}
|
||
*/
|
||
public function uiReset()
|
||
{
|
||
$this->setCfg('ui.logo', '');
|
||
$this->setCfg('ui.icons', []);
|
||
$this->setCfg('ui.updatedAt', date('c'));
|
||
$this->adminLog('重置LOGO与图标配置');
|
||
|
||
$this->ok([
|
||
'siteName' => (string)$this->cfg('ui.siteName', '同城分类信息'),
|
||
'logo' => '',
|
||
'icons' => (object)[],
|
||
'reset' => true,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* AS8 文件上传
|
||
* POST /api/v1/admin/upload multipart 字段 file → {url}
|
||
*/
|
||
public function upload()
|
||
{
|
||
$file = $this->request->file('file');
|
||
if (!$file) {
|
||
$this->fail('未接收到上传文件');
|
||
}
|
||
try {
|
||
$upload = new \app\common\library\Upload($file);
|
||
$attachment = $upload->upload();
|
||
} catch (\Exception $e) {
|
||
$this->fail($e->getMessage());
|
||
}
|
||
|
||
$this->ok(['url' => cdnurl($attachment->url, true)]);
|
||
}
|
||
|
||
/** 基础配置结构 */
|
||
protected function baseConfig()
|
||
{
|
||
return [
|
||
'appName' => (string)$this->cfg('base.appName', '同城分类信息'),
|
||
'logo' => (string)$this->cfg('base.logo', ''),
|
||
'servicePhone' => (string)$this->cfg('base.servicePhone', '400-000-0000'),
|
||
'aboutUs' => (string)$this->cfg('base.aboutUs', ''),
|
||
'shareTitle' => (string)$this->cfg('base.shareTitle', ''),
|
||
];
|
||
}
|
||
|
||
/** 费用配置结构 */
|
||
protected function feeConfig()
|
||
{
|
||
return [
|
||
'singlePost' => (float)$this->cfg('fee.singlePost', 5),
|
||
'freePostPerDay' => (int)$this->cfg('fee.freePostPerDay', 2),
|
||
'memberMonth' => (float)$this->cfg('fee.memberMonth', 19.9),
|
||
'memberQuarter' => (float)$this->cfg('fee.memberQuarter', 49.9),
|
||
'memberYear' => (float)$this->cfg('fee.memberYear', 168),
|
||
];
|
||
}
|
||
|
||
/** UI 配置结构(icons 保证为对象) */
|
||
protected function uiConfig()
|
||
{
|
||
$icons = $this->cfg('ui.icons', []);
|
||
$icons = is_array($icons) ? $icons : (array)$icons;
|
||
|
||
return [
|
||
'siteName' => (string)$this->cfg('ui.siteName', '同城分类信息'),
|
||
'logo' => (string)$this->cfg('ui.logo', ''),
|
||
'icons' => (object)$icons,
|
||
'updatedAt' => (string)$this->cfg('ui.updatedAt', date('c')),
|
||
];
|
||
}
|
||
}
|