mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-01 14:52:48 +08:00
feat(content-type): add content_type field, markdown auto-conversion, and API doc updates
- Add content_type column to ul_post via migration - Install league/commonmark for markdown->HTML conversion - Add Post model accessors/setters for content_type and content - Update API Articles controller save/update with content_type support - Update API docs with content_type parameter and markdown example Closes content-type-support plan
This commit is contained in:
@@ -8,6 +8,7 @@ use app\BaseController;
|
|||||||
use app\model\Post;
|
use app\model\Post;
|
||||||
use app\model\PostCategory;
|
use app\model\PostCategory;
|
||||||
use app\model\PostTag;
|
use app\model\PostTag;
|
||||||
|
use League\CommonMark\CommonMarkConverter;
|
||||||
use think\Request;
|
use think\Request;
|
||||||
|
|
||||||
class Articles extends BaseController
|
class Articles extends BaseController
|
||||||
@@ -105,6 +106,21 @@ class Articles extends BaseController
|
|||||||
if (!isset($post_data['status'])) {
|
if (!isset($post_data['status'])) {
|
||||||
$post_data['status'] = 0;
|
$post_data['status'] = 0;
|
||||||
}
|
}
|
||||||
|
if (!isset($post_data['content_type'])) {
|
||||||
|
$post_data['content_type'] = 'html';
|
||||||
|
}
|
||||||
|
|
||||||
|
// content_type 白名单校验
|
||||||
|
$allowed_content_types = ['html', 'markdown'];
|
||||||
|
if (!in_array($post_data['content_type'], $allowed_content_types)) {
|
||||||
|
return json_message('无效的content_type', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Markdown→HTML 自动转换
|
||||||
|
if ($post_data['content_type'] === 'markdown' && !empty($post_data['content'])) {
|
||||||
|
$converter = new CommonMarkConverter(['html_input' => 'strip', 'allow_unsafe_links' => false]);
|
||||||
|
$post_data['content_html'] = $converter->convert($post_data['content']);
|
||||||
|
}
|
||||||
|
|
||||||
$model_post = Post::create($post_data);
|
$model_post = Post::create($post_data);
|
||||||
|
|
||||||
@@ -201,6 +217,12 @@ class Articles extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Markdown→HTML 自动转换
|
||||||
|
if (isset($post_data['content_type']) && $post_data['content_type'] === 'markdown' && !empty($post_data['content'])) {
|
||||||
|
$converter = new CommonMarkConverter(['html_input' => 'strip', 'allow_unsafe_links' => false]);
|
||||||
|
$post_data['content_html'] = $converter->convert($post_data['content']);
|
||||||
|
}
|
||||||
|
|
||||||
$model_post->save($post_data);
|
$model_post->save($post_data);
|
||||||
|
|
||||||
return json_message([], 0, '更新成功');
|
return json_message([], 0, '更新成功');
|
||||||
|
|||||||
@@ -156,6 +156,30 @@ class Post extends Base
|
|||||||
return trim($value);
|
return trim($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getContentTypeAttr($value)
|
||||||
|
{
|
||||||
|
return $value ?: 'html';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContentTypeAttr($value)
|
||||||
|
{
|
||||||
|
if (!in_array($value, ['html', 'markdown'], true)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid content_type: ' . $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setContentAttr($value)
|
||||||
|
{
|
||||||
|
return trim($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContentAttr($value)
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function getContentMarkdownAttr()
|
public function getContentMarkdownAttr()
|
||||||
{
|
{
|
||||||
$content_html = $this->getAttr('content_html');
|
$content_html = $this->getAttr('content_html');
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
"topthink/think-filesystem": "^2.0",
|
"topthink/think-filesystem": "^2.0",
|
||||||
"mibe/feedwriter": "^1.1",
|
"mibe/feedwriter": "^1.1",
|
||||||
"matomo/device-detector": "^6.1",
|
"matomo/device-detector": "^6.1",
|
||||||
"intervention/image": "^2.7"
|
"intervention/image": "^2.7",
|
||||||
|
"league/commonmark": "^2.8"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/var-dumper": "^4.2"
|
"symfony/var-dumper": "^4.2"
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use think\migration\Migrator;
|
||||||
|
use think\migration\db\Column;
|
||||||
|
|
||||||
|
class AddContentTypeToPost extends Migrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
|
||||||
|
*
|
||||||
|
* The following commands can be used in this method and Phinx will
|
||||||
|
* automatically reverse them when rolling back:
|
||||||
|
*
|
||||||
|
* createTable
|
||||||
|
* renameTable
|
||||||
|
* addColumn
|
||||||
|
* renameColumn
|
||||||
|
* addIndex
|
||||||
|
* addForeignKey
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$this->table('post')->addColumn(
|
||||||
|
Column::make('content_type', 'string', ['limit' => 20, 'default' => 'html', 'comment' => '内容类型: html/markdown'])
|
||||||
|
)->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -196,6 +196,7 @@ X-API-Key: {api_key}</div>
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "文章标题",
|
"title": "文章标题",
|
||||||
"content": "文章内容",
|
"content": "文章内容",
|
||||||
|
"content_type": "html",
|
||||||
"type": "1",
|
"type": "1",
|
||||||
"status": 1,
|
"status": 1,
|
||||||
"source": "api",
|
"source": "api",
|
||||||
@@ -238,6 +239,7 @@ X-API-Key: {api_key}</div>
|
|||||||
"title": "文章标题",
|
"title": "文章标题",
|
||||||
"content": "文章内容",
|
"content": "文章内容",
|
||||||
"content_html": "<p>HTML内容</p>",
|
"content_html": "<p>HTML内容</p>",
|
||||||
|
"content_type": "html",
|
||||||
"desc": "摘要",
|
"desc": "摘要",
|
||||||
"poster": "/uploads/poster.jpg",
|
"poster": "/uploads/poster.jpg",
|
||||||
"type": "1",
|
"type": "1",
|
||||||
@@ -268,12 +270,13 @@ X-API-Key: {api_key}</div>
|
|||||||
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
||||||
<tr><td>type</td><td>string</td><td>否</td><td>文章类型,默认 "1"</td></tr>
|
<tr><td>type</td><td>string</td><td>否</td><td>文章类型,默认 "1"</td></tr>
|
||||||
<tr><td>status</td><td>int</td><td>否</td><td>状态,默认 0(草稿)</td></tr>
|
<tr><td>status</td><td>int</td><td>否</td><td>状态,默认 0(草稿)</td></tr>
|
||||||
|
<tr><td>content_type</td><td>string</td><td>否</td><td>内容类型: "html"(默认) 或 "markdown"</td></tr>
|
||||||
<tr><td>categorys</td><td>array</td><td>否</td><td>分类 ID 数组,如 [1, 2]</td></tr>
|
<tr><td>categorys</td><td>array</td><td>否</td><td>分类 ID 数组,如 [1, 2]</td></tr>
|
||||||
<tr><td>tags</td><td>array</td><td>否</td><td>标签 ID 数组,如 [1, 2]</td></tr>
|
<tr><td>tags</td><td>array</td><td>否</td><td>标签 ID 数组,如 [1, 2]</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h4>请求示例</h4>
|
<h4>请求示例(HTML)</h4>
|
||||||
<div class="api-code">curl -X POST \
|
<div class="api-code">curl -X POST \
|
||||||
"https://example.com/index.php/api/articles/save" \
|
"https://example.com/index.php/api/articles/save" \
|
||||||
-H "Authorization: Bearer your_api_key" \
|
-H "Authorization: Bearer your_api_key" \
|
||||||
@@ -286,6 +289,21 @@ X-API-Key: {api_key}</div>
|
|||||||
"tags": [3]
|
"tags": [3]
|
||||||
}'</div>
|
}'</div>
|
||||||
|
|
||||||
|
<h4>请求示例(Markdown)</h4>
|
||||||
|
<div class="api-code">curl -X POST \
|
||||||
|
"https://example.com/index.php/api/articles/save" \
|
||||||
|
-H "Authorization: Bearer your_api_key" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "Markdown 文章",
|
||||||
|
"content": "# 标题\n\n段落内容",
|
||||||
|
"content_type": "markdown",
|
||||||
|
"type": "1",
|
||||||
|
"status": 1,
|
||||||
|
"categorys": [1, 2],
|
||||||
|
"tags": [3]
|
||||||
|
}'</div>
|
||||||
|
|
||||||
<h4>返回示例</h4>
|
<h4>返回示例</h4>
|
||||||
<div class="api-code">{
|
<div class="api-code">{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
@@ -319,6 +337,7 @@ X-API-Key: {api_key}</div>
|
|||||||
<tr><td>desc</td><td>string</td><td>否</td><td>文章摘要</td></tr>
|
<tr><td>desc</td><td>string</td><td>否</td><td>文章摘要</td></tr>
|
||||||
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
||||||
<tr><td>status</td><td>int</td><td>否</td><td>状态</td></tr>
|
<tr><td>status</td><td>int</td><td>否</td><td>状态</td></tr>
|
||||||
|
<tr><td>content_type</td><td>string</td><td>否</td><td>内容类型: "html"(默认) 或 "markdown"</td></tr>
|
||||||
<tr><td>categorys</td><td>array</td><td>否</td><td>分类 ID 数组(全量覆盖)</td></tr>
|
<tr><td>categorys</td><td>array</td><td>否</td><td>分类 ID 数组(全量覆盖)</td></tr>
|
||||||
<tr><td>tags</td><td>array</td><td>否</td><td>标签 ID 数组(全量覆盖)</td></tr>
|
<tr><td>tags</td><td>array</td><td>否</td><td>标签 ID 数组(全量覆盖)</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user