Technical discuss

Joomla 网站API第三方适配程序

更多
2026-01-12 13:16 - 2026-01-12 13:53 #1010 by service
新帖
如果第三方程序使用的URL路径与当前joomla网站的组件不匹配。本站组件使用的是Joomla的标准路由格式,而第三方程序期望的是RESTful API格式:
第三方程序期望的是标准的RESTful API端点(如 
Code:
/api/publish
),而本站组件使用的是Joomla格式的URL(
Code:
index.php?option=com_aipublisher&task=publish
)。
以下是一种解决方案:安装包含
Code:
api.php
的适配器版本
  • 提供完整的RESTful API支持
  • 包含URL重写规则
  • 有详细的使用文档
  • 保持向后兼容

创建适配第三方程序的API端点文件
Code:
[code]#!/bin/bash # AI Publisher - 第三方程序API适配器(修复版) echo "🔧 创建第三方程序API适配器(完整功能版)..." echo "==============================================" cd com_aipublisher_final # 1. 修复的API适配器文件 - 修复EOF语法 cat > "site/api.php" << 'API_EOF' <?php defined('_JEXEC') or die; // 设置JSON响应头 header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); // 处理预检请求 if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {     http_response_code(200);     exit; } // 获取请求方法和路径 $method = $_SERVER['REQUEST_METHOD']; $requestUri = $_SERVER['REQUEST_URI']; $path = parse_url($requestUri, PHP_URL_PATH); // 移除查询字符串 $path = strtok($path, '?'); // 完整的路由映射表 $routes = [     'POST' => [         '/api/publish' => 'handleApiPublish',         '/api/test-connection' => 'handleApiTestConnection',         '/api/save-config' => 'handleApiSaveConfig',         '/api/conversations' => 'handleApiConversations'     ],     'GET' => [         '/api/conversations' => 'handleApiGetConversations',         '/api/website-configs' => 'handleApiGetWebsiteConfigs',         '/api/health' => 'handleApiHealth',         '/api/info' => 'handleApiInfo',         '/api/status' => 'handleApiStatus'     ],     'PUT' => [         '/api/update-config' => 'handleApiUpdateConfig'     ] ]; // 查找匹配的路由 $handler = null; $matchedRoute = null; if (isset($routes[$method])) {     foreach ($routes[$method] as $route => $handlerName) {         // 精确匹配或路径匹配         if ($path === $route || strpos($path, $route . '/') === 0) {             $handler = $handlerName;             $matchedRoute = $route;             break;         }     } } // 记录请求日志(调试用) logApiRequest($method, $path, $matchedRoute); // 执行处理函数 if ($handler && function_exists($handler)) {     try {         call_user_func($handler);     } catch (Exception $e) {         http_response_code(500);         echo json_encode([             'status' => 'error',             'message' => 'Internal server error: ' . $e->getMessage(),             'code' => 'INTERNAL_ERROR',             'timestamp' => date('Y-m-d H:i:s')         ]);     } } else {     // 提供友好的404错误     http_response_code(404);     echo json_encode([         'status' => 'error',         'message' => 'API endpoint not found',         'details' => [             'requested_path' => $path,             'request_method' => $method,             'available_endpoints' => getAvailableEndpoints()         ],         'suggestions' => [             'Check the endpoint URL',             'Verify the HTTP method (GET/POST/PUT/DELETE)',             'Ensure you have proper authentication token'         ]     ]); } exit; // ==================== API处理函数 ==================== /**  * 处理发布文章请求  */ function handleApiPublish() {     try {         // 获取并解析请求数据         $rawInput = file_get_contents('php://input');                  if (empty($rawInput)) {             throw new Exception('Request body is empty');         }                  $input = json_decode($rawInput, true);                  if (json_last_error() !== JSON_ERROR_NONE) {             throw new Exception('Invalid JSON format: ' . json_last_error_msg());         }                  // 验证必需字段         $requiredFields = ['token', 'content'];         $missingFields = [];                  foreach ($requiredFields as $field) {             if (!isset($input[$field]) || trim($input[$field]) === '') {                 $missingFields[] = $field;             }         }                  if (!empty($missingFields)) {             throw new Exception('Missing required fields: ' . implode(', ', $missingFields));         }                  // 验证API令牌         $tokenValidation = validateApiToken($input['token']);                  if (!$tokenValidation['valid']) {             http_response_code(401);             echo json_encode([                 'status' => 'error',                 'message' => 'Authentication failed',                 'code' => 'INVALID_TOKEN',                 'details' => $tokenValidation             ]);             return;         }                  // 准备文章数据         $articleData = [             'title' => $input['title'] ?? 'AI Generated Content - ' . date('Y-m-d H:i:s'),             'content' => $input['content'],             'category' => $input['category_id'] ?? $input['category'] ?? null,             'state' => $input['state'] ?? $input['published'] ?? null,             'author' => $input['author'] ?? $input['created_by_alias'] ?? null,             'tags' => $input['tags'] ?? $input['keywords'] ?? null,             'meta_description' => $input['description'] ?? $input['meta_description'] ?? null,             'meta_keywords' => $input['keywords'] ?? $input['meta_keywords'] ?? null         ];                  // 调用Joomla发布引擎         $publishResult = publishToJoomla($articleData);                  // 记录成功日志         logApiSuccess('publish', $publishResult['id']);                  // 返回成功响应         echo json_encode([             'status' => 'success',             'message' => 'Article published successfully',             'data' => $publishResult,             'meta' => [                 'timestamp' => date('Y-m-d H:i:s'),                 'request_id' => uniqid('req_', true),                 'processing_time' => getProcessingTime() . 'ms'             ]         ]);              } catch (Exception $e) {         // 记录错误日志         logApiError('publish', $e->getMessage());                  http_response_code(400);         echo json_encode([             'status' => 'error',             'message' => 'Failed to publish article: ' . $e->getMessage(),             'code' => 'PUBLISH_ERROR',             'timestamp' => date('Y-m-d H:i:s')         ]);     } } /**  * 处理测试连接请求  */ function handleApiTestConnection() {     try {         $rawInput = file_get_contents('php://input');                  if (empty($rawInput)) {             // 如果没有提供数据,检查GET参数             $token = $_GET['token'] ?? '';         } else {             $input = json_decode($rawInput, true);             $token = $input['token'] ?? '';         }                  if (empty($token)) {             throw new Exception('API token is required');         }                  $validation = validateApiToken($token);                  if ($validation['valid']) {             echo json_encode([                 'status' => 'success',                 'message' => 'Connection successful',                 'details' => [                     'joomla_version' => JVERSION,                     'php_version' => PHP_VERSION,                     'server_time' => date('Y-m-d H:i:s'),                     'component_version' => '1.1.0',                     'api_status' => 'operational'                 ],                 'system_info' => [                     'memory_usage' => round(memory_get_usage(true) / 1024 / 1024, 2) . ' MB',                     'max_execution_time' => ini_get('max_execution_time'),                     'upload_max_filesize' => ini_get('upload_max_filesize')                 ]             ]);         } else {             http_response_code(401);             echo json_encode([                 'status' => 'error',                 'message' => 'Connection failed: Invalid API token',                 'code' => 'AUTH_FAILED',                 'details' => $validation             ]);         }              } catch (Exception $e) {         http_response_code(400);         echo json_encode([             'status' => 'error',             'message' => 'Test failed: ' . $e->getMessage(),             'code' => 'TEST_ERROR'         ]);     } } /**  * 处理保存配置请求  */ function handleApiSaveConfig() {     try {         $input = json_decode(file_get_contents('php://input'), true);                  if (!$input) {             throw new Exception('Invalid configuration data');         }                  // 获取当前组件参数         $params = JComponentHelper::getParams('com_aipublisher');         $currentParams = $params->toArray();                  // 合并新配置         $newParams = array_merge($currentParams, $input);                  // 保存到数据库         $db = JFactory::getDbo();         $query = $db->getQuery(true)             ->update('#__extensions')             ->set('params = ' . $db->quote(json_encode($newParams)))             ->where('element = ' . $db->quote('com_aipublisher'));                  $db->setQuery($query);         $result = $db->execute();                  if ($result) {             // 清除缓存             JFactory::getCache('com_aipublisher')->clean();                          echo json_encode([                 'status' => 'success',                 'message' => 'Configuration saved successfully',                 'data' => [                     'updated_fields' => array_keys($input),                     'total_fields' => count($newParams),                     'cache_cleared' => true                 ]             ]);         } else {             throw new Exception('Failed to save configuration to database');         }              } catch (Exception $e) {         http_response_code(500);         echo json_encode([             'status' => 'error',             'message' => 'Failed to save configuration: ' . $e->getMessage(),             'code' => 'CONFIG_SAVE_ERROR'         ]);     } } /**  * 处理获取对话请求(POST)  */ function handleApiConversations() {     try {         $input = json_decode(file_get_contents('php://input'), true);                  // 这里可以实现对话存储逻辑         // 目前返回模拟数据         echo json_encode([             'status' => 'success',             'message' => 'Conversation processed',             'data' => [                 'conversation_id' => uniqid('conv_'),                 'timestamp' => date('Y-m-d H:i:s'),                 'message' => 'Conversation endpoint is ready for implementation'             ]         ]);              } catch (Exception $e) {         http_response_code(400);         echo json_encode([             'status' => 'error',             'message' => 'Failed to process conversation: ' . $e->getMessage()         ]);     } } /**  * 处理获取对话请求(GET)  */ function handleApiGetConversations() {     try {         $db = JFactory::getDbo();                  // 这里可以查询实际的对话数据         // 目前返回模拟数据         $conversations = [             [                 'id' => 1,                 'title' => 'Technical Discussion',                 'summary' => 'Discussion about API implementation',                 'message_count' => 5,                 'last_updated' => date('Y-m-d H:i:s', strtotime('-1 hour')),                 'participants' => ['AI System', 'Administrator']             ],             [                 'id' => 2,                 'title' => 'Content Planning',                 'summary' => 'Planning for upcoming articles',                 'message_count' => 3,                 'last_updated' => date('Y-m-d H:i:s', strtotime('-2 hours')),                 'participants' => ['AI Assistant', 'Editor']             ]         ];                  echo json_encode([             'status' => 'success',             'data' => $conversations,             'meta' => [                 'total' => count($conversations),                 'page' => 1,                 'per_page' => 10,                 'has_more' => false             ]         ]);              } catch (Exception $e) {         http_response_code(500);         echo json_encode([             'status' => 'error',             'message' => 'Failed to retrieve conversations: ' . $e->getMessage()         ]);     } } /**  * 处理获取网站配置请求  */ function handleApiGetWebsiteConfigs() {     try {         $config = JFactory::getConfig();         $params = JComponentHelper::getParams('com_aipublisher');                  $websiteConfig = [             'general' => [                 'site_name' => $config->get('sitename'),                 'site_url' => JUri::root(),                 'joomla_version' => JVERSION,                 'php_version' => PHP_VERSION,                 'server_timezone' => $config->get('offset'),                 'sef_enabled' => $config->get('sef'),                 'sef_rewrite' => $config->get('sef_rewrite')             ],             'api' => [                 'base_url' => JUri::root() . 'api.php',                 'joomla_endpoint' => JUri::root() . 'index.php?option=com_aipublisher',                 'token_configured' => !empty($params->get('api_token')),                 'default_category' => $params->get('default_category', 2),                 'auto_publish' => (bool)$params->get('auto_publish', 1),                 'default_author' => $params->get('default_author', 'AI Assistant'),                 'debug_mode' => (bool)$params->get('debug_mode', 0)             ],             'content' => [                 'article_count' => getArticleCount(),                 'categories' => getCategoryList(),                 'recent_articles' => getRecentArticles(5)             ],             'system' => [                 'max_upload_size' => ini_get('upload_max_filesize'),                 'memory_limit' => ini_get('memory_limit'),                 'max_execution_time' => ini_get('max_execution_time'),                 'timezone' => date_default_timezone_get()             ]         ];                  echo json_encode([             'status' => 'success',             'data' => $websiteConfig,             'meta' => [                 'timestamp' => date('Y-m-d H:i:s'),                 'generated_in' => getProcessingTime() . 'ms'             ]         ]);              } catch (Exception $e) {         http_response_code(500);         echo json_encode([             'status' => 'error',             'message' => 'Failed to retrieve website configs: ' . $e->getMessage()         ]);     } } /**  * 处理健康检查请求  */ function handleApiHealth() {     $healthStatus = [         'status' => 'healthy',         'timestamp' => date('Y-m-d H:i:s'),         'services' => []     ];          // 检查数据库连接     try {         $db = JFactory::getDbo();         $db->connect();         $healthStatus['services']['database'] = [             'status' => 'connected',             'latency' => 'OK'         ];     } catch (Exception $e) {         $healthStatus['services']['database'] = [             'status' => 'disconnected',             'error' => $e->getMessage()         ];         $healthStatus['status'] = 'degraded';     }          // 检查组件状态     $component = JComponentHelper::getComponent('com_aipublisher');     $healthStatus['services']['component'] = [         'status' => $component->enabled ? 'enabled' : 'disabled',         'version' => '1.1.0'     ];          // 检查API状态     $healthStatus['services']['api'] = [         'status' => 'operational',         'endpoints_available' => count(getAvailableEndpoints()['endpoints'])     ];          // 系统资源     $healthStatus['system'] = [         'memory_usage' => round(memory_get_usage(true) / 1024 / 1024, 2) . ' MB',         'memory_peak' => round(memory_get_peak_usage(true) / 1024 / 1024, 2) . ' MB',         'load_time' => getProcessingTime() . 'ms'     ];          echo json_encode($healthStatus); } /**  * 处理API信息请求  */ function handleApiInfo() {     echo json_encode([         'api_name' => 'AI Publisher REST API',         'version' => '1.1.0',         'description' => 'Complete REST API for AI-powered content publishing to Joomla',         'documentation' => JUri::root() . 'administrator/index.php?option=com_aipublisher',         'base_url' => JUri::root() . 'api.php',         'authentication' => 'Token-based authentication',         'rate_limiting' => 'Not implemented',         'endpoints' => getAvailableEndpoints()['endpoints'],         'supported_formats' => ['JSON'],         'changelog' => [             '1.1.0' => 'Added full REST API support for third-party applications',             '1.0.0' => 'Initial stable release with JTable publishing'         ]     ]); } /**  * 处理API状态请求  */ function handleApiStatus() {     echo json_encode([         'status' => 'operational',         'uptime' => '100%',         'response_time' => getProcessingTime() . 'ms',         'last_updated' => date('Y-m-d H:i:s'),         'metrics' => [             'total_requests' => 0, // 可以添加请求计数             'success_rate' => '100%',             'average_response_time' => getProcessingTime() . 'ms'         ]     ]); } /**  * 处理更新配置请求  */ function handleApiUpdateConfig() {     handleApiSaveConfig(); // 重用保存配置逻辑 } // ==================== 辅助函数 ==================== /**  * 验证API令牌  */ function validateApiToken($token) {     $result = [         'valid' => false,         'checks' => [],         'details' => []     ];          if (empty($token)) {         $result['checks'][] = 'Token is empty';         return $result;     }          $result['checks'][] = 'Token received: ' . substr($token, 0, 8) . '...';          // 从组件参数获取令牌     $params = JComponentHelper::getParams('com_aipublisher');     $storedToken = $params->get('api_token', '');          if (empty($storedToken)) {         $result['checks'][] = 'No token stored in component parameters';         $result['details']['stored_token'] = 'Not set';         return $result;     }          $result['checks'][] = 'Stored token: ' . substr($storedToken, 0, 8) . '...';     $result['details']['stored_token_length'] = strlen($storedToken);     $result['details']['received_token_length'] = strlen($token);          // 安全比较令牌     if (function_exists('hash_equals')) {         $result['valid'] = hash_equals($storedToken, $token);         $result['checks'][] = 'Used hash_equals for comparison';     } else {         $result['valid'] = ($storedToken === $token);         $result['checks'][] = 'Used string comparison (hash_equals not available)';     }          if ($result['valid']) {         $result['checks'][] = 'Token validation successful';     } else {         $result['checks'][] = 'Token validation failed';     }          return $result; } /**  * 发布文章到Joomla  */ function publishToJoomla($data) {     // 获取组件参数     $params = JComponentHelper::getParams('com_aipublisher');          // 准备文章数据     $articleData = [         'title' => $data['title'] ?? 'AI Generated Content - ' . date('Y-m-d H:i:s'),         'content' => $data['content'],         'category' => $data['category'] ?? $params->get('default_category', 2),         'state' => isset($data['state']) ? intval($data['state']) : $params->get('auto_publish', 1),         'author' => $data['author'] ?? $params->get('default_author', 'AI Assistant'),         'tags' => $data['tags'] ?? null,         'meta_description' => $data['meta_description'] ?? '',         'meta_keywords' => $data['meta_keywords'] ?? ''     ];          // 使用JTable创建文章     JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_content/tables');     $table = JTable::getInstance('Content', 'JTable');          if (!$table) {         throw new Exception('Could not load Joomla Content Table');     }          // 生成唯一别名     $alias = JFilterOutput::stringURLSafe($articleData['title']);     $baseAlias = $alias;     $counter = 1;          // 确保别名唯一     while ($table->load(['alias' => $alias, 'catid' => $articleData['category']])) {         $alias = $baseAlias . '-' . $counter;         $counter++;         if ($counter > 100) {             $alias = $baseAlias . '-' . time();             break;         }     }          // 准备完整的数据     $rowData = [         'title' => $articleData['title'],         'alias' => $alias,         'introtext' => nl2br(htmlspecialchars($articleData['content'], ENT_QUOTES, 'UTF-8')),         'fulltext' => '',         'state' => $articleData['state'],         'catid' => $articleData['category'],         'language' => '*',         'created' => JFactory::getDate()->toSql(),         'created_by' => JFactory::getUser()->id,         'created_by_alias' => $articleData['author'],         'modified' => JFactory::getDate()->toSql(),         'modified_by' => JFactory::getUser()->id,         'publish_up' => JFactory::getDate()->toSql(),         'publish_down' => JFactory::getDbo()->getNullDate(),         'featured' => 0,         'access' => 1,         'metadesc' => $articleData['meta_description'],         'metakey' => $articleData['meta_keywords'],         'metadata' => '{"robots":"","author":"","rights":"","xreference":""}',         'note' => 'Published via AI Publisher API v1.1.0',         'images' => '',         'urls' => '',         'attribs' => '{"show_title":"1","link_titles":"","show_intro":"","show_category":"","link_category":"","show_author":"","show_create_date":"","show_modify_date":"","show_publish_date":"","show_hits":""}'     ];          // Joomla 4/5工作流支持     $isJoomla4Plus = version_compare(JVERSION, '4.0', '>=');     if ($isJoomla4Plus) {         $rowData['workflow_id'] = 1;         $rowData['stage_id'] = $articleData['state'] ? 1 : 2;     }          // 绑定数据     if (!$table->bind($rowData)) {         throw new Exception('Failed to bind article data: ' . $table->getError());     }          // 检查数据     if (!$table->check()) {         throw new Exception('Article data validation failed: ' . $table->getError());     }          // 存储数据     if (!$table->store()) {         throw new Exception('Failed to save article: ' . $table->getError());     }          $articleId = $table->id;          if (!$articleId) {         throw new Exception('Failed to get article ID after save');     }          // Joomla 4/5工作流关联     if ($isJoomla4Plus) {         ensureWorkflowAssociation($articleId, $rowData['stage_id']);     }          // 处理标签(如果提供)     if (!empty($articleData['tags'])) {         assignTagsToArticle($articleId, $articleData['tags']);     }          // 清理缓存     clearContentCache();          return [         'id' => $articleId,         'title' => $articleData['title'],         'alias' => $alias,         'state' => $articleData['state'],         'category_id' => $articleData['category'],         'author' => $articleData['author'],         'url' => JUri::root() . 'index.php?option=com_content&view=article&id=' . $articleId,         'admin_url' => JUri::root() . 'administrator/index.php?option=com_content&task=article.edit&id=' . $articleId,         'created' => $rowData['created'],         'metadata' => [             'joomla_version' => JVERSION,             'workflow_supported' => $isJoomla4Plus,             'api_version' => '1.1.0'         ]     ]; } /**  * 确保工作流关联  */ function ensureWorkflowAssociation($articleId, $stageId) {     $db = JFactory::getDbo();          try {         // 检查是否已存在关联         $query = $db->getQuery(true)             ->select('COUNT(*)')             ->from('#__workflow_associations')             ->where('item_id = ' . (int)$articleId)             ->where('extension = ' . $db->quote('com_content.article'));                  $db->setQuery($query);         $exists = $db->loadResult() > 0;                  if (!$exists) {             // 插入工作流关联             $association = new stdClass();             $association->item_id = $articleId;             $association->stage_id = $stageId;             $association->extension = 'com_content.article';                          // 检查表结构             $columns = $db->getTableColumns('#__workflow_associations');             if (isset($columns['workflow_id'])) {                 $association->workflow_id = 1;             }                          $db->insertObject('#__workflow_associations', $association);         }                  // 确保文章状态正确         $state = ($stageId == 1) ? 1 : 0;         $query = $db->getQuery(true)             ->update('#__content')             ->set('state = ' . (int)$state)             ->where('id = ' . (int)$articleId);                  $db->setQuery($query);         $db->execute();              } catch (Exception $e) {         // 工作流错误不影响文章创建         error_log('Workflow association error: ' . $e->getMessage());     } } /**  * 为文章分配标签  */ function assignTagsToArticle($articleId, $tags) {     if (!class_exists('JHelperTags')) {         return;     }          try {         if (is_string($tags)) {             $tags = explode(',', $tags);             $tags = array_map('trim', $tags);         }                  if (!is_array($tags) || empty($tags)) {             return;         }                  $tagsHelper = new JHelperTags();         $tagsHelper->typeAlias = 'com_content.article';         $tagsHelper->tags = $tags;         $tagsHelper->tagMethod = 0; // 替换现有标签         $tagsHelper->tagIds = null;         $tagsHelper->typeId = 0;         $tagsHelper->item = [             'id' => $articleId,             'title' => '',             'alias' => '',             'catid' => 0         ];                  $tagsHelper->postStoreProcess();              } catch (Exception $e) {         error_log('Tag assignment error: ' . $e->getMessage());     } } /**  * 清理内容缓存  */ function clearContentCache() {     try {         $cache = JFactory::getCache('com_content');         $cache->clean();         JFactory::getCache()->clean();     } catch (Exception $e) {         // 忽略缓存错误     } } /**  * 获取可用端点  */ function getAvailableEndpoints() {     return [         'endpoints' => [             ['method' => 'POST', 'path' => '/api/publish', 'description' => 'Publish new article'],             ['method' => 'POST', 'path' => '/api/test-connection', 'description' => 'Test API connection'],             ['method' => 'POST', 'path' => '/api/save-config', 'description' => 'Save configuration'],             ['method' => 'GET', 'path' => '/api/website-configs', 'description' => 'Get website configuration'],             ['method' => 'GET', 'path' => '/api/health', 'description' => 'Health check'],             ['method' => 'GET', 'path' => '/api/info', 'description' => 'API information'],             ['method' => 'GET', 'path' => '/api/status', 'description' => 'API status'],             ['method' => 'GET', 'path' => '/api/conversations', 'description' => 'Get conversations'],             ['method' => 'POST', 'path' => '/api/conversations', 'description' => 'Create conversation']         ],         'authentication' => 'Token-based (required for publish and test endpoints)',         'base_url' => JUri::root() . 'api.php'     ]; } /**  * 获取文章数量  */ function getArticleCount() {     $db = JFactory::getDbo();     $query = $db->getQuery(true)         ->select('COUNT(*)')         ->from('#__content')         ->where('created_by_alias LIKE ' . $db->quote('%AI%'));          $db->setQuery($query);     return $db->loadResult(); } /**  * 获取分类列表  */ function getCategoryList() {     $db = JFactory::getDbo();     $query = $db->getQuery(true)         ->select('id, title')         ->from('#__categories')         ->where('extension = ' . $db->quote('com_content'))         ->where('published = 1')         ->order('lft ASC');          $db->setQuery($query);     return $db->loadObjectList(); } /**  * 获取最近文章  */ function getRecentArticles($limit = 5) {     $db = JFactory::getDbo();     $query = $db->getQuery(true)         ->select('id, title, created, state, hits')         ->from('#__content')         ->where('created_by_alias LIKE ' . $db->quote('%AI%'))         ->order('created DESC')         ->setLimit($limit);          $db->setQuery($query);     return $db->loadObjectList(); } /**  * 记录API请求  */ function logApiRequest($method, $path, $matchedRoute) {     // 这里可以添加请求日志记录     // 例如保存到数据库或文件     if (defined('JDEBUG') && JDEBUG) {         error_log("API Request: $method $path -> " . ($matchedRoute ?: 'NO_MATCH'));     } } /**  * 记录API成功  */ function logApiSuccess($action, $data) {     if (defined('JDEBUG') && JDEBUG) {         error_log("API Success [$action]: " . json_encode($data));     } } /**  * 记录API错误  */ function logApiError($action, $error) {     error_log("API Error [$action]: $error"); } /**  * 获取处理时间  */ function getProcessingTime() {     static $startTime = null;          if ($startTime === null) {         $startTime = microtime(true);         return 0;     }          return round((microtime(true) - $startTime) * 1000, 2); } API_EOF # 2. 创建.htaccess文件 cat > "site/.htaccess" << 'HTACCESS_EOF' # AI Publisher API - URL Rewrite Rules <IfModule mod_rewrite.c>     RewriteEngine On          # 重写API请求到api.php     RewriteCond %{REQUEST_FILENAME} !-f     RewriteCond %{REQUEST_FILENAME} !-d     RewriteRule ^api/(.*)$ api.php [QSA,L]          # 支持直接访问/api路径     RewriteCond %{REQUEST_URI} ^/api$     RewriteRule ^api$ api.php [QSA,L]          # 重写旧的Joomla格式到API格式(可选)     RewriteCond %{QUERY_STRING} option=com_aipublisher&task=([^&]+)     RewriteRule ^index\.php$ api.php?action=%1 [QSA,L,NC] </IfModule> # 安全设置 <IfModule mod_headers.c>     Header set X-Content-Type-Options "nosniff"     Header set X-Frame-Options "SAMEORIGIN"     Header set X-XSS-Protection "1; mode=block" </IfModule> # 压缩设置 <IfModule mod_deflate.c>     AddOutputFilterByType DEFLATE application/json     AddOutputFilterByType DEFLATE application/javascript     AddOutputFilterByType DEFLATE text/css     AddOutputFilterByType DEFLATE text/html </IfModule> HTACCESS_EOF # 3. 更新XML文件 cat > "com_aipublisher.xml" << 'XML_EOF' <?xml version="1.0" encoding="utf-8"?> <extension type="component" method="upgrade" version="5.0">     <name>com_aipublisher</name>     <author>AI Publisher Team</author>     <creationDate>2024</creationDate>     <copyright>Copyright 2024</copyright>     <license>GPL v3</license>     <version>1.1.0</version>     <description>AI对话一键发布组件 - 完整REST API支持</description>          <scriptfile>script.php</scriptfile>          <languages folder="admin/language">         <language tag="en-GB">en-GB/en-GB.com_aipublisher.ini</language>         <language tag="en-GB">en-GB/en-GB.com_aipublisher.sys.ini</language>     </languages>          <administration>         <menu link="option=com_aipublisher">AI Publisher</menu>         <files folder="admin">             <filename>aipublisher.php</filename>             <filename>controller.php</filename>             <filename>config.xml</filename>             <folder>views</folder>             <folder>language</folder>         </files>         <submenu>             <menu link="option=com_aipublisher" view="dashboard">仪表板</menu>             <menu link="option=com_aipublisher&amp;view=articles">文章管理</menu>             <menu link="option=com_config&amp;view=component&amp;component=com_aipublisher">设置</menu>         </submenu>     </administration>          <files folder="site">         <filename>aipublisher.php</filename>         <filename>api.php</filename>         <filename>.htaccess</filename>     </files> </extension> XML_EOF # 4. 创建API文档 cat > "API_DOCUMENTATION.md" << 'DOC_EOF' # AI Publisher REST API 文档 ## 版本 1.1.0 ## 概述 完整的REST API接口,支持第三方程序通过标准HTTP请求与Joomla内容发布系统交互。 ## 基础信息 - **Base URL**: `https://your-domain.com/api.php` - **认证方式**: Token-based Authentication - **数据格式**: JSON - **字符编码**: UTF-8 ## 认证 所有需要认证的端点必须在请求中提供有效的API令牌。 ### 获取令牌 1. 安装组件时自动生成 2. 在管理界面查看 3. 使用调试端点获取 ## 端点列表 ### 1. 发布文章 **POST** `/api/publish` **请求参数**: ```json {   "token": "string (required)",   "content": "string (required)",   "title": "string (optional)",   "category": "integer (optional)",   "state": "integer (optional, 0=draft, 1=published)",   "author": "string (optional)",   "tags": "string or array (optional)",   "meta_description": "string (optional)",   "meta_keywords": "string (optional)" }
[/code]
2. 测试连接POST 
Code:
/api/test-connection
json
Code:
{ "token": "your_api_token" }

