- Posts: 61
- Thank you received: 0
Technical discuss
Joomla 网站API第三方适配程序
12 Jan 2026 13:16 - 12 Jan 2026 13:53 #1010
by service
New Topic
如果第三方程序使用的URL路径与当前joomla网站的组件不匹配。本站组件使用的是Joomla的标准路由格式,而第三方程序期望的是RESTful API格式:
第三方程序期望的是标准的RESTful API端点(如
),而本站组件使用的是Joomla格式的URL(
)。
以下是一种解决方案:安装包含
的适配器版本
创建适配第三方程序的API端点文件
[/code]
2. 测试连接POST
json
3. 获取网站配置
4. 保存配置
json
5. 健康检查
6. API信息
原来的Joomla端点(仍然可用)发布文章POST
json
测试连接
获取API令牌
cURL 测试连接
Python 示例
错误代码
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 ""
第三方程序期望的是标准的RESTful API端点(如
Code:
/api/publish
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&view=articles">文章管理</menu>
<menu link="option=com_config&view=component&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)"
}
2. 测试连接POST
Code:
/api/test-connection
Code:
{
"token": "your_api_token"
}
3. 获取网站配置
Code:
GET /api/website-configs
Code:
POST /api/save-config
Code:
{
"config": "配置数据"
}
5. 健康检查
Code:
GET /api/health
Code:
GET /api/info
Code:
index.php?option=com_aipublisher&task=publish
Code:
{
"token": "your_api_token",
"content": "文章内容"
}
测试连接
Code:
GET index.php?option=com_aipublisher&task=test&token=your_api_token
- 安装组件后复制显示的令牌
- 或在后台管理界面查看
- 或使用调试端点:
Code:index.php?option=com_aipublisher&task=debug
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
Code:
zip -r "com_aipublisher_api_adapter.zip" "com_aipublisher_final"
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: 12 Jan 2026 13:53 by service.
Please Log in or Create an account to join the conversation.
12 Jan 2026 13:32 #1011
by service
Replied by service on topic Joomla 网站API第三方适配程序
简单点的话就是在网站根目录下创建一个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 ""
Please Log in or Create an account to join the conversation.
12 Jan 2026 14:19 - 12 Jan 2026 14:19 #1012
by service
Replied by service on topic Joomla 网站API第三方适配程序
适配器不好用。考虑到已经有一个稳定工作的版本(使用 task=publish 参数),建议:
保持现有的 aipublisher.php 稳定版不变(它已经工作正常)
将API适配器作为可选功能,让用户可以选择使用REST风格API或传统风格
添加一个兼容层,让REST API调用可以转换为传统调用
或者简单一点:告诉第三方使用传统方式调用:
POST /index.php?option=com_aipublisher&task=publish&token=...
而不是 /api/publish
保持现有的 aipublisher.php 稳定版不变(它已经工作正常)
将API适配器作为可选功能,让用户可以选择使用REST风格API或传统风格
添加一个兼容层,让REST API调用可以转换为传统调用
或者简单一点:告诉第三方使用传统方式调用:
POST /index.php?option=com_aipublisher&task=publish&token=...
而不是 /api/publish
Last edit: 12 Jan 2026 14:19 by service.
Please Log in or Create an account to join the conversation.
12 Jan 2026 14:23 #1013
by service
Replied by service on topic Joomla 网站API第三方适配程序
这个适配器还有很多问题:主要问题分析:
的路由问题修改
开头的路径处理逻辑:php
2. 添加Joomla集成函数在
中添加Joomla环境检测:php
3. 修复XML文件冲突不要覆盖原有的
,而是创建一个新版本:修改
中的这一部分:bash
或者更好的方法:创建一个新的XML文件专门用于API适配器。4. 添加API测试端点在
中添加一个简单的测试端点来验证路由:php
5. 修复
文件更新
以更好地处理API路由:bash
6. 添加调试信息在API响应中添加更多调试信息:
php
建议的解决方案:考虑到你已经有一个稳定工作的版本(使用
参数),建议:
- REST API路由问题:你的
使用Code:api.php前缀,但路由映射中缺少对多种路径格式的支持Code:/api/
- Joomla SEF URL问题:可能由于Joomla的URL重写,
没有正确路由到Code:/api/publishCode:api.php
- XML文件冲突:你有两个
文件,这会导致安装问题Code:com_aipublisher.xml
Code:
api.php
Code:
api.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;
}
}
}
Code:
api.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;
}
Code:
com_aipublisher.xml
Code:
third_part_API_adapt.sh
Code:
# 3. 更新XML文件(改为添加而不是覆盖)
cat >> "com_aipublisher.xml" << 'XML_EOF'
<files folder="site">
<filename>api.php</filename>
<filename>.htaccess</filename>
</files>
XML_EOF
Code:
api.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'
]
];
Code:
.htaccess
Code:
.htaccess
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
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
- 保持现有的
稳定版不变(它已经工作正常)Code:aipublisher.php
- 将API适配器作为可选功能,让用户可以选择使用REST风格API或传统风格
- 添加一个兼容层,让REST API调用可以转换为传统调用
-
Code:POST /index.php?option=com_aipublisher&task=publish&token=...
- 而不是
Code:/api/publish
Please Log in or Create an account to join the conversation.
12 Jan 2026 15:32 #1014
by service
Replied by service on topic Joomla 网站API第三方适配程序
可以。知识API的访问方式不同
Please Log in or Create an account to join the conversation.

