内容库优化 + 内容库导入功能
This commit is contained in:
@@ -15,6 +15,8 @@ use think\facade\Cache;
|
||||
use think\facade\Env;
|
||||
use app\api\controller\AutomaticAssign;
|
||||
use think\facade\Request;
|
||||
use PHPExcel_IOFactory;
|
||||
use app\common\util\AliyunOSS;
|
||||
|
||||
/**
|
||||
* 内容库控制器
|
||||
@@ -92,6 +94,8 @@ class ContentLibraryController extends Controller
|
||||
'timeEnd' => isset($param['endTime']) ? strtotime($param['endTime']) : 0, // 结束时间(转换为时间戳)
|
||||
// 来源类型
|
||||
'sourceType' => $sourceType, // 1=好友,2=群,3=好友和群
|
||||
// 表单类型
|
||||
'formType' => isset($param['formType']) ? intval($param['formType']) : 0, // 表单类型,默认为0
|
||||
// 基础信息
|
||||
'status' => isset($param['status']) ? $param['status'] : 0, // 状态:0=禁用,1=启用
|
||||
'userId' => $this->request->userInfo['id'],
|
||||
@@ -127,6 +131,7 @@ class ContentLibraryController extends Controller
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$sourceType = $this->request->param('sourceType', ''); // 来源类型,1=好友,2=群
|
||||
$formType = $this->request->param('formType', ''); // 表单类型筛选
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$isAdmin = !empty($this->request->userInfo['isAdmin']);
|
||||
@@ -152,12 +157,17 @@ class ContentLibraryController extends Controller
|
||||
$where[] = ['sourceType', '=', $sourceType];
|
||||
}
|
||||
|
||||
// 添加表单类型筛选
|
||||
if ($formType !== '') {
|
||||
$where[] = ['formType', '=', $formType];
|
||||
}
|
||||
|
||||
// 获取总记录数
|
||||
$total = ContentLibrary::where($where)->count();
|
||||
|
||||
// 获取分页数据
|
||||
$list = ContentLibrary::where($where)
|
||||
->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,userId,createTime,updateTime')
|
||||
->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,formType,userId,createTime,updateTime')
|
||||
->with(['user' => function ($query) {
|
||||
$query->field('id,username');
|
||||
}])
|
||||
@@ -319,7 +329,7 @@ class ContentLibraryController extends Controller
|
||||
|
||||
// 查询内容库信息
|
||||
$library = ContentLibrary::where($where)
|
||||
->field('id,name,sourceType,devices ,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType')
|
||||
->field('id,name,sourceType,formType,devices ,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime,groupMembers,catchType')
|
||||
->find();
|
||||
|
||||
if (empty($library)) {
|
||||
@@ -470,6 +480,7 @@ class ContentLibraryController extends Controller
|
||||
$library->timeEnabled = isset($param['timeEnabled']) ? $param['timeEnabled'] : 0;
|
||||
$library->timeStart = isset($param['startTime']) ? strtotime($param['startTime']) : 0;
|
||||
$library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0;
|
||||
$library->formType = isset($param['formType']) ? intval($param['formType']) : $library->formType; // 表单类型,如果未传则保持原值
|
||||
$library->status = isset($param['status']) ? $param['status'] : 0;
|
||||
$library->updateTime = time();
|
||||
$library->save();
|
||||
@@ -2384,4 +2395,352 @@ class ContentLibraryController extends Controller
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入Excel表格(支持图片导入)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function importExcel()
|
||||
{
|
||||
try {
|
||||
$libraryId = $this->request->param('id', 0);
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$isAdmin = !empty($this->request->userInfo['isAdmin']);
|
||||
|
||||
if (empty($libraryId)) {
|
||||
return json(['code' => 400, 'msg' => '内容库ID不能为空']);
|
||||
}
|
||||
|
||||
// 验证内容库权限
|
||||
$libraryWhere = [
|
||||
['id', '=', $libraryId],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
];
|
||||
|
||||
if (!$isAdmin) {
|
||||
$libraryWhere[] = ['userId', '=', $userId];
|
||||
}
|
||||
|
||||
$library = ContentLibrary::where($libraryWhere)->find();
|
||||
if (empty($library)) {
|
||||
return json(['code' => 500, 'msg' => '内容库不存在或无权限访问']);
|
||||
}
|
||||
|
||||
// 获取文件(可能是上传的文件或远程URL)
|
||||
$fileUrl = $this->request->param('fileUrl', '');
|
||||
$file = Request::file('file');
|
||||
$tmpFile = '';
|
||||
|
||||
if (!empty($fileUrl)) {
|
||||
// 处理远程URL
|
||||
if (!preg_match('/^https?:\/\//i', $fileUrl)) {
|
||||
return json(['code' => 400, 'msg' => '无效的文件URL']);
|
||||
}
|
||||
|
||||
// 验证文件扩展名
|
||||
$urlExt = strtolower(pathinfo(parse_url($fileUrl, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
if (!in_array($urlExt, ['xls', 'xlsx'])) {
|
||||
return json(['code' => 400, 'msg' => '只支持Excel文件(.xls, .xlsx)']);
|
||||
}
|
||||
|
||||
// 下载远程文件到临时目录
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'excel_import_') . '.' . $urlExt;
|
||||
$fileContent = @file_get_contents($fileUrl);
|
||||
|
||||
if ($fileContent === false) {
|
||||
return json(['code' => 400, 'msg' => '下载远程文件失败,请检查URL是否可访问']);
|
||||
}
|
||||
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
|
||||
} elseif ($file) {
|
||||
// 处理上传的文件
|
||||
$ext = strtolower($file->getExtension());
|
||||
if (!in_array($ext, ['xls', 'xlsx'])) {
|
||||
return json(['code' => 400, 'msg' => '只支持Excel文件(.xls, .xlsx)']);
|
||||
}
|
||||
|
||||
// 保存临时文件
|
||||
$tmpFile = $file->getRealPath();
|
||||
if (empty($tmpFile)) {
|
||||
$savePath = $file->move(sys_get_temp_dir());
|
||||
$tmpFile = $savePath->getRealPath();
|
||||
}
|
||||
} else {
|
||||
return json(['code' => 400, 'msg' => '请上传Excel文件或提供文件URL']);
|
||||
}
|
||||
|
||||
if (empty($tmpFile) || !file_exists($tmpFile)) {
|
||||
return json(['code' => 400, 'msg' => '文件不存在或无法访问']);
|
||||
}
|
||||
|
||||
// 加载Excel文件
|
||||
$excel = PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
|
||||
// 获取所有图片
|
||||
$images = [];
|
||||
try {
|
||||
$drawings = $sheet->getDrawingCollection();
|
||||
foreach ($drawings as $drawing) {
|
||||
if ($drawing instanceof \PHPExcel_Worksheet_Drawing) {
|
||||
$coordinates = $drawing->getCoordinates();
|
||||
$imagePath = $drawing->getPath();
|
||||
|
||||
// 如果是嵌入的图片(zip://格式),提取到临时文件
|
||||
if (strpos($imagePath, 'zip://') === 0) {
|
||||
$zipEntry = str_replace('zip://', '', $imagePath);
|
||||
$zipEntry = explode('#', $zipEntry);
|
||||
$zipFile = $zipEntry[0];
|
||||
$imageEntry = isset($zipEntry[1]) ? $zipEntry[1] : '';
|
||||
|
||||
if (!empty($imageEntry)) {
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($zipFile) === true) {
|
||||
$imageContent = $zip->getFromName($imageEntry);
|
||||
if ($imageContent !== false) {
|
||||
$tempImageFile = tempnam(sys_get_temp_dir(), 'excel_img_');
|
||||
file_put_contents($tempImageFile, $imageContent);
|
||||
$images[$coordinates] = $tempImageFile;
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
} elseif (file_exists($imagePath)) {
|
||||
// 如果是外部文件路径
|
||||
$images[$coordinates] = $imagePath;
|
||||
}
|
||||
} elseif ($drawing instanceof \PHPExcel_Worksheet_MemoryDrawing) {
|
||||
// 处理内存中的图片
|
||||
$coordinates = $drawing->getCoordinates();
|
||||
$imageResource = $drawing->getImageResource();
|
||||
|
||||
if ($imageResource) {
|
||||
$tempImageFile = tempnam(sys_get_temp_dir(), 'excel_img_') . '.png';
|
||||
$imageType = $drawing->getMimeType();
|
||||
|
||||
switch ($imageType) {
|
||||
case 'image/png':
|
||||
imagepng($imageResource, $tempImageFile);
|
||||
break;
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
imagejpeg($imageResource, $tempImageFile);
|
||||
break;
|
||||
case 'image/gif':
|
||||
imagegif($imageResource, $tempImageFile);
|
||||
break;
|
||||
default:
|
||||
imagepng($imageResource, $tempImageFile);
|
||||
}
|
||||
|
||||
$images[$coordinates] = $tempImageFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('提取Excel图片失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 读取数据(实际内容从第三行开始,前两行是标题和说明)
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) < 3) {
|
||||
return json(['code' => 400, 'msg' => 'Excel文件数据为空']);
|
||||
}
|
||||
|
||||
// 移除前两行(标题行和说明行)
|
||||
array_shift($data); // 移除第1行
|
||||
array_shift($data); // 移除第2行
|
||||
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
$errors = [];
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($data as $rowIndex => $row) {
|
||||
$rowNum = $rowIndex + 3; // Excel行号(从3开始,因为前两行是标题和说明)
|
||||
|
||||
// 跳过空行
|
||||
if (empty(array_filter($row))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析数据(根据图片中的表格结构)
|
||||
// A:日期, B:投放时间, C:作用分类, D:朋友圈文案, E:自回评内容, F:朋友圈展示形式, G-O:配图1-9
|
||||
$date = isset($row[0]) ? trim($row[0]) : '';
|
||||
$placementTime = isset($row[1]) ? trim($row[1]) : '';
|
||||
$functionCategory = isset($row[2]) ? trim($row[2]) : '';
|
||||
$content = isset($row[3]) ? trim($row[3]) : '';
|
||||
$selfReply = isset($row[4]) ? trim($row[4]) : '';
|
||||
$displayForm = isset($row[5]) ? trim($row[5]) : '';
|
||||
|
||||
// 如果没有朋友圈文案,跳过
|
||||
if (empty($content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 提取配图(G-O列,索引6-14)
|
||||
$imageUrls = [];
|
||||
for ($colIndex = 6; $colIndex <= 14; $colIndex++) {
|
||||
$columnLetter = $this->columnLetter($colIndex);
|
||||
$cellCoordinate = $columnLetter . $rowNum;
|
||||
|
||||
// 检查是否有图片
|
||||
if (isset($images[$cellCoordinate])) {
|
||||
$imagePath = $images[$cellCoordinate];
|
||||
|
||||
// 上传图片到OSS
|
||||
$imageExt = 'jpg';
|
||||
if (file_exists($imagePath)) {
|
||||
$imageInfo = @getimagesize($imagePath);
|
||||
if ($imageInfo) {
|
||||
$imageExt = image_type_to_extension($imageInfo[2], false);
|
||||
if ($imageExt === 'jpeg') {
|
||||
$imageExt = 'jpg';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$objectName = AliyunOSS::generateObjectName('excel_img_' . $rowNum . '_' . ($colIndex - 5) . '.' . $imageExt);
|
||||
$uploadResult = AliyunOSS::uploadFile($imagePath, $objectName);
|
||||
|
||||
if ($uploadResult['success']) {
|
||||
$imageUrls[] = $uploadResult['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析日期和时间
|
||||
$createMomentTime = 0;
|
||||
if (!empty($date)) {
|
||||
// 尝试解析日期格式:2025年11月25日 或 2025-11-25
|
||||
$dateStr = $date;
|
||||
if (preg_match('/(\d{4})[年\-](\d{1,2})[月\-](\d{1,2})/', $dateStr, $matches)) {
|
||||
$year = $matches[1];
|
||||
$month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
|
||||
$day = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
|
||||
|
||||
// 解析时间
|
||||
$hour = 0;
|
||||
$minute = 0;
|
||||
if (!empty($placementTime) && preg_match('/(\d{1,2}):(\d{2})/', $placementTime, $timeMatches)) {
|
||||
$hour = intval($timeMatches[1]);
|
||||
$minute = intval($timeMatches[2]);
|
||||
}
|
||||
|
||||
$createMomentTime = strtotime("{$year}-{$month}-{$day} {$hour}:{$minute}:00");
|
||||
}
|
||||
}
|
||||
|
||||
if ($createMomentTime == 0) {
|
||||
$createMomentTime = time();
|
||||
}
|
||||
|
||||
// 判断内容类型
|
||||
$contentType = 4; // 默认文本
|
||||
if (!empty($imageUrls)) {
|
||||
$contentType = 1; // 图文
|
||||
}
|
||||
|
||||
// 创建内容项
|
||||
$item = new ContentItem();
|
||||
$item->libraryId = $libraryId;
|
||||
$item->type = 'diy'; // 自定义类型
|
||||
$item->title = !empty($date) ? $date . ' ' . $placementTime : '导入的内容';
|
||||
$item->content = $content;
|
||||
$item->comment = $selfReply; // 自回评内容
|
||||
$item->contentType = $contentType;
|
||||
$item->resUrls = json_encode($imageUrls, JSON_UNESCAPED_UNICODE);
|
||||
$item->urls = json_encode([], JSON_UNESCAPED_UNICODE);
|
||||
$item->createMomentTime = $createMomentTime;
|
||||
$item->createTime = time();
|
||||
|
||||
// 设置封面图片
|
||||
if (!empty($imageUrls[0])) {
|
||||
$item->coverImage = $imageUrls[0];
|
||||
}
|
||||
|
||||
// 保存其他信息到contentData
|
||||
$contentData = [
|
||||
'date' => $date,
|
||||
'placementTime' => $placementTime,
|
||||
'functionCategory' => $functionCategory,
|
||||
'displayForm' => $displayForm,
|
||||
'selfReply' => $selfReply
|
||||
];
|
||||
$item->contentData = json_encode($contentData, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$item->save();
|
||||
$successCount++;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$failCount++;
|
||||
$errors[] = "第{$rowNum}行处理失败:" . $e->getMessage();
|
||||
\think\facade\Log::error('导入Excel第' . $rowNum . '行失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
|
||||
// 清理临时图片文件
|
||||
foreach ($images as $imagePath) {
|
||||
if (file_exists($imagePath) && strpos($imagePath, sys_get_temp_dir()) === 0) {
|
||||
@unlink($imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理临时Excel文件
|
||||
if (file_exists($tmpFile) && strpos($tmpFile, sys_get_temp_dir()) === 0) {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '导入完成',
|
||||
'data' => [
|
||||
'success' => $successCount,
|
||||
'fail' => $failCount,
|
||||
'errors' => $errors
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
|
||||
// 清理临时文件
|
||||
foreach ($images as $imagePath) {
|
||||
if (file_exists($imagePath) && strpos($imagePath, sys_get_temp_dir()) === 0) {
|
||||
@unlink($imagePath);
|
||||
}
|
||||
}
|
||||
if (file_exists($tmpFile) && strpos($tmpFile, sys_get_temp_dir()) === 0) {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
return json(['code' => 500, 'msg' => '导入失败:' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return json(['code' => 500, 'msg' => '导入失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列序号生成Excel列字母
|
||||
* @param int $index 列索引(从0开始)
|
||||
* @return string 列字母(如A, B, C, ..., Z, AA, AB等)
|
||||
*/
|
||||
private function columnLetter($index)
|
||||
{
|
||||
$letters = '';
|
||||
do {
|
||||
$letters = chr($index % 26 + 65) . $letters;
|
||||
$index = intval($index / 26) - 1;
|
||||
} while ($index >= 0);
|
||||
return $letters;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user