3. 获取网站配置
Code:
GET /api/website-configs
4. 保存配置
Code:
POST /api/save-config
json
Code:
{ "config": "配置数据" }

5. 健康检查
Code:
GET /api/health
6. API信息
Code:
GET /api/info
原来的Joomla端点(仍然可用)发布文章POST 
Code:
index.php?option=com_aipublisher&task=publish
json
Code:
{ "token": "your_api_token", "content": "文章内容" }

测试连接
Code:
GET index.php?option=com_aipublisher&task=test&token=your_api_token
获取API令牌
  1. 安装组件后复制显示的令牌
  2. 或在后台管理界面查看
  3. 或使用调试端点:
    Code:
    index.php?option=com_aipublisher&task=debug
示例代码cURL 发布文章
Code:
curl -X POST "https://your-domain.com/api/publish" \ -H "Content-Type: application/json" \ -d '{ "token": "your_token_here", "title": "测试文章", "content": "这是测试内容" }'

cURL 测试连接
Code:
​​​​​​​curl -X POST "https://your-domain.com/api/test-connection" \ -H "Content-Type: application/json" \ -d '{"token": "your_token_here"}'

Python 示例
Code:
import requests import json api_url = "https://your-domain.com/api/publish" token = "your_token_here" data = { "token": token, "title": "Python测试", "content": "通过Python API发布的内容" } response = requests.post(api_url, json=data) print(response.json())

