Files
ulthon_information/app/api/controller/Articles.php
augushong cbf9b21b96 feat(api): 增强文章API功能并更新文档
- 为文章创建和更新接口添加 `publish_time` 字段支持
- 升级 Markdown 解析器以支持表格扩展
- 增加数据库字段默认值设置,避免严格模式错误
- 禁止客户端设置 `create_time` 和 `update_time` 字段
- 更新 API 文档以反映上述更改
- 将临时文件和工具目录添加到 .gitignore
2026-04-30 12:17:50 +08:00

333 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace app\api\controller;
use app\BaseController;
use app\model\Post;
use app\model\PostCategory;
use app\model\PostTag;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\MarkdownConverter;
use think\Request;
class Articles extends BaseController
{
protected $middleware = [\app\middleware\ApiKeyAuth::class];
/**
* 文章列表
*/
public function index(Request $request)
{
$page = $request->param('page', 1, 'intval');
$limit = $request->param('limit', 15, 'intval');
$type = $request->param('type', '');
$category_id = $request->param('category_id', 0, 'intval');
$keyword = $request->param('keyword', '');
$model_list = Post::with(['categorys.category', 'tags.tag'])
->order('id desc');
if (!empty($type)) {
$model_list = $model_list->where('type', $type);
}
if (!empty($category_id)) {
$model_list = $model_list->where('category_id', $category_id);
}
if (!empty($keyword)) {
$model_list = $model_list->where('title', 'like', "%{$keyword}%");
}
$list = $model_list->paginate([
'page' => $page,
'list_rows' => $limit,
]);
return json_message([
'list' => $list->items(),
'total' => $list->total(),
'page' => $page,
]);
}
/**
* 文章详情
*/
public function read(Request $request)
{
$id = $request->param('id', 0, 'intval');
if ($id <= 0) {
return json_message('参数错误');
}
$post = Post::with(['categorys.category', 'tags.tag'])->find($id);
if (empty($post)) {
return json_message('文章不存在');
}
return json_message(['post' => $post]);
}
/**
* 创建文章
*/
public function save(Request $request)
{
if ($request->can_write_own != 1) {
return json_message('无权操作', 403);
}
$post_data = $request->post();
if (empty($post_data['title'])) {
return json_message('标题必填');
}
$categorys = [];
$tags = [];
if (isset($post_data['categorys'])) {
$categorys = $post_data['categorys'];
unset($post_data['categorys']);
}
if (isset($post_data['tags'])) {
$tags = $post_data['tags'];
unset($post_data['tags']);
}
// 禁止客户端设置的时间字段
unset($post_data['create_time'], $post_data['update_time']);
$post_data['uid'] = uniqid();
$post_data['source'] = 'api';
$post_data['create_time'] = time();
$post_data['update_time'] = time();
if (!isset($post_data['type'])) {
$post_data['type'] = '1';
}
if (!isset($post_data['status'])) {
$post_data['status'] = 0;
}
if (!isset($post_data['content_type'])) {
$post_data['content_type'] = 'html';
}
// 校验 publish_time 格式(必须是 Y-m-d H:i:s
if (isset($post_data['publish_time']) && !empty($post_data['publish_time'])) {
if (!preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $post_data['publish_time'])) {
return json_message('publish_time 格式错误,应为 Y-m-d H:i:s如 2024-01-15 14:30:00');
}
}
// 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'])) {
$environment = Environment::createCommonMarkEnvironment();
$environment->addExtension(new TableExtension());
$converter = new MarkdownConverter($environment);
$post_data['content_html'] = $converter->convert($post_data['content'])->getContent();
}
// 提供字段默认值,避免数据库严格模式报错
$default_fields = [
'jump_to_btn_title' => '',
'jump_to_url' => '',
'jump_to_url_status' => 0,
'poster' => '',
'desc' => '',
'author_name' => '',
'hits' => 0,
'is_top' => 0,
'sort' => 0,
'files' => '',
'pictures' => '',
'tpl_name' => '',
];
foreach ($default_fields as $field => $default) {
if (!isset($post_data[$field])) {
$post_data[$field] = $default;
}
}
$model_post = Post::create($post_data);
foreach ($categorys as $category) {
PostCategory::create([
'post_id' => $model_post->id,
'category_id' => $category,
]);
}
foreach ($tags as $tag) {
PostTag::create([
'post_id' => $model_post->id,
'tag_id' => $tag,
]);
}
return json_message(['id' => $model_post->id, 'uid' => $model_post->uid], 0, '创建成功');
}
/**
* 编辑文章
*/
public function update(Request $request)
{
$id = $request->param('id', 0, 'intval');
if ($id <= 0) {
return json_message('参数错误');
}
$model_post = Post::find($id);
if (empty($model_post)) {
return json_message('文章不存在');
}
$source = $model_post->getData('source');
if ($source === 'api') {
if ($request->can_write_own != 1) {
return json_message('无权操作', 403);
}
} else {
if ($request->can_write_other != 1) {
return json_message('无权操作', 403);
}
}
$post_data = $request->post();
unset($post_data['id']);
// 禁止客户端设置的时间字段
unset($post_data['create_time'], $post_data['update_time']);
// 校验 publish_time 格式(必须是 Y-m-d H:i:s
if (isset($post_data['publish_time']) && !empty($post_data['publish_time'])) {
if (!preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $post_data['publish_time'])) {
return json_message('publish_time 格式错误,应为 Y-m-d H:i:s如 2024-01-15 14:30:00');
}
}
// categorys diff update
if (isset($post_data['categorys'])) {
$categorys = $post_data['categorys'];
unset($post_data['categorys']);
$old_category_list = PostCategory::where('post_id', $id)->select();
$old_category_ids = array_column($old_category_list->toArray(), 'category_id');
// old has, new doesn't -> delete
foreach ($old_category_list as $model_category) {
if (!in_array($model_category->category_id, $categorys)) {
$model_category->delete();
}
}
// new has, old doesn't -> create
foreach ($categorys as $category) {
if (!in_array($category, $old_category_ids)) {
PostCategory::create([
'post_id' => $id,
'category_id' => $category,
]);
}
}
}
// tags diff update
if (isset($post_data['tags'])) {
$tags = $post_data['tags'];
unset($post_data['tags']);
$old_tag_list = PostTag::where('post_id', $id)->select();
$old_tag_ids = array_column($old_tag_list->toArray(), 'tag_id');
foreach ($old_tag_list as $model_tag) {
if (!in_array($model_tag->tag_id, $tags)) {
$model_tag->delete();
}
}
foreach ($tags as $tag) {
if (!in_array($tag, $old_tag_ids)) {
PostTag::create([
'post_id' => $id,
'tag_id' => $tag,
]);
}
}
}
// Markdown→HTML 自动转换(启用表格扩展)
if (isset($post_data['content_type']) && $post_data['content_type'] === 'markdown' && !empty($post_data['content'])) {
$environment = Environment::createCommonMarkEnvironment();
$environment->addExtension(new TableExtension());
$converter = new MarkdownConverter($environment);
$post_data['content_html'] = $converter->convert($post_data['content'])->getContent();
}
// 提供字段默认值,避免数据库严格模式报错
$default_fields = [
'jump_to_btn_title' => '',
'jump_to_url' => '',
'jump_to_url_status' => 0,
'poster' => '',
'desc' => '',
'author_name' => '',
'hits' => 0,
'is_top' => 0,
'sort' => 0,
'files' => '',
'pictures' => '',
'tpl_name' => '',
];
foreach ($default_fields as $field => $default) {
if (!isset($post_data[$field])) {
$post_data[$field] = $default;
}
}
$model_post->save($post_data);
return json_message([], 0, '更新成功');
}
/**
* 删除文章
*/
public function delete(Request $request)
{
$id = $request->param('id', 0, 'intval');
if ($id <= 0) {
return json_message('参数错误');
}
$model_post = Post::find($id);
if (empty($model_post)) {
return json_message('文章不存在');
}
$source = $model_post->getData('source');
if ($source === 'api') {
if ($request->can_delete < 1) {
return json_message('无权删除', 403);
}
} else {
if ($request->can_delete != 2) {
return json_message('无权删除', 403);
}
}
$model_post->delete();
PostCategory::where('post_id', $id)->delete();
PostTag::where('post_id', $id)->delete();
return json_message([], 0, '删除成功');
}
}