feat(api): add article/attachment API endpoints, admin management, and API docs

- Articles API: list/detail/create/update/delete with source-based permission control
- Attachments API: upload/list/delete with source-based permission control
- ApiKeyInfo API: query current key permissions
- Admin ApiKey management: generate/regenerate/toggle/permission settings with layui UI
- Frontend API documentation page with complete interface reference
This commit is contained in:
augushong
2026-04-27 00:38:57 +08:00
parent ac4d34884e
commit dc116a1c77
7 changed files with 1365 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
<?php
namespace app\admin\controller;
use app\model\ApiKey as ApiKeyModel;
use think\facade\Session;
use think\facade\View;
class ApiKey extends Common
{
/**
* API Key 管理页面
*/
public function index()
{
$admin_id = Session::get('admin_id');
$api_key = ApiKeyModel::where('admin_id', $admin_id)->find();
if (!empty($api_key)) {
$api_key->api_key_preview = substr($api_key->getData('api_key'), 0, 8) . '...';
$api_key->status_text = $api_key->status == 1 ? '启用' : '禁用';
$api_key->create_time_text = date('Y-m-d H:i:s', $api_key->create_time);
}
View::assign('api_key', $api_key);
View::assign('admin_info', $this->adminInfo);
return View::fetch();
}
/**
* 生成 API Key
*/
public function generate()
{
$admin_id = Session::get('admin_id');
$name = $this->request->param('name', '');
$exists = ApiKeyModel::where('admin_id', $admin_id)->find();
if (!empty($exists)) {
return json_message('您已有 API Key如需更换请使用重新生成');
}
$raw_key = ApiKeyModel::generateKey($admin_id, $name);
return json_message(['api_key' => $raw_key, 'name' => $name], 0, 'API Key 生成成功,请妥善保管');
}
/**
* 重新生成 API Key
*/
public function regenerate()
{
$id = $this->request->param('id', 0);
$admin_id = Session::get('admin_id');
$api_key = ApiKeyModel::find($id);
if (empty($api_key) || $api_key->admin_id != $admin_id) {
return json_message('API Key 不存在或无权操作');
}
$raw_key = ApiKeyModel::regenerateKey($id);
return json_message(['api_key' => $raw_key], 0, 'API Key 已重新生成');
}
/**
* 启用/禁用 API Key
*/
public function toggle()
{
$id = $this->request->param('id', 0);
$admin_id = Session::get('admin_id');
$api_key = ApiKeyModel::find($id);
if (empty($api_key) || $api_key->admin_id != $admin_id) {
return json_message('API Key 不存在或无权操作');
}
$api_key->status = $api_key->status == 1 ? 0 : 1;
$api_key->save();
return json_message('', 0, '状态已更新');
}
/**
* 切换写权限
*/
public function toggleWrite()
{
$id = $this->request->param('id', 0);
$field = $this->request->param('field', '');
$value = $this->request->param('value', 0);
$admin_id = Session::get('admin_id');
if (!in_array($field, ['can_write_own', 'can_write_other'])) {
return json_message('无效的权限字段');
}
$api_key = ApiKeyModel::find($id);
if (empty($api_key) || $api_key->admin_id != $admin_id) {
return json_message('API Key 不存在或无权操作');
}
$api_key->$field = $value ? 1 : 0;
$api_key->save();
return json_message('', 0, '权限已更新');
}
/**
* 设置删除权限
*/
public function updateDelete()
{
$id = $this->request->param('id', 0);
$value = $this->request->param('value', 0);
$admin_id = Session::get('admin_id');
$api_key = ApiKeyModel::find($id);
if (empty($api_key) || $api_key->admin_id != $admin_id) {
return json_message('API Key 不存在或无权操作');
}
$api_key->can_delete = intval($value);
$api_key->save();
return json_message('', 0, '删除权限已更新');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace app\api\controller;
use app\BaseController;
class ApiKeyInfo extends BaseController
{
protected $middleware = [\app\middleware\ApiKeyAuth::class];
public function info()
{
$request = $this->request;
$can_delete_text = '不能删除';
if ($request->can_delete == 1) {
$can_delete_text = '仅删除API数据';
} elseif ($request->can_delete == 2) {
$can_delete_text = '可删除所有数据';
}
return json_message([
'admin_id' => $request->admin_id,
'can_write_own' => $request->can_write_own,
'can_write_other' => $request->can_write_other,
'can_delete' => $request->can_delete,
'permissions_text' => [
'can_write_own' => $request->can_write_own ? '可管理自己的数据' : '不可管理自己的数据',
'can_write_other' => $request->can_write_other ? '可管理后台数据' : '不可管理后台数据',
'can_delete' => $can_delete_text,
],
]);
}
}

View File

@@ -0,0 +1,241 @@
<?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 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']);
}
$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;
}
$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']);
// 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,
]);
}
}
}
$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, '删除成功');
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace app\api\controller;
use app\BaseController;
use app\UploadFiles as AppUploadFiles;
use app\model\UploadFiles as UploadFilesModel;
use app\middleware\ApiKeyAuth;
class Attachments extends BaseController
{
protected $middleware = [ApiKeyAuth::class];
/**
* 附件列表
*/
public function index()
{
$page = $this->request->param('page', 1, 'intval');
$limit = $this->request->param('limit', 20, 'intval');
$type = $this->request->param('type', '');
$query = UploadFilesModel::order('id desc');
if (!empty($type)) {
$query->where('type', $type);
}
$list = $query->paginate([
'list_rows' => $limit,
'page' => $page,
]);
$items = [];
foreach ($list as $item) {
$items[] = [
'id' => $item->id,
'name' => $item->getData('file_name'),
'save_name' => $item->getData('save_name'),
'url' => $item->src,
'type' => $item->getData('type'),
'size' => $item->getData('file_size'),
'source' => $item->getData('source'),
'create_time' => $item->create_time,
];
}
return json_message([
'list' => $items,
'total' => $list->total(),
'page' => $page,
]);
}
/**
* 上传附件
*/
public function upload()
{
if (empty($this->request->can_write_own)) {
return json_message('无权操作', 403);
}
$file = $this->request->file('file');
if (empty($file)) {
return json_message('请选择上传文件');
}
try {
AppUploadFiles::fileScan($file);
$model_file = AppUploadFiles::saveFile($file, 'api_upload');
$upload_model = UploadFilesModel::where('save_name', $model_file->getData('save_name'))->find();
if ($upload_model) {
$upload_model->source = 'api';
$upload_model->save();
}
return json_message([
'id' => $model_file->id,
'name' => $model_file->getData('file_name'),
'url' => $model_file->src,
'size' => $model_file->getData('file_size'),
], 0, '上传成功');
} catch (\Throwable $th) {
return json_message($th->getMessage());
}
}
/**
* 删除附件
*/
public function delete()
{
$id = $this->request->param('id', 0, 'intval');
if (empty($id)) {
return json_message('缺少参数');
}
$file = UploadFilesModel::find($id);
if (empty($file)) {
return json_message('附件不存在');
}
$source = $file->getData('source');
$can_delete = $this->request->can_delete;
if ($source === 'api' && $can_delete < 1) {
return json_message('无权操作', 403);
}
if ($source === 'admin' && $can_delete != 2) {
return json_message('无权操作', 403);
}
if (!in_array($source, ['api', 'admin']) && $can_delete < 2) {
return json_message('无权操作', 403);
}
$file->delete();
return json_message('', 0, '删除成功');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace app\index\controller;
use think\facade\View;
class ApiDoc extends Common
{
public function index()
{
$page_title = 'API 文档';
View::assign('page_title', $page_title);
return View::fetch();
}
}