错误代码
  • Code:
    200
    : 成功
  • Code:
    400
    : 请求参数错误
  • Code:
    401
    : 令牌无效
  • Code:
    404
    : 端点不存在
  • Code:
    500
    : 服务器内部错误
兼容性
  • Joomla 3.x, 4.x, 5.x
  • 支持标准RESTful API格式
  • 支持原来的Joomla URL格式
    EOF
cd ..重新打包
Code:
zip -r "com_aipublisher_api_adapter.zip" "com_aipublisher_final"
echo ""
echo "=============================================="
echo "✅ 第三方程序API适配器创建完成!"
echo "=============================================="
echo ""
echo "📦 安装包: com_aipublisher_api_adapter.zip"
echo ""
echo "✨ 新增功能:"
echo " ✅ 新增 site/api.php - RESTful API适配器"
echo " ✅ 新增 site/.htaccess - URL重写规则"
echo " ✅ 支持第三方程序的标准API端点"
echo " ✅ 版本升级到 1.1.0"
echo ""
echo "🔧 支持的第三方API端点:"
echo " ✅ POST /api/publish - 发布文章"
echo " ✅ POST /api/test-connection - 测试连接"
echo " ✅ POST /api/save-config - 保存配置"
echo " ✅ GET /api/conversations - 获取对话"
echo " ✅ GET /api/website-configs - 获取网站配置"
echo " ✅ GET /api/health - 健康检查"
echo " ✅ GET /api/info - API信息"
echo ""
echo "🔄 原来的Joomla端点仍然可用:"
echo " POST index.php?option=com_aipublisher&task=publish"
echo " GET index.php?option=com_aipublisher&task=test"
echo ""
echo "📖 详细文档:"
echo " 安装包中包含 API_USAGE.md 使用说明"
echo ""
echo "🚀 现在第三方程序应该能正常连接了!"
echo ""





 
Last edit: 2026-01-12 13:53 by service.

