文件
MiAn e01cedd18e feat: 新增短视频支持及多媒体管理功能
1. 新增视频图标及上传示例视频文件
2. 调整开发环境后端域名
3. 新增结构化媒体列表功能,区分图片与视频
4. 新增短视频弹窗预览功能,支持在审核和详情页播放
5. 调整图片和视频文件大小限制,放宽至15MB和100MB
6. 优化详情页,支持视频播放、图片视频混合画廊展示
7. 优化后台审核和详情页面,新增短视频质检模块
2026-11-11 14:46:02 +08:00

349 行
11 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\admin\controller\fc;
use app\admin\model\AdminLog;
use app\common\controller\Backend;
use think\Db;
/**
* 同城-信息审核中心
*
* @icon fa fa-check-square-o
*/
class Postaudit extends Backend
{
/**
* FcPost模型对象
* @var \app\admin\model\FcPost
*/
protected $model = null;
/**
* 无需鉴权的方法(只读辅助接口)
* @var array
*/
protected $noNeedRight = ['detail', 'queue'];
/**
* 初始化控制器
*
* @return void
*/
public function _initialize()
{
parent::_initialize();
$this->model = new \app\admin\model\FcPost;
}
/**
* 查看待审核信息列表
*
* 默认展示待审核(audit_status=0)的数据,支持通用搜索
*
* @return string|\think\response\Json
* @throws \think\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
return $this->view->fetch();
}
if ($this->request->request('keyField')) {
return $this->selectpage();
}
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
// 如果通用搜索未指定审核状态,默认只展示待审核信息(audit_status = 0)
$filter = (array)json_decode($this->request->get('filter', ''), true);
$hasAuditFilter = isset($filter['audit_status']) && $filter['audit_status'] !== '';
$query = $this->model->where($where);
if (!$hasAuditFilter) {
$query->where('audit_status', 0);
}
$list = $query
->order('id', 'desc')
->paginate($limit);
$result = ['total' => $list->total(), 'rows' => $list->items()];
return json($result);
}
/**
* 审核处理接口(支持单条与批量)
*
* @return void
* @throws \think\Exception
*/
public function audit()
{
$ids = $this->request->post('ids');
$auditStatus = (int)$this->request->post('auditStatus');
$reason = trim((string)$this->request->post('reason', ''));
if (!in_array($auditStatus, [1, 2], true)) {
$this->error('审核状态仅支持通过(1)或驳回(2)');
}
$ids = $this->normalizeIds($ids);
if (!$ids) {
$this->error('请选择要审核的信息');
}
if ($auditStatus === 2 && $reason === '') {
$this->error('请填写驳回原因');
}
if (mb_strlen($reason, 'utf-8') > 200) {
$reason = mb_substr($reason, 0, 200, 'utf-8');
}
$posts = Db::name('fc_post')->where('id', 'in', $ids)->whereNull('deletetime')->select();
if (!$posts) {
$this->error('信息不存在或已删除');
}
$changed = 0;
$skipped = 0;
Db::startTrans();
try {
foreach ($posts as $post) {
$from = (int)$post['audit_status'];
if ($from === $auditStatus) {
$skipped++;
continue;
}
Db::name('fc_post')->where('id', $post['id'])->update([
'audit_status' => $auditStatus,
'audit_reason' => $reason,
'updatetime' => time(),
]);
// 分类信息量维护
if (!empty($post['category_id'])) {
if ($auditStatus === 1 && $from !== 1) {
Db::name('fc_category')->where('id', $post['category_id'])->setInc('post_count');
} elseif ($auditStatus === 2 && $from === 1) {
Db::name('fc_category')
->where('id', $post['category_id'])
->where('post_count', '>', 0)
->setDec('post_count');
}
}
// 站内信通知发布用户
Db::name('fc_message')->insert([
'user_id' => $post['user_id'],
'type' => 'system',
'title' => $auditStatus === 1 ? '信息审核通过' : '信息审核未通过',
'content' => $auditStatus === 1
? sprintf('您发布的信息「%s」已通过审核并上架展示。', $post['title'])
: sprintf('您发布的信息「%s」未通过审核,原因:%s', $post['title'], $reason),
'isread' => 0,
'createtime' => time(),
]);
$changed++;
}
Db::commit();
} catch (\Exception $e) {
Db::rollback();
$this->error('审核处理失败:' . $e->getMessage());
}
$actionText = $auditStatus === 1 ? '审核通过' : '审核驳回';
AdminLog::setTitle(sprintf('%s,涉及 %d 条信息(成功 %d 条,跳过 %d 条)', $actionText, count($ids), $changed, $skipped));
$msg = $changed > 0
? sprintf('%s成功(已处理 %d 条)', $actionText, $changed)
: '所选信息已处于该状态,未重复处理';
$this->success($msg);
}
/**
* 快捷通过接口
*
* @return void
*/
public function pass()
{
$this->request->post(['auditStatus' => 1, 'reason' => '']);
$this->audit();
}
/**
* 快捷驳回接口
*
* @return void
*/
public function reject()
{
$this->request->post(['auditStatus' => 2]);
$this->audit();
}
/**
* 查看信息详情或进入专属审核工作台
*
* @param int|null $ids 信息ID
* @return string|\think\response\Json
*/
public function detail($ids = null)
{
$id = $this->request->param('ids', 0) ?: $this->request->param('id/d', 0);
if (!$id) {
$this->error('缺少参数ID');
}
$row = $this->model->where('id', $id)->find();
if (!$row) {
$this->error('信息不存在或已删除');
}
// 补齐分类名称
if ($row->category_id) {
$category = Db::name('fc_category')->where('id', $row->category_id)->find();
$row->category_name = $category ? $category['name'] : '未分类';
} else {
$row->category_name = '未分类';
}
// 解析多图相册与多媒体
$mediaList = [];
$rawMedia = $row->getData('media');
if (!empty($rawMedia)) {
$decoded = is_string($rawMedia) ? json_decode($rawMedia, true) : $rawMedia;
if (is_array($decoded)) {
$mediaList = array_values(array_filter($decoded));
} else {
$mediaList = array_values(array_filter(explode(',', (string)$rawMedia)));
}
}
if (empty($mediaList) && !empty($row->cover)) {
$mediaList[] = $row->cover;
}
// 构造结构化媒体列表(精准区分图片与视频)
$mediaItems = [];
foreach ($mediaList as $item) {
$pathOnly = parse_url($item, PHP_URL_PATH) ?: $item;
$ext = strtolower(pathinfo($pathOnly, PATHINFO_EXTENSION));
$isVideo = in_array($ext, ['mp4', 'mov', 'avi', 'wmv', 'm4v', '3gp', 'flv', 'mkv', 'webm'], true);
$mediaItems[] = [
'url' => $item,
'is_video' => $isVideo,
'ext' => $ext
];
}
// 获取商家门店信息(如存在)
$merchantInfo = null;
if (!empty($row->merchant_id)) {
$merchantInfo = Db::name('fc_merchant')->where('id', $row->merchant_id)->find();
}
// 获取发布用户画像
$userInfo = [
'id' => $row->user_id,
'nickname' => $row->user_name ?: '未知用户',
'mobile' => '',
'avatar' => '/assets/img/avatar.png',
'post_count' => 0
];
$user = Db::name('user')->where('id', $row->user_id)->field('id,nickname,mobile,avatar')->find();
if ($user) {
$userInfo['nickname'] = $user['nickname'] ?: $userInfo['nickname'];
$userInfo['mobile'] = $user['mobile'] ?: '';
$userInfo['avatar'] = $user['avatar'] ?: $userInfo['avatar'];
$userInfo['post_count'] = Db::name('fc_post')->where('user_id', $user['id'])->count();
}
$mode = $this->request->param('mode', 'audit');
$this->view->assign('mode', $mode);
$this->view->assign('row', $row);
$this->view->assign('userInfo', $userInfo);
$this->view->assign('merchantInfo', $merchantInfo);
$this->view->assign('mediaList', $mediaList);
$this->view->assign('mediaItems', $mediaItems);
$this->view->assign('queue', $this->auditQueue($row->getData()));
if ($mode === 'audit') {
return $this->view->fetch('fc/post/audit');
}
return $this->view->fetch('fc/post/detail');
}
/**
* 审核队列位置(供审核工作台显示「第 x / y 条」和上/下一条)
* GET fc/postaudit/queue?id=123
*
* @return void
*/
public function queue()
{
$id = (int)$this->request->param('id', 0);
$row = Db::name('fc_post')->where('id', $id)->whereNull('deletetime')->find();
if (!$row) {
$this->error('信息不存在或已删除');
}
$this->success('', null, $this->auditQueue($row));
}
/**
* 计算待审核队列的上下文(第几条、总数、上一条、下一条)
*
* @param array $current 当前信息数组
* @return array
*/
protected function auditQueue(array $current)
{
$id = (int)$current['id'];
$pendingIds = Db::name('fc_post')
->whereNull('deletetime')
->where('audit_status', 0)
->order('id', 'desc')
->column('id');
$total = count($pendingIds);
$pos = array_search($id, $pendingIds, true);
if ($pos === false) {
return ['index' => 0, 'total' => $total, 'prev' => 0, 'next' => 0];
}
$prevId = $pos > 0 ? $pendingIds[$pos - 1] : 0;
$nextId = $pos + 1 < $total ? $pendingIds[$pos + 1] : 0;
return [
'index' => $pos + 1,
'total' => $total,
'prev' => $prevId,
'next' => $nextId,
];
}
/**
* 解析请求中的 ID 数组
*
* @param mixed $ids 前端提交的ID
* @return array
*/
protected function normalizeIds($ids)
{
if (is_array($ids)) {
$arr = $ids;
} elseif (is_string($ids)) {
$arr = explode(',', $ids);
} else {
$arr = [$ids];
}
$arr = array_map('intval', $arr);
$arr = array_filter($arr, function ($v) {
return $v > 0;
});
return array_values(array_unique($arr));
}
}