isValid()) { throw new \Exception('文件上传失败'); } // 2. 文件类型验证 $ext = strtolower($file->getOriginalExtension()); if (!in_array($ext, ['docx'])) { throw new \Exception('只支持.docx格式的文件'); } // 3. 文件大小验证(10MB限制) if ($file->getSize() > 10 * 1024 * 1024) { throw new \Exception('文件大小不能超过10MB'); } // 4. 创建上传目录 $uploadPath = 'upload/contract/' . date('Y/m/d/'); $fullDir = public_path() . '/' . $uploadPath; if (!is_dir($fullDir)) { if (!mkdir($fullDir, 0755, true)) { throw new \Exception('创建上传目录失败'); } } // 5. 生成唯一文件名 $fileName = time() . '_' . uniqid() . '.' . $ext; $fullPath = $uploadPath . $fileName; $absolutePath = public_path() . '/' . $fullPath; // 6. 移动文件 if (!$file->move($fullDir, $fileName)) { throw new \Exception('文件保存失败'); } // 7. 验证文件是否成功保存 if (!file_exists($absolutePath)) { throw new \Exception('文件保存验证失败'); } // 8. 解析占位符 $placeholders = $this->parsePlaceholders($absolutePath); // 9. 保存合同记录 $contract = new Contract(); $contractData = [ 'name' => $data['contract_name'] ?? '未命名合同', 'file_path' => $fullPath, 'status' => 1, // 启用状态 'type' => $data['contract_type'] ?? 'general', 'created_at' => time(), 'updated_at' => time() ]; $contractId = $contract->insertGetId($contractData); if (!$contractId) { // 如果保存失败,删除已上传的文件 unlink($absolutePath); throw new \Exception('保存合同记录失败'); } return [ 'id' => $contractId, 'file_path' => $fullPath, 'placeholders' => $placeholders, 'file_size' => $file->getSize(), 'original_name' => $file->getOriginalName() ]; } /** * 解析Word文档中的占位符 * @param string $filePath 文件路径 * @return array 占位符列表 * @throws \Exception */ public function parsePlaceholders(string $filePath): array { try { // 使用phpoffice/phpword解析文档 $phpWord = \PhpOffice\PhpWord\IOFactory::load($filePath); $placeholders = []; // 遍历所有section foreach ($phpWord->getSections() as $section) { $elements = $section->getElements(); foreach ($elements as $element) { // 提取占位符逻辑 $this->extractPlaceholders($element, $placeholders); } } return array_unique($placeholders); } catch (\Exception $e) { throw new \Exception('文档解析失败:' . $e->getMessage()); } } /** * 递归提取占位符 * @param mixed $element 文档元素 * @param array $placeholders 占位符数组 */ private function extractPlaceholders($element, &$placeholders) { // 检查是否是文本元素 if (method_exists($element, 'getText')) { $text = $element->getText(); if (is_string($text)) { // 使用正则表达式提取{{占位符}}格式 preg_match_all('/\{\{([^}]+)\}\}/', $text, $matches); if (!empty($matches[1])) { $placeholders = array_merge($placeholders, $matches[1]); } } } // 如果元素包含子元素,递归处理 if (method_exists($element, 'getElements')) { foreach ($element->getElements() as $subElement) { $this->extractPlaceholders($subElement, $placeholders); } } } /** * 配置数据源 * @param int $contractId 合同ID * @param array $config 配置数据 * @return bool 是否成功 * @throws \Exception */ public function configDataSource(int $contractId, array $config): bool { // 1. 验证合同是否存在 $contract = (new Contract())->find($contractId); if (!$contract) { throw new \Exception('合同不存在'); } // 2. 验证配置数据 if (empty($config) || !is_array($config)) { throw new \Exception('配置数据不能为空'); } // 3. 开启事务 \think\facade\Db::startTrans(); try { // 4. 删除现有配置 (new DocumentDataSourceConfig())->where('contract_id', $contractId)->delete(); // 5. 批量插入新配置 $insertData = []; foreach ($config as $item) { // 验证必需字段 if (empty($item['placeholder'])) { throw new \Exception('占位符不能为空'); } $insertData[] = [ 'contract_id' => $contractId, 'placeholder' => $item['placeholder'], 'table_name' => $item['table_name'] ?? '', 'field_name' => $item['field_name'] ?? '', 'field_type' => $item['field_type'] ?? 'string', 'is_required' => $item['is_required'] ?? 0, 'default_value' => $item['default_value'] ?? '', 'created_at' => date('Y-m-d H:i:s') ]; } // 6. 批量插入 if (!empty($insertData)) { $result = (new DocumentDataSourceConfig())->insertAll($insertData); if (!$result) { throw new \Exception('保存配置失败'); } } // 7. 提交事务 \think\facade\Db::commit(); return true; } catch (\Exception $e) { // 8. 回滚事务 \think\facade\Db::rollback(); throw $e; } } }