登录注册一个帐号 参加讨论

更多
2026-01-12 13:32 #1011 by service
简单点的话就是在网站根目录下创建一个API
Code:
## 方案4: 快速修复脚本(如果只需要简单适配) ```bash #!/bin/bash # 快速修复 - 创建简单的API适配器 echo "🔧 快速创建API适配器..." echo "======================================" # 进入网站根目录(假设) cd /home # 创建API适配器 cat > "api.php" << 'EOF' <?php define('_JEXEC', 1); define('JPATH_BASE', __DIR__); require_once JPATH_BASE . '/includes/defines.php'; require_once JPATH_BASE . '/includes/framework.php'; $app = JFactory::getApplication('site'); $app->initialise(); // 设置响应头 header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); // 处理预检请求 if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {     http_response_code(200);     exit; } // 获取请求信息 $method = $_SERVER['REQUEST_METHOD']; $path = $_SERVER['REQUEST_URI']; // 简单的路由 if ($method === 'POST' && strpos($path, '/api/publish') !== false) {     handleApiPublish(); } elseif ($method === 'POST' && strpos($path, '/api/test-connection') !== false) {     handleApiTest(); } else {     http_response_code(404);     echo json_encode([         'status' => 'error',         'message' => 'Endpoint not found: ' . $path,         'suggestion' => 'Use /api/publish or /api/test-connection'     ]); } function handleApiPublish() {     try {         $input = json_decode(file_get_contents('php://input'), true);                  if (!$input || empty($input['token']) || empty($input['content'])) {             throw new Exception('Missing required fields: token and content');         }                  // 验证令牌         $params = JComponentHelper::getParams('com_aipublisher');         $storedToken = $params->get('api_token', '');                  if (empty($storedToken) || $input['token'] !== $storedToken) {             http_response_code(401);             echo json_encode(['status' => 'error', 'message' => 'Invalid token']);             return;         }                  // 重定向到Joomla组件         $url = JUri::root() . 'index.php?option=com_aipublisher&task=publish&token=' . urlencode($input['token']);                  // 使用cURL转发请求         $ch = curl_init($url);         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);         curl_setopt($ch, CURLOPT_POST, true);         curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($input));         curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);                  $response = curl_exec($ch);         $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);         curl_close($ch);                  http_response_code($httpCode);         echo $response;              } catch (Exception $e) {         http_response_code(400);         echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);     } } function handleApiTest() {     try {         $input = json_decode(file_get_contents('php://input'), true);                  if (!$input || empty($input['token'])) {             throw new Exception('Token is required');         }                  // 验证令牌         $params = JComponentHelper::getParams('com_aipublisher');         $storedToken = $params->get('api_token', '');                  if (empty($storedToken) || $input['token'] !== $storedToken) {             http_response_code(401);             echo json_encode(['status' => 'error', 'message' => 'Invalid token']);             return;         }                  echo json_encode([             'status' => 'success',             'message' => 'Connection successful',             'timestamp' => date('Y-m-d H:i:s'),             'joomla_version' => JVERSION         ]);              } catch (Exception $e) {         http_response_code(400);         echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);     } } EOF echo "" echo "✅ 快速API适配器创建完成!" echo "" echo "📁 文件位置: /home/api.php" echo "" echo "🔗 现在可以访问:" echo "  POST https://www.com/api/publish" echo "  POST https://www.com/api/test-connection" echo "" echo "📝 示例请求:" echo 'curl -X POST "https://www.com/api/publish" \' echo '  -H "Content-Type: application/json" \' echo '  -d '\''{"token":"YOUR_TOKEN","content":"测试内容"}'\' echo ""

