java/wangpan-gateway/target/classes/META-INF/resources/api-doc.html
liRQ 1f498d2396 feat: 完成网盘电影刮削系统全量功能开发
本次提交完成了整个网盘电影刮削系统的功能开发,包含以下核心改动:
1.  新增个人资料模块,支持用户修改用户名和头像上传
2.  实现电影刮削状态查询接口,可查看刮削进度、结果和失败原因
3.  完善每日推荐功能,支持当日固定推荐结果缓存
4.  新增图标清理定时任务,自动清理过期图标文件
5.  优化数据库查询映射,修复关联表查询错误
6.  统一各服务配置,移除调试日志并升级加密配置
7.  新增MinIO工具类和文件上传支持
8.  完善接口文档,补充所有新增API的说明示例
2026-06-25 18:25:08 +08:00

2460 lines
129 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网盘系统 API 接口文档</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 1000px;
margin: 0 auto;
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: white;
padding: 30px 40px;
text-align: center;
}
.header h1 { font-size: 28px; margin-bottom: 8px; }
.header p { color: #a0aec0; font-size: 14px; }
.gateway-badge {
display: inline-block;
background: #48bb78;
color: white;
padding: 6px 16px;
border-radius: 20px;
font-size: 13px;
margin-top: 12px;
font-family: monospace;
}
.nav {
display: flex;
background: #f7fafc;
border-bottom: 1px solid #e2e8f0;
overflow-x: auto;
}
.nav-item {
padding: 14px 24px;
cursor: pointer;
border-bottom: 3px solid transparent;
white-space: nowrap;
font-size: 14px;
color: #4a5568;
transition: all 0.2s;
}
.nav-item:hover { background: #edf2f7; }
.nav-item.active {
border-bottom-color: #667eea;
color: #667eea;
font-weight: 600;
}
.badge-danger { background: #e53e3e; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
.content { padding: 24px 40px; }
.section { display: none; }
.section.active { display: block; }
.service-card {
background: #f7fafc;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
border-left: 4px solid #667eea;
}
.service-card h2 {
font-size: 18px;
color: #2d3748;
margin-bottom: 4px;
}
.service-card .port {
font-size: 12px;
color: #718096;
font-family: monospace;
}
.api-item {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
margin: 12px 0;
overflow: hidden;
}
.api-header {
display: flex;
align-items: center;
padding: 14px 18px;
cursor: pointer;
transition: background 0.2s;
}
.api-header:hover { background: #f7fafc; }
.method {
display: inline-block;
padding: 4px 10px;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
font-family: monospace;
margin-right: 12px;
min-width: 65px;
text-align: center;
}
.method.GET { background: #c6f6d5; color: #276749; }
.method.POST { background: #bee3f8; color: #2a4365; }
.method.PUT { background: #fefcbf; color: #744210; }
.method.DELETE { background: #fed7d7; color: #742a2a; }
.api-path {
font-family: monospace;
font-size: 14px;
color: #2d3748;
flex: 1;
}
.api-desc {
font-size: 13px;
color: #718096;
margin-left: 12px;
}
.api-detail {
display: none;
padding: 0 18px 18px;
border-top: 1px solid #e2e8f0;
}
.api-detail.show { display: block; }
.detail-section { margin: 12px 0; }
.detail-section h4 {
font-size: 13px;
color: #4a5568;
margin-bottom: 6px;
font-weight: 600;
}
.param-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.param-table th, .param-table td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
.param-table th {
background: #f7fafc;
color: #4a5568;
font-weight: 600;
}
.param-table td { color: #2d3748; }
.code-block {
background: #1a202c;
color: #a0aec0;
padding: 14px 18px;
border-radius: 6px;
font-family: 'Fira Code', monospace;
font-size: 12px;
overflow-x: auto;
line-height: 1.6;
}
.code-block .key { color: #63b3ed; }
.code-block .string { color: #68d391; }
.code-block .number { color: #f6ad55; }
.code-block .keyword { color: #fc8181; }
.status-table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
}
.status-table th, .status-table td {
padding: 10px 14px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
font-size: 13px;
}
.status-table th { background: #f7fafc; color: #4a5568; }
.arch-diagram {
background: #f7fafc;
border-radius: 8px;
padding: 20px;
text-align: center;
margin: 16px 0;
}
.arch-flow {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
font-size: 13px;
}
.arch-box {
padding: 8px 16px;
border-radius: 6px;
color: white;
font-weight: 600;
}
.arch-box.gateway { background: #48bb78; }
.arch-box.user { background: #4299e1; }
.arch-box.drive { background: #ed8936; }
.arch-box.tmdb { background: #9f7aea; }
.arch-arrow { color: #a0aec0; font-size: 18px; }
/* 认证标识 */
.auth-badge {
display: inline-block;
background: #ed8936;
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
margin-left: 8px;
vertical-align: middle;
}
/* 搜索框样式 */
.search-container {
padding: 16px 40px;
background: #f7fafc;
border-bottom: 1px solid #e2e8f0;
}
.search-box {
display: flex;
align-items: center;
background: white;
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 10px 16px;
transition: all 0.2s;
}
.search-box:focus-within {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-icon {
color: #a0aec0;
margin-right: 10px;
font-size: 16px;
}
.search-input {
flex: 1;
border: none;
outline: none;
font-size: 14px;
color: #2d3748;
}
.search-input::placeholder {
color: #a0aec0;
}
.search-clear {
cursor: pointer;
color: #a0aec0;
font-size: 18px;
padding: 0 4px;
display: none;
}
.search-clear:hover {
color: #e53e3e;
}
.search-clear.show {
display: block;
}
.search-result-count {
font-size: 12px;
color: #718096;
margin-top: 8px;
display: none;
}
.search-result-count.show {
display: block;
}
/* 搜索高亮 */
.highlight {
background: #fefcbf;
padding: 1px 2px;
border-radius: 2px;
}
/* 搜索时隐藏不匹配的项 */
.api-item.search-hidden {
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>网盘系统 API 接口文档</h1>
<p>微服务架构 · 统一网关入口</p>
<div class="gateway-badge">网关地址http://localhost:8888</div>
</div>
<div class="nav">
<div class="nav-item active" data-section="overview">架构总览</div>
<div class="nav-item" data-section="user">用户服务</div>
<div class="nav-item" data-section="users">用户管理</div>
<div class="nav-item" data-section="profile">个人资料</div>
<div class="nav-item" data-section="drive">网盘服务</div>
<div class="nav-item" data-section="icon">Icon服务</div>
<div class="nav-item" data-section="tmdb">TMDB服务</div>
<div class="nav-item" data-section="status">状态码</div>
</div>
<!-- 搜索框 -->
<div class="search-container">
<div class="search-box">
<span class="search-icon">🔍</span>
<input type="text" class="search-input" id="apiSearch" placeholder="搜索接口路径、描述或功能...">
<span class="search-clear" id="searchClear">×</span>
</div>
<div class="search-result-count" id="searchResultCount"></div>
</div>
<div class="content">
<!-- 架构总览 -->
<div class="section active" id="overview">
<div class="arch-diagram">
<h3 style="margin-bottom: 16px; color: #2d3748;">服务架构图</h3>
<div class="arch-flow">
<div class="arch-box" style="background: #4a5568;">前端</div>
<span class="arch-arrow"></span>
<div class="arch-box gateway">Gateway:8888</div>
<span class="arch-arrow"></span>
<div class="arch-box user">User:8081</div>
</div>
<div class="arch-flow" style="margin-top: 8px;">
<div class="arch-box" style="background: #4a5568; visibility: hidden;">前端</div>
<span class="arch-arrow" style="visibility: hidden;"></span>
<div class="arch-box gateway" style="visibility: hidden;">Gateway</div>
<span class="arch-arrow"></span>
<div class="arch-box drive">Drive:8082</div>
</div>
<div class="arch-flow" style="margin-top: 8px;">
<div class="arch-box" style="background: #4a5568; visibility: hidden;">前端</div>
<span class="arch-arrow" style="visibility: hidden;"></span>
<div class="arch-box gateway" style="visibility: hidden;">Gateway</div>
<span class="arch-arrow"></span>
<div class="arch-box tmdb">TMDB:8083</div>
</div>
</div>
<div class="service-card">
<h2>路由规则</h2>
<table class="param-table">
<tr><th>请求路径</th><th>转发服务</th><th>说明</th></tr>
<tr><td>/api/login, /api/register</td><td>user-service</td><td>登录注册</td></tr>
<tr><td>/api/token/**, /api/login-logs/**</td><td>user-service</td><td>令牌/日志</td></tr>
<tr><td>/api/users/**</td><td>user-service</td><td>用户管理</td></tr>
<tr><td>/api/profile/**</td><td>user-service</td><td>个人资料</td></tr>
<tr><td>/api/drive/**</td><td>drive-service</td><td>网盘管理</td></tr>
<tr><td>/api/icon/**</td><td>drive-service</td><td>Icon管理</td></tr>
<tr><td>/api/movies/**</td><td>tmdb-service</td><td>电影管理</td></tr>
<tr><td>/api/tmdb/**, /api/tmdb-scraper/**</td><td>tmdb-service</td><td>TMDB刮削</td></tr>
</table>
</div>
</div>
<!-- 用户服务 -->
<div class="section" id="user">
<div class="service-card">
<h2>用户认证服务</h2>
<span class="port">wangpan-user-service : 8081</span>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/login</span>
<span class="api-desc">用户登录</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"username"</span>: <span class="string">"用户名"</span>,
<span class="key">"password"</span>: <span class="string">"密码"</span>
}</div>
</div>
<div class="detail-section">
<h4>成功响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"登录成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">7</span>,
<span class="key">"username"</span>: <span class="string">"lrq"</span>,
<span class="key">"userType"</span>: <span class="number">0</span>, <span style="color:#718096">// 0=普通用户, 1=VIP, 2=管理员</span>
<span class="key">"status"</span>: <span class="number">1</span>,
<span class="key">"email"</span>: <span class="string">"xxx@xx.com"</span>,
<span class="key">"phone"</span>: <span class="string">"138****0000"</span>,
<span class="key">"realName"</span>: <span class="string">"张三"</span>,
<span class="key">"avatarUrl"</span>: <span class="string">""</span>,
<span class="key">"lastLoginIp"</span>: <span class="string">"127.0.0.1"</span>,
<span class="key">"lastLoginTime"</span>: <span class="string">"2026-06-10T11:20:21"</span>,
<span class="key">"hasTmdbKey"</span>: <span class="keyword">false</span>,
<span class="key">"token"</span>: <span class="string">"tk_xxx..."</span>,
<span class="key">"tokenType"</span>: <span class="string">"Bearer"</span>,
<span class="key">"expireIn"</span>: <span class="number">604800</span>
}
}</div>
</div>
<div class="detail-section">
<h4>userType 说明</h4>
<table class="param-table">
<tr><th></th><th>角色</th><th>权限说明</th></tr>
<tr><td><strong>0</strong></td><td>普通用户</td><td>仅访问自己的数据(如日志只看自己的)</td></tr>
<tr><td><strong>1</strong></td><td>VIP 用户</td><td>普通用户 + 更多存储空间</td></tr>
<tr><td><strong>2</strong></td><td>管理员</td><td>可查看所有用户数据(如全部日志)</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/register</span>
<span class="api-desc">用户注册</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"username"</span>: <span class="string">"用户名"</span>,
<span class="key">"password"</span>: <span class="string">"密码"</span>
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/token/validate</span>
<span class="api-desc">令牌验证</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求头</h4>
<div class="code-block">Authorization: Bearer &lt;token&gt;</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/login-logs</span>
<span class="api-desc">登录日志查询</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>权限说明</h4>
<table class="param-table">
<tr><th>用户类型</th><th>可见范围</th></tr>
<tr><td>普通用户userType=0/1</td><td style="color:#e53e3e">仅自己的登录记录</td></tr>
<tr><td>管理员userType=2</td><td style="color:#38a169">所有用户的登录记录</td></tr>
</table>
</div>
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>username</td><td>String</td><td></td><td>按用户名模糊筛选(管理员可用)</td></tr>
<tr><td>result</td><td>String</td><td></td><td>登录结果success / fail</td></tr>
<tr><td>startDate</td><td>String</td><td></td><td>开始日期格式yyyy-MM-dd如 2026-06-01</td></tr>
<tr><td>endDate</td><td>String</td><td></td><td>结束日期格式yyyy-MM-dd如 2026-06-10</td></tr>
<tr><td>pageNo</td><td>int</td><td></td><td>页码,默认 1</td></tr>
<tr><td>pageSize</td><td>int</td><td></td><td>每页条数,默认 20</td></tr>
</table>
<p style="color:#718096; font-size:13px; margin-top:8px;">提示:可组合使用多个筛选条件,如 ?result=fail&startDate=2026-06-01&endDate=2026-06-10</p>
</div>
</div>
</div>
</div>
<!-- 用户管理(管理员专用) -->
<div class="section" id="users">
<div class="service-card">
<h2>用户管理</h2>
<span class="port">wangpan-user-service : 8081</span>
<span class="badge badge-danger">仅管理员 (userType=2)</span>
</div>
<!-- 查询用户列表 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/users</span>
<span class="api-desc">查询用户列表</span><span class="auth-badge">需Token + 管理员</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>权限说明</h4>
<table class="param-table">
<tr><th>角色</th><th>访问权限</th></tr>
<tr><td>普通/VIP 用户</td><td style="color:#e53e3e">403 权限不足</td></tr>
<tr><td><strong>管理员</strong></td><td style="color:#38a169">可查看所有用户列表</td></tr>
</table>
</div>
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>username</td><td>String</td><td></td><td>用户名模糊搜索</td></tr>
<tr><td>status</td><td>int</td><td></td><td>状态0=禁用, 1=正常, 2=锁定</td></tr>
<tr><td>userType</td><td>int</td><td></td><td>角色0=普通, 1=VIP, 2=管理员</td></tr>
<tr><td>pageNo</td><td>int</td><td></td><td>页码,默认 1</td></tr>
<tr><td>pageSize</td><td>int</td><td></td><td>每页条数,默认 20</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"data"</span>: {
<span class="key">"total"</span>: <span class="number">5</span>,
<span class="key">"list"</span>: [
{ <span class="key">"id"</span>: <span class="number">7</span>, <span class="key">"username"</span>: <span class="string">"lrq"</span>, <span class="key">"userType"</span>: <span class="number">0</span>, <span class="key">"status"</span>: <span class="number">1</span>, ... }
],
<span class="key">"pageNo"</span>: <span class="number">1</span>,
<span class="key">"pageSize"</span>: <span class="number">20</span>
}
}</div>
</div>
</div>
</div>
<!-- 新增用户 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/users</span>
<span class="api-desc">新增用户</span><span class="auth-badge">需Token + 管理员</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"username"</span>: <span class="string">"必填"</span>,
<span class="key">"password"</span>: <span class="string">"必填"</span>,
<span class="key">"email"</span>: <span class="string">"可选"</span>,
<span class="key">"phone"</span>: <span class="string">"可选"</span>,
<span class="key">"realName"</span>: <span class="string">"可选"</span>,
<span class="key">"status"</span>: <span class="number">1</span>, <span style="color:#718096">// 可选0=禁用, 1=正常, 默认1</span>
<span class="key">"userType"</span>: <span class="number">0</span> <span style="color:#718096">// 可选0=普通, 1=VIP, 2=管理员, 默认0</span>
}</div>
</div>
<div class="detail-section">
<h4>成功响应</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"用户创建成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">10</span>,
<span class="key">"username"</span>: <span class="string">"newuser"</span>,
<span class="key">"email"</span>: <span class="string">"new@test.com"</span>,
<span class="key">"status"</span>: <span class="number">1</span>,
<span class="key">"userType"</span>: <span class="number">0</span>
}
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>400</td><td>用户名或密码为空</td></tr>
<tr><td>409</td><td>用户名已被占用</td></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>403</td><td>非管理员,无权限操作</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
</div>
</div>
<!-- 修改用户 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/users/{userId}</span>
<span class="api-desc">修改用户信息</span><span class="auth-badge">需Token + 管理员</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>userId</td><td>Long</td><td>目标用户 ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求体(部分更新,只传需要修改的字段)</h4>
<div class="code-block">{
<span class="key">"email"</span>: <span class="string">"新邮箱"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"phone"</span>: <span class="string">"新手机号"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"realName"</span>: <span class="string">"新姓名"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"status"</span>: <span class="number">0</span>, <span style="color:#718096">// 可选0=禁用, 1=正常, 2=锁定</span>
<span class="key">"userType"</span>: <span class="number">1</span> <span style="color:#718096">// 可选0=普通, 1=VIP, 2=管理员</span>
}</div>
</div>
<div class="detail-section">
<h4>成功响应</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"用户信息更新成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">10</span>,
<span class="key">"username"</span>: <span class="string">"newuser"</span>,
<span class="key">"email"</span>: <span class="string">"new@test.com"</span>,
<span class="key">"status"</span>: <span class="number">1</span>,
<span class="key">"userType"</span>: <span class="number">0</span>
}
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>404</td><td>目标用户不存在</td></tr>
<tr><td>400</td><td>没有需要更新的字段</td></tr>
<tr><td>403</td><td>非管理员</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
</div>
</div>
<!-- 删除用户 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/users/{userId}</span>
<span class="api-desc">删除用户(软删除)</span><span class="auth-badge">需Token + 管理员</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>userId</td><td>Long</td><td>要删除的用户 ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>说明</h4>
<p>软删除操作,将 <code>is_deleted</code> 设为 1不物理删除数据。</p>
</div>
<div class="detail-section">
<h4>成功响应</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"用户删除成功"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>404</td><td>目标用户不存在</td></tr>
<tr><td>400</td><td>删除失败(可能已被删除)</td></tr>
<tr><td>403</td><td>非管理员</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
</div>
</div>
</div>
<!-- 个人资料 -->
<div class="section" id="profile">
<div class="service-card">
<h2>个人资料</h2>
<span class="port">wangpan-user-service : 8081</span>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/profile</span>
<span class="api-desc">获取当前用户个人资料</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>获取当前登录用户的基本信息(不含密码等敏感字段)。</p>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"username"</span>: <span class="string">"admin"</span>,
<span class="key">"email"</span>: <span class="string">"admin@example.com"</span>,
<span class="key">"phone"</span>: <span class="string">"13800138000"</span>,
<span class="key">"realName"</span>: <span class="string">"管理员"</span>,
<span class="key">"avatarUrl"</span>: <span class="string">"http://..."</span>,
<span class="key">"userType"</span>: <span class="number">2</span>,
<span class="key">"status"</span>: <span class="number">1</span>,
<span class="key">"createTime"</span>: <span class="string">"2024-01-01T00:00:00"</span>
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/profile/username</span>
<span class="api-desc">修改用户名</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>修改当前登录用户的用户名。</p>
<p><strong>校验规则:</strong></p>
<ul>
<li>用户名不能为空</li>
<li>新用户名不能与当前用户名相同</li>
<li>新用户名不能被其他用户占用</li>
</ul>
</div>
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"username"</span>: <span class="string">"新用户名"</span>
}</div>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"用户名修改成功"</span>,
<span class="key">"data"</span>: null
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/profile/avatar</span>
<span class="api-desc">修改头像</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>修改当前登录用户的头像。</p>
<p><strong>流程:</strong></p>
<ol>
<li>接收 Base64 编码的图片数据</li>
<li>解析 MIME 类型和 Base64 内容</li>
<li>上传到 MinIO路径avatar/{userId}_{timestamp}.{ext}</li>
<li>更新数据库中的 avatar_url 字段</li>
</ol>
<p><strong>支持的图片格式:</strong>PNG、JPG/JPEG、GIF、WebP</p>
</div>
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"avatarData"</span>: <span class="string">"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."</span>
}</div>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"头像更新成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"avatarUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/avatar/1_1719234567890.png?..."</span>
}
}</div>
</div>
</div>
</div>
</div>
<!-- 网盘服务 -->
<div class="section" id="drive">
<div class="service-card">
<h2>网盘管理服务</h2>
<span class="port">wangpan-drive-service : 8082</span>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/drive</span>
<span class="api-desc">创建网盘</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"网盘名称"</span>,
<span class="key">"description"</span>: <span class="string">"网盘描述"</span>,
<span class="key">"logoData"</span>: <span class="string">"data:image/png;base64,iVBOR..."</span>
}</div>
</div>
<div class="detail-section">
<h4>参数说明</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>name</td><td>String</td><td></td><td>网盘名称</td></tr>
<tr><td>description</td><td>String</td><td></td><td>网盘描述</td></tr>
<tr><td>logoData</td><td>String</td><td></td><td>Logo图片Base64编码支持 data:image/xxx;base64, 前缀格式上传后存储到MinIO数据库仅保存URL</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive</span>
<span class="api-desc">查询网盘列表</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
<tr><td>pageNo</td><td>1</td><td>页码</td></tr>
<tr><td>pageSize</td><td>20</td><td>每页条数</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>name</td><td>String</td><td>网盘名称</td></tr>
<tr><td>description</td><td>String</td><td>网盘描述</td></tr>
<tr><td>logoUrl</td><td>String</td><td>网盘Logo图片链接MinIO存储地址</td></tr>
<tr><td>folderCount</td><td>Integer</td><td>文件夹数量</td></tr>
<tr><td>fileCount</td><td>Integer</td><td>文件数量</td></tr>
<tr><td>totalSize</td><td>Long</td><td>总大小(字节)</td></tr>
<tr><td>status</td><td>String</td><td>状态</td></tr>
<tr><td>createTime</td><td>String</td><td>创建时间</td></tr>
<tr><td>updateTime</td><td>String</td><td>更新时间</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive/{id}</span>
<span class="api-desc">查询网盘详情</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>网盘ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>name</td><td>String</td><td>网盘名称</td></tr>
<tr><td>description</td><td>String</td><td>网盘描述</td></tr>
<tr><td>logoUrl</td><td>String</td><td>网盘Logo图片链接MinIO存储地址</td></tr>
<tr><td>folderCount</td><td>Integer</td><td>文件夹数量</td></tr>
<tr><td>fileCount</td><td>Integer</td><td>文件数量</td></tr>
<tr><td>totalSize</td><td>Long</td><td>总大小(字节)</td></tr>
<tr><td>status</td><td>String</td><td>状态</td></tr>
<tr><td>createTime</td><td>String</td><td>创建时间</td></tr>
<tr><td>updateTime</td><td>String</td><td>更新时间</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/drive/{id}</span>
<span class="api-desc">更新网盘</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"新网盘名称"</span>,
<span class="key">"description"</span>: <span class="string">"新描述"</span>,
<span class="key">"logoData"</span>: <span class="string">"data:image/png;base64,iVBOR..."</span>
}</div>
</div>
<div class="detail-section">
<h4>参数说明</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>name</td><td>String</td><td></td><td>网盘名称</td></tr>
<tr><td>description</td><td>String</td><td></td><td>网盘描述</td></tr>
<tr><td>logoData</td><td>String</td><td></td><td>新的Logo图片Base64编码传入后覆盖旧Logo不传则保留原Logo</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/drive/{id}</span>
<span class="api-desc">删除网盘</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail"></div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/drive/{driveId}/folders</span>
<span class="api-desc">创建文件夹</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"文件夹名称"</span>,
<span class="key">"parentId"</span>: <span class="number">0</span>
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive/{driveId}/folders</span>
<span class="api-desc">查询文件夹列表</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
<tr><td>parentId</td><td>0</td><td>父文件夹 ID</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive/{driveId}/folders/{folderId}</span>
<span class="api-desc">查询文件夹详情</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>folderId</td><td>Long</td><td>文件夹ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>文件夹ID</td></tr>
<tr><td>driveId</td><td>Long</td><td>所属网盘ID</td></tr>
<tr><td>parentId</td><td>Long</td><td>父文件夹ID0表示根目录</td></tr>
<tr><td>name</td><td>String</td><td>文件夹名称</td></tr>
<tr><td>description</td><td>String</td><td>文件夹描述</td></tr>
<tr><td>depth</td><td>Integer</td><td>层级深度</td></tr>
<tr><td>childFolderCount</td><td>Integer</td><td>子文件夹数量</td></tr>
<tr><td>fileCount</td><td>Integer</td><td>文件数量</td></tr>
<tr><td>totalSize</td><td>Long</td><td>总大小(字节)</td></tr>
<tr><td>createTime</td><td>String</td><td>创建时间</td></tr>
<tr><td>updateTime</td><td>String</td><td>更新时间</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/drive/{driveId}/folders/{folderId}</span>
<span class="api-desc">更新文件夹</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>folderId</td><td>Long</td><td>文件夹ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求体(部分更新)</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"新文件夹名称"</span>,
<span class="key">"description"</span>: <span class="string">"新描述"</span>,
<span class="key">"parentId"</span>: <span class="number">0</span>
}</div>
</div>
<div class="detail-section">
<h4>参数说明</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>name</td><td>String</td><td></td><td>文件夹名称</td></tr>
<tr><td>description</td><td>String</td><td></td><td>文件夹描述</td></tr>
<tr><td>parentId</td><td>Long</td><td></td><td>父文件夹ID可移动文件夹</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/drive/{driveId}/folders/{folderId}</span>
<span class="api-desc">删除文件夹</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>folderId</td><td>Long</td><td>文件夹ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>说明</h4>
<p>删除文件夹前会检查文件夹是否为空,如果包含子文件夹或电影文件则拒绝删除。</p>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>400</td><td>文件夹不为空或数据库操作失败</td></tr>
<tr><td>401</td><td>未登录或Token无效</td></tr>
<tr><td>403</td><td>无权访问该文件夹</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/drive/{driveId}/movies</span>
<span class="api-desc">创建电影(只传文件名)</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"title"</span>: <span class="string">"文件名.mp4"</span>, <span style="color:#718096">// 必填,文件名(同时作为电影标题)</span>
<span class="key">"folderId"</span>: <span class="number">0</span> <span style="color:#718096">// 可选默认0根目录</span>
}</div>
</div>
<div class="detail-section">
<h4>参数说明</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>title</td><td>String</td><td></td><td>文件名(同时作为电影标题,后续通过 TMDB 刮削补充信息)</td></tr>
<tr><td>folderId</td><td>Long</td><td></td><td>目标文件夹ID默认0根目录</td></tr>
</table>
</div>
<div class="detail-section">
<h4>说明</h4>
<p>只记录文件名,不上传文件数据。创建后通过 TMDB 刮削接口补充电影的海报、描述、评分等信息。自动检测 MIME 类型mp4/mkv/avi → video/mp4</p>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive/{driveId}/folders/{folderId}/movies</span>
<span class="api-desc">查询电影文件列表</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>folderId</td><td>Long</td><td>文件夹ID0表示根目录</td></tr>
</table>
</div>
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
<tr><td>pageNo</td><td>1</td><td>页码</td></tr>
<tr><td>pageSize</td><td>20</td><td>每页条数最大100</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>list[].id</td><td>Long</td><td>电影ID</td></tr>
<tr><td>list[].title</td><td>String</td><td>电影名称</td></tr>
<tr><td>list[].originalName</td><td>String</td><td>原始文件名</td></tr>
<tr><td>list[].mimeType</td><td>String</td><td>MIME类型</td></tr>
<tr><td>list[].fileSize</td><td>Long</td><td>文件大小(字节)</td></tr>
<tr><td>list[].extension</td><td>String</td><td>文件扩展名</td></tr>
<tr><td>list[].minioUrl</td><td>String</td><td>MinIO文件访问URL</td></tr>
<tr><td>list[].rating</td><td>Integer</td><td>评分</td></tr>
<tr><td>list[].tags</td><td>String</td><td>标签</td></tr>
<tr><td>total</td><td>Integer</td><td>总数</td></tr>
<tr><td>pageNo</td><td>Integer</td><td>当前页码</td></tr>
<tr><td>pageSize</td><td>Integer</td><td>每页条数</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive/{driveId}/movies/{movieId}</span>
<span class="api-desc">查询电影文件详情</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>movieId</td><td>Long</td><td>电影ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>电影ID</td></tr>
<tr><td>driveId</td><td>Long</td><td>所属网盘ID</td></tr>
<tr><td>folderId</td><td>Long</td><td>所属文件夹ID</td></tr>
<tr><td>title</td><td>String</td><td>电影名称</td></tr>
<tr><td>subtitle</td><td>String</td><td>副标题</td></tr>
<tr><td>originalName</td><td>String</td><td>原始文件名</td></tr>
<tr><td>mimeType</td><td>String</td><td>MIME类型</td></tr>
<tr><td>fileSize</td><td>Long</td><td>文件大小(字节)</td></tr>
<tr><td>extension</td><td>String</td><td>文件扩展名</td></tr>
<tr><td>description</td><td>String</td><td>描述</td></tr>
<tr><td>resourceLibrary</td><td>String</td><td>资源库</td></tr>
<tr><td>maintainer</td><td>String</td><td>维护者</td></tr>
<tr><td>status</td><td>String</td><td>状态</td></tr>
<tr><td>category</td><td>String</td><td>分类</td></tr>
<tr><td>scrapeType</td><td>String</td><td>刮削类型</td></tr>
<tr><td>rating</td><td>Integer</td><td>评分</td></tr>
<tr><td>tags</td><td>String</td><td>标签</td></tr>
<tr><td>minioUrl</td><td>String</td><td>MinIO文件访问URL</td></tr>
<tr><td>posterUrl</td><td>String</td><td>海报URL</td></tr>
<tr><td>createTime</td><td>String</td><td>创建时间</td></tr>
<tr><td>updateTime</td><td>String</td><td>更新时间</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/drive/{driveId}/movies/{movieId}</span>
<span class="api-desc">更新电影文件信息</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>movieId</td><td>Long</td><td>电影ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求体(部分更新,所有字段可选)</h4>
<div class="code-block">{
<span class="key">"title"</span>: <span class="string">"新电影名称"</span>,
<span class="key">"originalName"</span>: <span class="string">"新文件名.mp4"</span>,
<span class="key">"subtitle"</span>: <span class="string">"新副标题"</span>,
<span class="key">"description"</span>: <span class="string">"新描述"</span>,
<span class="key">"rating"</span>: <span class="number">90</span>,
<span class="key">"tags"</span>: <span class="string">"新标签"</span>,
<span class="key">"category"</span>: <span class="string">"新分类"</span>
}</div>
</div>
<div class="detail-section">
<h4>参数说明</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>title</td><td>String</td><td>电影名称</td></tr>
<tr><td>originalName</td><td>String</td><td>原始文件名(用于 TMDB 刮削有误时修改)</td></tr>
<tr><td>subtitle</td><td>String</td><td>副标题</td></tr>
<tr><td>description</td><td>String</td><td>描述</td></tr>
<tr><td>rating</td><td>Integer</td><td>评分 0-100</td></tr>
<tr><td>tags</td><td>String</td><td>标签</td></tr>
<tr><td>category</td><td>String</td><td>分类</td></tr>
</table>
</div>
<div class="detail-section">
<h4>说明</h4>
<p>用于 TMDB 刮削有误时,用户可自定义修改文件名等信息。所有字段均为可选,只更新传入的字段。</p>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/drive/{driveId}/movies/{movieId}</span>
<span class="api-desc">删除电影文件</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>driveId</td><td>Long</td><td>网盘ID</td></tr>
<tr><td>movieId</td><td>Long</td><td>电影ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>说明</h4>
<p>逻辑删除(软删除),删除后自动更新网盘和文件夹的统计信息。</p>
</div>
</div>
</div>
<!-- 重复电影检查 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/drive/movies/duplicates</span>
<span class="api-desc">检查重复电影</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>查询当前用户所有网盘下所有文件夹中的电影按标题title分组找出重复的电影。返回每组重复电影的详细信息包括所在文件夹的完整路径前端可让用户选择删除其中一个。</p>
</div>
<div class="detail-section">
<h4>成功响应</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"duplicateGroups"</span>: [
{
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"count"</span>: <span class="number">2</span>,
<span class="key">"movies"</span>: [
{
<span class="key">"id"</span>: <span class="number">10</span>,
<span class="key">"driveId"</span>: <span class="number">1</span>,
<span class="key">"folderId"</span>: <span class="number">5</span>,
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"originalName"</span>: <span class="string">"电影名称.mp4"</span>,
<span class="key">"fileSize"</span>: <span class="number">1073741824</span>,
<span class="key">"extension"</span>: <span class="string">"mp4"</span>,
<span class="key">"category"</span>: <span class="string">"sci-fi"</span>,
<span class="key">"rating"</span>: <span class="number">85</span>,
<span class="key">"createTime"</span>: <span class="string">"2025-01-01T10:00:00"</span>,
<span class="key">"folderPath"</span>: <span class="string">"我的电影库/科幻/2024"</span>
},
{
<span class="key">"id"</span>: <span class="number">20</span>,
<span class="key">"driveId"</span>: <span class="number">1</span>,
<span class="key">"folderId"</span>: <span class="number">8</span>,
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"originalName"</span>: <span class="string">"电影名称.mkv"</span>,
<span class="key">"fileSize"</span>: <span class="number">2147483648</span>,
<span class="key">"extension"</span>: <span class="string">"mkv"</span>,
<span class="key">"category"</span>: <span class="string">"sci-fi"</span>,
<span class="key">"rating"</span>: <span class="keyword">null</span>,
<span class="key">"createTime"</span>: <span class="string">"2025-02-01T15:00:00"</span>,
<span class="key">"folderPath"</span>: <span class="string">"我的电影库/动作"</span>
}
]
}
],
<span class="key">"totalGroups"</span>: <span class="number">1</span>,
<span class="key">"totalDuplicates"</span>: <span class="number">2</span>
}
}</div>
</div>
<div class="detail-section">
<h4>返回字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>duplicateGroups</td><td>Array</td><td>重复电影分组列表</td></tr>
<tr><td>duplicateGroups[].title</td><td>String</td><td>重复的电影标题</td></tr>
<tr><td>duplicateGroups[].count</td><td>Integer</td><td>该标题下的重复数量</td></tr>
<tr><td>duplicateGroups[].movies</td><td>Array</td><td>该标题下的所有电影记录</td></tr>
<tr><td>duplicateGroups[].movies[].id</td><td>Long</td><td>电影ID可用于删除接口</td></tr>
<tr><td>duplicateGroups[].movies[].folderPath</td><td>String</td><td>所在文件夹完整路径,如"网盘名/文件夹1/文件夹2"</td></tr>
<tr><td>totalGroups</td><td>Integer</td><td>重复组总数</td></tr>
<tr><td>totalDuplicates</td><td>Integer</td><td>涉及重复的电影总数</td></tr>
</table>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
</div>
</div>
</div>
<!-- Icon服务 -->
<div class="section" id="icon">
<div class="service-card">
<h2>Icon 管理服务</h2>
<span class="port">wangpan-drive-service : 8082</span>
</div>
<!-- 创建 Icon -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/icon</span>
<span class="api-desc">创建 Icon</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体JSON</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>name</td><td>String</td><td></td><td>Icon 名称最大100字符</td></tr>
<tr><td>url</td><td>String</td><td>条件必填</td><td>外网链接 URL最大500字符linkType为external或both时必填</td></tr>
<tr><td>internalUrl</td><td>String</td><td>条件必填</td><td>内网链接 URL最大500字符linkType为internal或both时必填</td></tr>
<tr><td>linkType</td><td>String</td><td></td><td>链接类型external仅外网默认/ internal仅内网/ both内外网</td></tr>
<tr><td>description</td><td>String</td><td></td><td>Icon 描述最大500字符</td></tr>
<tr><td>sortOrder</td><td>Integer</td><td></td><td>排序值数字越小越靠前默认0</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求示例(仅外网)</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"百度"</span>,
<span class="key">"url"</span>: <span class="string">"https://www.baidu.com"</span>,
<span class="key">"linkType"</span>: <span class="string">"external"</span>,
<span class="key">"description"</span>: <span class="string">"搜索引擎"</span>,
<span class="key">"sortOrder"</span>: <span class="number">1</span>
}</div>
</div>
<div class="detail-section">
<h4>请求示例(内外网都有)</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"内部系统"</span>,
<span class="key">"url"</span>: <span class="string">"https://system.example.com"</span>,
<span class="key">"internalUrl"</span>: <span class="string">"http://192.168.1.100:8080"</span>,
<span class="key">"linkType"</span>: <span class="string">"both"</span>,
<span class="key">"description"</span>: <span class="string">"内部管理系统"</span>,
<span class="key">"sortOrder"</span>: <span class="number">2</span>
}</div>
</div>
<div class="detail-section">
<h4>成功响应200</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"创建成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"百度"</span>,
<span class="key">"url"</span>: <span class="string">"https://www.baidu.com"</span>,
<span class="key">"internalUrl"</span>: <span class="keyword">null</span>,
<span class="key">"linkType"</span>: <span class="string">"external"</span>,
<span class="key">"description"</span>: <span class="string">"搜索引擎"</span>,
<span class="key">"sortOrder"</span>: <span class="number">1</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/icons/2/1718265600000.png"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-13T16:00:00"</span>
}
}</div>
</div>
</div>
</div>
<!-- 查询 Icon 列表 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/icon</span>
<span class="api-desc">查询当前用户的 Icon 列表</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>查询当前登录用户的所有 Icon按 sortOrder 升序、createTime 降序排列。每个用户只能看到自己创建的 Icon。</p>
</div>
<div class="detail-section">
<h4>成功响应200</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"list"</span>: [
{
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"百度"</span>,
<span class="key">"url"</span>: <span class="string">"https://www.baidu.com"</span>,
<span class="key">"internalUrl"</span>: <span class="keyword">null</span>,
<span class="key">"linkType"</span>: <span class="string">"external"</span>,
<span class="key">"description"</span>: <span class="string">"搜索引擎"</span>,
<span class="key">"sortOrder"</span>: <span class="number">1</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/icons/2/1718265600000.png"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-13T16:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-13T16:00:00"</span>
}
],
<span class="key">"total"</span>: <span class="number">1</span>
}
}</div>
</div>
</div>
</div>
<!-- 查询单个 Icon -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/icon/{id}</span>
<span class="api-desc">查询单个 Icon 详情</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>Icon ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>成功响应200</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"百度"</span>,
<span class="key">"url"</span>: <span class="string">"https://www.baidu.com"</span>,
<span class="key">"internalUrl"</span>: <span class="keyword">null</span>,
<span class="key">"linkType"</span>: <span class="string">"external"</span>,
<span class="key">"description"</span>: <span class="string">"搜索引擎"</span>,
<span class="key">"sortOrder"</span>: <span class="number">1</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/icons/2/1718265600000.png"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-13T16:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-13T16:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>500</td><td>Icon 不存在或无权访问</td></tr>
</table>
</div>
</div>
</div>
<!-- 更新 Icon -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/icon/{id}</span>
<span class="api-desc">更新 Icon</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>Icon ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求体JSON所有字段可选</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>name</td><td>String</td><td>Icon 名称</td></tr>
<tr><td>url</td><td>String</td><td>外网链接</td></tr>
<tr><td>internalUrl</td><td>String</td><td>内网链接</td></tr>
<tr><td>linkType</td><td>String</td><td>链接类型external / internal / both</td></tr>
<tr><td>description</td><td>String</td><td>Icon 描述</td></tr>
<tr><td>sortOrder</td><td>Integer</td><td>排序值</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求示例</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"新名称"</span>,
<span class="key">"url"</span>: <span class="string">"https://new-url.com"</span>,
<span class="key">"internalUrl"</span>: <span class="string">"http://192.168.1.100:8080"</span>,
<span class="key">"linkType"</span>: <span class="string">"both"</span>,
<span class="key">"sortOrder"</span>: <span class="number">2</span>
}</div>
</div>
<div class="detail-section">
<h4>成功响应200</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"更新成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"新名称"</span>,
<span class="key">"url"</span>: <span class="string">"https://new-url.com"</span>,
<span class="key">"internalUrl"</span>: <span class="string">"http://192.168.1.100:8080"</span>,
<span class="key">"linkType"</span>: <span class="string">"both"</span>,
<span class="key">"description"</span>: <span class="string">"搜索引擎"</span>,
<span class="key">"sortOrder"</span>: <span class="number">2</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/icons/2/1718265600000.png"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-13T16:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-13T17:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>500</td><td>Icon 不存在或无权修改</td></tr>
</table>
</div>
</div>
</div>
<!-- 删除 Icon -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/icon/{id}</span>
<span class="api-desc">删除 Icon</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>Icon ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>成功响应200</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"删除成功"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>500</td><td>Icon 不存在或无权删除</td></tr>
</table>
</div>
</div>
</div>
<!-- 上传自定义图标 -->
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/icon/{id}/logo</span>
<span class="api-desc">上传自定义图标图片</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>上传自定义图标图片替换自动抓取的 logo。上传后会自动删除旧的 MinIO 图片,防止硬盘空间浪费。系统每 24 小时自动清理无用图片。</p>
</div>
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>Icon ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求格式</h4>
<p><code>multipart/form-data</code></p>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>file</td><td>File</td><td></td><td>图片文件png/jpg/jpeg/gif/webp/ico最大 1MB</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求示例curl</h4>
<div class="code-block">curl -X POST http://localhost:8888/api/icon/1/logo \
-H "Authorization: Bearer &lt;token&gt;" \
-F "file=@/path/to/icon.png"</div>
</div>
<div class="detail-section">
<h4>成功响应200</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"图标上传成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"百度"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/icons/2/1718265600000.png"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-15T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>400</td><td>文件为空/格式不支持/超过 1MB</td></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>500</td><td>Icon 不存在或无权访问/上传失败</td></tr>
</table>
</div>
</div>
</div>
</div>
<!-- TMDB服务 -->
<div class="section" id="tmdb">
<div class="service-card">
<h2>TMDB 刮削服务</h2>
<span class="port">wangpan-tmdb-service : 8083</span>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/movies/recommend</span>
<span class="api-desc">今日推荐每天固定3部电影</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>从当前用户的电影库中随机抽取3部电影用于首页展示。</p>
<p><strong>推荐规则:</strong></p>
<ul>
<li>从 00:00 到 23:59 返回固定的3部电影</li>
<li>当天不管查询多少次都返回相同的结果</li>
<li>第二天 00:00 后重新随机挑选3部</li>
<li>推荐结果缓存在 wp_daily_recommend 表中</li>
</ul>
</div>
<div class="detail-section">
<h4>成功响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"list"</span>: [
{
<span class="key">"id"</span>: <span class="number">10</span>,
<span class="key">"title"</span>: <span class="string">"电影名称1"</span>,
<span class="key">"subtitle"</span>: <span class="string">"副标题"</span>,
<span class="key">"posterUrl"</span>: <span class="string">"海报URL"</span>,
<span class="key">"rating"</span>: <span class="number">85</span>,
<span class="key">"category"</span>: <span class="string">"动作"</span>,
<span class="key">"tags"</span>: <span class="string">"标签1,标签2"</span>,
<span class="key">"createTime"</span>: <span class="string">"2025-01-01T10:00:00"</span>
},
{
<span class="key">"id"</span>: <span class="number">20</span>,
<span class="key">"title"</span>: <span class="string">"电影名称2"</span>,
...
},
{
<span class="key">"id"</span>: <span class="number">30</span>,
<span class="key">"title"</span>: <span class="string">"电影名称3"</span>,
...
}
],
<span class="key">"count"</span>: <span class="number">3</span>
}
}</div>
</div>
<div class="detail-section">
<h4>返回字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>list</td><td>Array</td><td>推荐的电影列表最多3部</td></tr>
<tr><td>list[].id</td><td>Long</td><td>电影ID</td></tr>
<tr><td>list[].title</td><td>String</td><td>电影标题</td></tr>
<tr><td>list[].subtitle</td><td>String</td><td>副标题</td></tr>
<tr><td>list[].posterUrl</td><td>String</td><td>海报URL</td></tr>
<tr><td>list[].rating</td><td>Integer</td><td>评分0-100</td></tr>
<tr><td>list[].category</td><td>String</td><td>分类</td></tr>
<tr><td>list[].tags</td><td>String</td><td>标签</td></tr>
<tr><td>list[].createTime</td><td>String</td><td>创建时间</td></tr>
<tr><td>count</td><td>Integer</td><td>实际返回的电影数量可能小于3</td></tr>
</table>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/movies</span>
<span class="api-desc">电影列表查询按文件夹分组每组最多10条</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p><code>wp_drive_movie</code> 表查询当前用户所有电影,按文件夹分组返回,每个文件夹最多返回 10 条电影数据。支持分页pageNo 控制文件夹分页。</p>
</div>
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>keyword</td><td>String</td><td></td><td>搜索关键词</td></tr>
<tr><td>status</td><td>String</td><td></td><td>电影状态active正常/ deleted已删除</td></tr>
<tr><td>scrapeType</td><td>String</td><td></td><td>刮削类型tmdb / manual / none</td></tr>
<tr><td>sortBy</td><td>String</td><td></td><td>排序字段(如 createTime、rating、title</td></tr>
<tr><td>sortOrder</td><td>String</td><td></td><td>排序方向asc / desc</td></tr>
<tr><td>pageNo</td><td>int</td><td></td><td>页码,默认 1</td></tr>
<tr><td>pageSize</td><td>int</td><td></td><td>每页条数(文件夹数),默认 20</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>totalFolders</td><td>Integer</td><td>文件夹总数</td></tr>
<tr><td>pageNo</td><td>Integer</td><td>当前页码</td></tr>
<tr><td>pageSize</td><td>Integer</td><td>每页条数</td></tr>
<tr><td>folderGroups[].folderId</td><td>Long</td><td>文件夹ID</td></tr>
<tr><td>folderGroups[].movieCount</td><td>Integer</td><td>该文件夹下返回的电影数量</td></tr>
<tr><td>folderGroups[].movies[].id</td><td>Long</td><td>电影ID</td></tr>
<tr><td>folderGroups[].movies[].driveId</td><td>Long</td><td>所属网盘ID</td></tr>
<tr><td>folderGroups[].movies[].folderId</td><td>Long</td><td>所属文件夹ID</td></tr>
<tr><td>folderGroups[].movies[].title</td><td>String</td><td>电影名称</td></tr>
<tr><td>folderGroups[].movies[].subtitle</td><td>String</td><td>副标题</td></tr>
<tr><td>folderGroups[].movies[].originalName</td><td>String</td><td>原始文件名</td></tr>
<tr><td>folderGroups[].movies[].mimeType</td><td>String</td><td>MIME类型</td></tr>
<tr><td>folderGroups[].movies[].fileSize</td><td>Long</td><td>文件大小(字节)</td></tr>
<tr><td>folderGroups[].movies[].extension</td><td>String</td><td>文件扩展名</td></tr>
<tr><td>folderGroups[].movies[].description</td><td>String</td><td>描述</td></tr>
<tr><td>folderGroups[].movies[].scrapeStatus</td><td>String</td><td>刮削状态</td></tr>
<tr><td>folderGroups[].movies[].rating</td><td>Integer</td><td>评分0-100</td></tr>
<tr><td>folderGroups[].movies[].tags</td><td>String</td><td>标签</td></tr>
<tr><td>folderGroups[].movies[].posterUrl</td><td>String</td><td>竖版海报URL2:3 比例)</td></tr>
<tr><td>folderGroups[].movies[].backdropUrl</td><td>String</td><td>横版背景图URL16:9 比例)</td></tr>
<tr><td>folderGroups[].movies[].minioUrl</td><td>String</td><td>MinIO 文件访问URL</td></tr>
<tr><td>folderGroups[].movies[].createTime</td><td>String</td><td>创建时间</td></tr>
<tr><td>folderGroups[].movies[].updateTime</td><td>String</td><td>更新时间</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"totalFolders"</span>: <span class="number">5</span>,
<span class="key">"pageNo"</span>: <span class="number">1</span>,
<span class="key">"pageSize"</span>: <span class="number">20</span>,
<span class="key">"folderGroups"</span>: [
{
<span class="key">"folderId"</span>: <span class="number">3</span>,
<span class="key">"movieCount"</span>: <span class="number">2</span>,
<span class="key">"movies"</span>: [
{
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"driveId"</span>: <span class="number">1</span>,
<span class="key">"folderId"</span>: <span class="number">3</span>,
<span class="key">"title"</span>: <span class="string">"肖申克的救赎"</span>,
<span class="key">"posterUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/1/poster_xxx.jpg"</span>,
<span class="key">"backdropUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/1/backdrop_xxx.jpg"</span>,
<span class="key">"rating"</span>: <span class="number">93</span>,
...
}
]
}
]
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/movies/{id}</span>
<span class="api-desc">电影详情查询</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail"></div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/movies/{id}/scrape-status</span>
<span class="api-desc">查询单部电影刮削状态</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>查询单部电影的刮削状态返回三种状态刮削中scraping、刮削失败failed、刮削成功success</p>
</div>
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>电影ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td>movieId</td><td>Long</td><td>电影ID</td></tr>
<tr><td>title</td><td>String</td><td>电影名称</td></tr>
<tr><td>status</td><td>String</td><td>刮削状态scraping / failed / success / unknown</td></tr>
<tr><td>message</td><td>String</td><td>状态描述信息</td></tr>
<tr><td>error</td><td>String</td><td>失败原因(仅 status=failed 时返回)</td></tr>
<tr><td>posterUrl</td><td>String</td><td>竖版海报URL仅 status=success 时返回)</td></tr>
<tr><td>backdropUrl</td><td>String</td><td>横版背景图URL仅 status=success 时返回)</td></tr>
<tr><td>rating</td><td>Integer</td><td>评分(仅 status=success 时返回)</td></tr>
<tr><td>category</td><td>String</td><td>分类(仅 status=success 时返回)</td></tr>
<tr><td>tags</td><td>String</td><td>标签(仅 status=success 时返回)</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应示例 - 刮削中</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"movieId"</span>: <span class="number">1</span>,
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"status"</span>: <span class="string">"scraping"</span>,
<span class="key">"message"</span>: <span class="string">"正在刮削中,请稍候..."</span>
}
}</div>
</div>
<div class="detail-section">
<h4>响应示例 - 刮削失败</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"movieId"</span>: <span class="number">1</span>,
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"status"</span>: <span class="string">"failed"</span>,
<span class="key">"message"</span>: <span class="string">"刮削失败"</span>,
<span class="key">"error"</span>: <span class="string">"TMDB API 请求失败"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>响应示例 - 刮削成功</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"查询成功"</span>,
<span class="key">"data"</span>: {
<span class="key">"movieId"</span>: <span class="number">1</span>,
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"status"</span>: <span class="string">"success"</span>,
<span class="key">"message"</span>: <span class="string">"刮削成功"</span>,
<span class="key">"posterUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/1/poster_xxx.jpg"</span>,
<span class="key">"backdropUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/1/backdrop_xxx.jpg"</span>,
<span class="key">"rating"</span>: <span class="number">85</span>,
<span class="key">"category"</span>: <span class="string">"动作"</span>,
<span class="key">"tags"</span>: <span class="string">"动作,科幻"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>错误码</h4>
<table class="param-table">
<tr><th>code</th><th>说明</th></tr>
<tr><td>401</td><td>未登录或 Token 无效</td></tr>
<tr><td>404</td><td>电影不存在或无权访问</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/movies</span>
<span class="api-desc">创建电影</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体所有字段可选title 必填)</h4>
<div class="code-block">{
<span class="key">"title"</span>: <span class="string">"电影名称"</span>, <span style="color:#718096">// 必填</span>
<span class="key">"subtitle"</span>: <span class="string">"副标题"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"resourceLibrary"</span>: <span class="string">"资源库"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"maintainer"</span>: <span class="string">"维护者"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"status"</span>: <span class="string">"active"</span>, <span style="color:#718096">// 可选active / deleted</span>
<span class="key">"category"</span>: <span class="string">"动作"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"scrapeType"</span>: <span class="string">"tmdb"</span>, <span style="color:#718096">// 可选tmdb / manual / none</span>
<span class="key">"rating"</span>: <span class="number">85</span>, <span style="color:#718096">// 可选0-100</span>
<span class="key">"posterUrl"</span>: <span class="string">"海报URL"</span>, <span style="color:#718096">// 可选</span>
<span class="key">"tags"</span>: <span class="string">"标签1,标签2"</span> <span style="color:#718096">// 可选</span>
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method PUT">PUT</span>
<span class="api-path">/api/movies/{id}</span>
<span class="api-desc">更新电影</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>id</td><td>Long</td><td>电影ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>请求体(部分更新,所有字段可选)</h4>
<div class="code-block">{
<span class="key">"title"</span>: <span class="string">"新电影名称"</span>,
<span class="key">"subtitle"</span>: <span class="string">"新副标题"</span>,
<span class="key">"resourceLibrary"</span>: <span class="string">"新资源库"</span>,
<span class="key">"maintainer"</span>: <span class="string">"新维护者"</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"category"</span>: <span class="string">"新分类"</span>,
<span class="key">"scrapeType"</span>: <span class="string">"tmdb"</span>,
<span class="key">"rating"</span>: <span class="number">90</span>,
<span class="key">"posterUrl"</span>: <span class="string">"新海报URL"</span>,
<span class="key">"tags"</span>: <span class="string">"新标签"</span>
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/movies/{id}</span>
<span class="api-desc">删除电影</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail"></div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/movies/batch-delete</span>
<span class="api-desc">批量删除电影</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{ <span class="key">"ids"</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>] }</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/movies/stats</span>
<span class="api-desc">电影统计</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>获取当前用户的电影数量统计,按刮削类型和分类分组。</p>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"data"</span>: {
<span class="key">"totalCount"</span>: <span class="number">10</span>,
<span class="key">"scrapedCount"</span>: <span class="number">8</span>,
<span class="key">"pendingCount"</span>: <span class="number">2</span>
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/tmdb/key</span>
<span class="api-desc">获取 TMDB API Key</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail"></div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/tmdb/key</span>
<span class="api-desc">设置 TMDB API Key</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{ <span class="key">"tmdbApiKey"</span>: <span class="string">"你的API Key"</span> }</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/tmdb-scraper/scrape</span>
<span class="api-desc">单部电影刮削</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求方式</h4>
<p>支持两种方式传递 movieId</p>
<p>1. <strong>URL 查询参数</strong><code>?movieId=1</code></p>
<p>2. <strong>请求体 JSON</strong></p>
<div class="code-block">{ <span class="key">"movieId"</span>: <span class="number">1</span> }</div>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"刮削完成"</span>,
<span class="key">"data"</span>: {
<span class="key">"movieId"</span>: <span class="number">1</span>,
<span class="key">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"rating"</span>: <span class="number">85</span>,
<span class="key">"posterUrl"</span>: <span class="string">"海报URL"</span>
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/tmdb-scraper/batch-scrape</span>
<span class="api-desc">批量刮削</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求体movieIds 可选)</h4>
<div class="code-block">{ <span class="key">"movieIds"</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>] } <span style="color:#718096">// 可选:不传则自动查找需要刮削的电影</span></div>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"批量刮削完成"</span>,
<span class="key">"data"</span>: {
<span class="key">"successCount"</span>: <span class="number">2</span>,
<span class="key">"failCount"</span>: <span class="number">0</span>,
<span class="key">"total"</span>: <span class="number">2</span>
}
}</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/tmdb-scraper/caches</span>
<span class="api-desc">查询缓存列表</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
<tr><td>pageNo</td><td>1</td><td>页码</td></tr>
<tr><td>pageSize</td><td>20</td><td>每页条数</td></tr>
</table>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/tmdb-scraper/stats</span>
<span class="api-desc">刮削统计</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"data"</span>: {
<span class="key">"totalCaches"</span>: <span class="number">5</span>,
<span class="key">"activeCaches"</span>: <span class="number">3</span>,
<span class="key">"expiredCaches"</span>: <span class="number">2</span>,
<span class="key">"userId"</span>: <span class="number">2</span>
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/tmdb-scraper/trigger</span>
<span class="api-desc">手动触发定时任务</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>手动触发 TMDB 定时扫描任务,查找需要刮削的电影并执行刮削。无需请求体。</p>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"扫描任务已触发"</span>,
<span class="key">"data"</span>: {
<span class="key">"message"</span>: <span class="string">"扫描任务已触发"</span>,
<span class="key">"triggeredBy"</span>: <span class="number">2</span>,
<span class="key">"triggerType"</span>: <span class="string">"manual"</span>
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method GET">GET</span>
<span class="api-path">/api/tmdb-scraper/scraper-logs</span>
<span class="api-desc">查询刮削日志</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>默认值</th><th>说明</th></tr>
<tr><td>pageNo</td><td>1</td><td>页码</td></tr>
<tr><td>pageSize</td><td>20</td><td>每页条数</td></tr>
</table>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"data"</span>: {
<span class="key">"total"</span>: <span class="number">10</span>,
<span class="key">"list"</span>: [
{
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"scanTime"</span>: <span class="string">"2026-06-18T10:00:00"</span>,
<span class="key">"totalMovies"</span>: <span class="number">5</span>,
<span class="key">"successCount"</span>: <span class="number">4</span>,
<span class="key">"failCount"</span>: <span class="number">1</span>
}
],
<span class="key">"pageNo"</span>: <span class="number">1</span>,
<span class="key">"pageSize"</span>: <span class="number">20</span>
}
}</div>
</div>
</div>
</div>
<div class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method DELETE">DELETE</span>
<span class="api-path">/api/tmdb-scraper/cache/{movieId}</span>
<span class="api-desc">删除缓存</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>路径参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>说明</th></tr>
<tr><td>movieId</td><td>Long</td><td>电影ID</td></tr>
</table>
</div>
<div class="detail-section">
<h4>功能说明</h4>
<p>删除指定电影的 TMDB 刮削缓存,下次刮削时将重新从 TMDB 获取数据。</p>
</div>
<div class="detail-section">
<h4>响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"缓存删除成功"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
</div>
</div>
</div>
<!-- 状态码 -->
<div class="section" id="status">
<div class="service-card">
<h2>通用响应格式</h2>
<div class="code-block" style="margin-top: 12px;">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"操作结果描述"</span>,
<span class="key">"data"</span>: { <span class="string">...</span> }
}</div>
</div>
<div class="service-card">
<h2>状态码说明</h2>
<table class="status-table">
<tr><th>状态码</th><th>说明</th></tr>
<tr><td>200</td><td>请求成功</td></tr>
<tr><td>400</td><td>请求参数错误</td></tr>
<tr><td>401</td><td>未授权 / 令牌无效</td></tr>
<tr><td>403</td><td>权限不足</td></tr>
<tr><td>404</td><td>资源不存在</td></tr>
<tr><td>500</td><td>服务器内部错误</td></tr>
</table>
</div>
<div class="service-card">
<h2>认证说明</h2>
<p style="font-size: 14px; color: #4a5568; line-height: 1.8;">
除以下 <strong>3 个接口</strong> 外,所有接口都需要在请求头中携带访问令牌:<br><br>
<strong>无需 Token 的接口:</strong><br>
&nbsp;&nbsp;<code style="background:#c6f6d5;padding:2px 8px;border-radius:4px;">POST /api/login</code>&nbsp;&nbsp;用户登录<br>
&nbsp;&nbsp;<code style="background:#c6f6d5;padding:2px 8px;border-radius:4px;">POST /api/register</code>&nbsp;&nbsp;用户注册<br>
&nbsp;&nbsp;<code style="background:#c6f6d5;padding:2px 8px;border-radius:4px;">GET /api/token/validate?token=xxx</code>&nbsp;&nbsp;令牌验证token 通过 URL 参数传递)<br><br>
<strong>需要 Token 的接口(带 <span style="background:#ed8936;color:white;padding:1px 6px;border-radius:10px;font-size:10px;font-weight:600;">需Token</span> 标识):</strong><br>
&nbsp;&nbsp;在请求头中添加:<code style="background: #edf2f7; padding: 2px 8px; border-radius: 4px;">Authorization: Bearer &lt;token&gt;</code>
</p>
</div>
</div>
</div>
</div>
<script>
// 导航切换
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
this.classList.add('active');
document.getElementById(this.dataset.section).classList.add('active');
});
});
// API 详情展开/收起
function toggleDetail(header) {
const detail = header.nextElementSibling;
detail.classList.toggle('show');
}
// 搜索功能
const searchInput = document.getElementById('apiSearch');
const searchClear = document.getElementById('searchClear');
const searchResultCount = document.getElementById('searchResultCount');
let searchTimeout;
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => performSearch(this.value), 200);
// 显示/隐藏清除按钮
searchClear.classList.toggle('show', this.value.length > 0);
});
searchClear.addEventListener('click', function() {
searchInput.value = '';
performSearch('');
searchClear.classList.remove('show');
searchInput.focus();
});
// 支持快捷键 Ctrl/Cmd + K 聚焦搜索框
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
searchInput.focus();
searchInput.select();
}
// ESC 键清除搜索
if (e.key === 'Escape' && document.activeElement === searchInput) {
searchInput.value = '';
performSearch('');
searchClear.classList.remove('show');
}
});
function performSearch(keyword) {
const apiItems = document.querySelectorAll('.api-item');
const sections = document.querySelectorAll('.section');
// 清除之前的高亮
document.querySelectorAll('.highlight').forEach(el => {
const parent = el.parentNode;
parent.replaceChild(document.createTextNode(el.textContent), el);
parent.normalize();
});
if (!keyword.trim()) {
// 恢复所有项目
apiItems.forEach(item => item.classList.remove('search-hidden'));
searchResultCount.classList.remove('show');
// 恢复导航栏
document.querySelectorAll('.nav-item').forEach(item => {
item.style.display = '';
});
// 恢复 section 显示状态
sections.forEach(section => {
section.style.display = '';
});
return;
}
const lowerKeyword = keyword.toLowerCase();
let matchCount = 0;
const matchedSections = new Set();
// 全局搜索:遍历所有分类中的所有接口
apiItems.forEach(item => {
const path = item.querySelector('.api-path');
const desc = item.querySelector('.api-desc');
const method = item.querySelector('.method');
if (!path || !desc) return;
const pathText = path.textContent.toLowerCase();
const descText = desc.textContent.toLowerCase();
const methodText = method ? method.textContent.toLowerCase() : '';
const isMatch = pathText.includes(lowerKeyword) ||
descText.includes(lowerKeyword) ||
methodText.includes(lowerKeyword);
if (isMatch) {
item.classList.remove('search-hidden');
matchCount++;
// 记录匹配的 section
const section = item.closest('.section');
if (section) {
matchedSections.add(section.id);
}
// 高亮匹配内容
highlightText(path, keyword);
highlightText(desc, keyword);
// 自动展开匹配的详情
const detail = item.querySelector('.api-detail');
if (detail && !detail.classList.contains('show')) {
detail.classList.add('show');
}
} else {
item.classList.add('search-hidden');
}
});
// 全局搜索模式:显示所有有匹配结果的分类
sections.forEach(section => {
if (section.id === 'overview' || section.id === 'status') {
// 架构总览和状态码始终显示
section.style.display = '';
} else if (matchedSections.has(section.id)) {
// 有匹配结果的分类强制显示
section.style.display = 'block';
} else {
// 无匹配结果的分类隐藏
section.style.display = 'none';
}
});
// 更新导航栏显示(只显示有匹配结果的分类)
document.querySelectorAll('.nav-item').forEach(item => {
const sectionId = item.dataset.section;
if (sectionId === 'overview' || sectionId === 'status') {
item.style.display = '';
} else if (matchedSections.has(sectionId)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
// 显示搜索结果
searchResultCount.textContent = `找到 ${matchCount} 个匹配的接口(跨 ${matchedSections.size} 个分类)`;
searchResultCount.classList.add('show');
}
function highlightText(element, keyword) {
const text = element.textContent;
const lowerText = text.toLowerCase();
const lowerKeyword = keyword.toLowerCase();
const index = lowerText.indexOf(lowerKeyword);
if (index === -1) return;
const before = text.substring(0, index);
const match = text.substring(index, index + keyword.length);
const after = text.substring(index + keyword.length);
element.innerHTML = '';
if (before) element.appendChild(document.createTextNode(before));
const highlight = document.createElement('span');
highlight.className = 'highlight';
highlight.textContent = match;
element.appendChild(highlight);
if (after) element.appendChild(document.createTextNode(after));
}
</script>
</body>
</html>