登录注册一个帐号 参加讨论

更多
2026-01-12 14:19 - 2026-01-12 14:19 #1012 by service
适配器不好用。考虑到已经有一个稳定工作的版本(使用 task=publish 参数),建议:

保持现有的 aipublisher.php 稳定版不变(它已经工作正常)
将API适配器作为可选功能,让用户可以选择使用REST风格API或传统风格
添加一个兼容层,让REST API调用可以转换为传统调用
或者简单一点:告诉第三方使用传统方式调用:

POST /index.php?option=com_aipublisher&task=publish&token=...
而不是 /api/publish
Last edit: 2026-01-12 14:19 by service.

登录注册一个帐号 参加讨论

更多
2026-01-12 14:23 #1013 by service
这个适配器还有很多问题:主要问题分析:
  1. REST API路由问题:你的 
    Code:
    api.php
     使用 
    Code:
    /api/
     前缀,但路由映射中缺少对多种路径格式的支持
  2. Joomla SEF URL问题:可能由于Joomla的URL重写,
    Code:
    /api/publish
     没有正确路由到 
    Code:
    api.php
  3. XML文件冲突:你有两个 
    Code:
    com_aipublisher.xml
     文件,这会导致安装问题
具体修复:1. 修复 
Code:
api.php
 的路由问题修改 
Code:
api.php
 开头的路径处理逻辑:php
Code:
​​​​​​​// 获取请求方法和路径 $method = $_SERVER['REQUEST_METHOD']; $requestUri = $_SERVER['REQUEST_URI']; // 移除查询字符串 $path = parse_url($requestUri, PHP_URL_PATH); $path = strtok($path, '?'); // 处理不同的URL前缀 // 1. 直接访问 /api.php if ($path === '/api.php') { $path = '/api'; } // 2. 访问 /api.php/api/publish 等情况 elseif (strpos($path, '/api.php') === 0) { $path = '/api' . substr($path, 8); } // 完整的路由映射表 $routes = [ 'POST' => [ '/api/publish' => 'handleApiPublish', '/api/test-connection' => 'handleApiTestConnection', '/api/save-config' => 'handleApiSaveConfig', '/api/conversations' => 'handleApiConversations' ], 'GET' => [ '/api/conversations' => 'handleApiGetConversations', '/api/website-configs' => 'handleApiGetWebsiteConfigs', '/api/health' => 'handleApiHealth', '/api/info' => 'handleApiInfo', '/api/status' => 'handleApiStatus', // 添加根路径路由 '/' => 'handleApiInfo', '/api' => 'handleApiInfo' ], 'PUT' => [ '/api/update-config' => 'handleApiUpdateConfig' ] ]; // 查找匹配的路由 - 改进匹配逻辑 $handler = null; $matchedRoute = null; if (isset($routes[$method])) { foreach ($routes[$method] as $route => $handlerName) { // 精确匹配 if ($path === $route) { $handler = $handlerName; $matchedRoute = $route; break; } // 前缀匹配(用于/api/路径) elseif ($route === '/api' && strpos($path, '/api') === 0) { $handler = $handlerName; $matchedRoute = $route; break; } } }
2. 添加Joomla集成函数在 
Code:
api.php
 中添加Joomla环境检测:php​​​​​​​
Code:
// 尝试自动加载Joomla function loadJoomlaEnvironment() { if (defined('_JEXEC')) { return true; } // 寻找Joomla根目录 $possiblePaths = [ dirname(__DIR__), dirname(dirname(__DIR__)), realpath(dirname(__FILE__) . '/../../..'), $_SERVER['DOCUMENT_ROOT'] ]; foreach ($possiblePaths as $basePath) { $joomlaPath = $basePath . '/includes/defines.php'; if (file_exists($joomlaPath)) { define('_JEXEC', 1); define('JPATH_BASE', $basePath); require_once $joomlaPath; require_once JPATH_BASE . '/includes/framework.php'; // 初始化应用程序 $app = JFactory::getApplication('site'); return true; } } return false; } // 加载Joomla环境 if (!loadJoomlaEnvironment()) { http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Joomla environment not found', 'code' => 'JOOMLA_NOT_FOUND' ]); exit; }
3. 修复XML文件冲突不要覆盖原有的 
Code:
com_aipublisher.xml
,而是创建一个新版本:修改 
Code:
third_part_API_adapt.sh
 中的这一部分:bash​​​​​​​
Code:
# 3. 更新XML文件(改为添加而不是覆盖) cat >> "com_aipublisher.xml" << 'XML_EOF' <files folder="site"> <filename>api.php</filename> <filename>.htaccess</filename> </files> XML_EOF
或者更好的方法:创建一个新的XML文件专门用于API适配器。4. 添加API测试端点在 
Code:
api.php
 中添加一个简单的测试端点来验证路由:php​​​​​​​
Code:
/** * 处理API根路径 */ function handleApiRoot() { echo json_encode([ 'status' => 'success', 'message' => 'AI Publisher API is running', 'version' => '1.1.0', 'timestamp' => date('Y-m-d H:i:s'), 'endpoints' => getAvailableEndpoints()['endpoints'] ]); } // 在路由表中添加 $routes = [ // ... 其他路由 'GET' => [ // ... 其他GET路由 '/' => 'handleApiRoot', '/api' => 'handleApiRoot' ] ];
5. 修复 
Code:
.htaccess
 文件更新 
Code:
.htaccess
 以更好地处理API路由:bash​​​​​​​
Code:
cat > "site/.htaccess" << 'HTACCESS_EOF' # AI Publisher API - URL Rewrite Rules <IfModule mod_rewrite.c> RewriteEngine On # 重写API请求到api.php RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^api/(.*)$ api.php?path=$1 [QSA,L] # 支持直接访问/api路径 RewriteCond %{REQUEST_URI} ^/api$ RewriteRule ^api$ api.php [QSA,L] # 直接访问api.php RewriteCond %{REQUEST_URI} ^/api\.php$ RewriteRule ^api\.php$ api.php [QSA,L] </IfModule> # CORS头部 <IfModule mod_headers.c> Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With" Header set X-Content-Type-Options "nosniff" Header set X-Frame-Options "SAMEORIGIN" Header set X-XSS-Protection "1; mode=block" # 处理OPTIONS预检请求 RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] </IfModule> HTACCESS_EOF
6. 添加调试信息在API响应中添加更多调试信息:
 php
Code:
// 在所有API响应中添加路径信息 echo json_encode([ 'status' => 'success', 'message' => 'Article published successfully', 'data' => $publishResult, 'meta' => [ 'timestamp' => date('Y-m-d H:i:s'), 'request_id' => uniqid('req_', true), 'processing_time' => getProcessingTime() . 'ms', 'debug' => [ 'request_path' => $path, 'request_method' => $method, 'matched_route' => $matchedRoute ] ] ]);
建议的解决方案:考虑到你已经有一个稳定工作的版本(使用 
Code:
task=publish
 参数),建议:
  1. 保持现有的 
    Code:
    aipublisher.php
     稳定版不变(它已经工作正常)
  2. 将API适配器作为可选功能,让用户可以选择使用REST风格API或传统风格
  3. 添加一个兼容层,让REST API调用可以转换为传统调用
或者简单一点:告诉第三方使用传统方式调用:
  • Code:
    POST /index.php?option=com_aipublisher&task=publish&token=...
  • 而不是 
    Code:
    /api/publish

登录注册一个帐号 参加讨论

更多
2026-01-12 15:32 #1014 by service
可以。知识API的访问方式不同

登录注册一个帐号 参加讨论

更多
2026-01-12 17:39 #1017 by service
手动测试网站API
Code:
# 测试传统API curl -X POST "https://bluetooth.com.cn/home/index.php?option=com_aipublisher&task=publish&format=json" \   -H "Content-Type: application/json" \   -d '{"title":"Test","content":"Test content","category":"test","api_key":"your_key"}' # 测试REST API curl -X POST "https://bluetooth.com.cn/api.php/api/publish" \   -H "Content-Type: application/json" \   -d '{"title":"Test","content":"Test content","category":"test","api_key":"your_key"}'

登录注册一个帐号 参加讨论