java/wangpan-gateway/target/classes/META-INF/resources/api-doc.html
liRQ ac0d94bd00 feat(openlist): 集成OpenList多网盘聚合服务,新增相关接口与文档
新增OpenList集成模块,支持80+种网盘接入,添加创建关联网盘、文件同步、获取播放链接等接口,更新API文档完善相关说明
2026-06-27 15:22:52 +08:00

4075 lines
221 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="openlist">OpenList集成</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 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>
<span class="arch-arrow"></span>
<div class="arch-box" style="background: #38b2ac;">OpenList:13000</div>
<span class="arch-arrow"></span>
<div class="arch-box" style="background: #e53e3e;">各网盘</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/drive/**/sync-files</td><td>drive-service</td><td>OpenList 文件同步</td></tr>
<tr><td>/api/drive/**/play-url</td><td>drive-service</td><td>OpenList 播放链接</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>失败响应示例 — 用户名或密码错误</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>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 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>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 用户名已存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">409</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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 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 class="detail-section">
<h4>成功响应示例 — Token 有效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"message"</span>: <span class="string">"Token 有效"</span>,
<span class="key">"data"</span>: {
<span class="key">"valid"</span>: <span class="keyword">true</span>,
<span class="key">"userId"</span>: <span class="number">7</span>,
<span class="key">"username"</span>: <span class="string">"lrq"</span>,
<span class="key">"expireTime"</span>: <span class="string">"2026-06-17T11:20:21"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — Token 无效或已过期</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"Token 无效或已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/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 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">"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">"userId"</span>: <span class="number">7</span>,
<span class="key">"username"</span>: <span class="string">"lrq"</span>,
<span class="key">"loginIp"</span>: <span class="string">"127.0.0.1"</span>,
<span class="key">"loginTime"</span>: <span class="string">"2026-06-10T11:20:21"</span>,
<span class="key">"result"</span>: <span class="string">"success"</span>,
<span class="key">"userAgent"</span>: <span class="string">"Mozilla/5.0..."</span>
}
],
<span class="key">"pageNo"</span>: <span class="number">1</span>,
<span class="key">"pageSize"</span>: <span class="number">20</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 权限不足</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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="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">"message"</span>: <span class="string">"查询成功"</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 class="detail-section">
<h4>失败响应示例 — 权限不足</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">409</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</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 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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</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 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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</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="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 class="detail-section">
<h4>失败响应示例 — 未登录</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 用户不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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 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 class="detail-section">
<h4>失败响应示例 — 用户名已存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">409</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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 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 class="detail-section">
<h4>失败响应示例 — 图片格式不支持</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</span>,
<span class="key">"message"</span>: <span class="string">"不支持的图片格式,仅支持 PNG、JPG、GIF、WebP"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — MinIO 上传失败</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</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="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 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">"name"</span>: <span class="string">"我的网盘"</span>,
<span class="key">"description"</span>: <span class="string">"个人网盘"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/drive/1/logo.png"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 参数缺失</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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</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 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">"total"</span>: <span class="number">2</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">"description"</span>: <span class="string">"个人网盘"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/drive/1/logo.png"</span>,
<span class="key">"folderCount"</span>: <span class="number">5</span>,
<span class="key">"fileCount"</span>: <span class="number">20</span>,
<span class="key">"totalSize"</span>: <span class="number">10737418240</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
],
<span class="key">"pageNo"</span>: <span class="number">1</span>,
<span class="key">"pageSize"</span>: <span class="number">20</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/{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 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">"name"</span>: <span class="string">"我的网盘"</span>,
<span class="key">"description"</span>: <span class="string">"个人网盘"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/drive/1/logo.png"</span>,
<span class="key">"folderCount"</span>: <span class="number">5</span>,
<span class="key">"fileCount"</span>: <span class="number">20</span>,
<span class="key">"totalSize"</span>: <span class="number">10737418240</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 网盘不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/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 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">"name"</span>: <span class="string">"新网盘名称"</span>,
<span class="key">"description"</span>: <span class="string">"新描述"</span>,
<span class="key">"logoUrl"</span>: <span class="string">"http://118.178.238.159:39000/wangpan/drive/1/logo.png"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T11:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 网盘不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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 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 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">"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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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 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 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">"driveId"</span>: <span class="number">1</span>,
<span class="key">"parentId"</span>: <span class="number">0</span>,
<span class="key">"name"</span>: <span class="string">"我的电影"</span>,
<span class="key">"description"</span>: <span class="string">""</span>,
<span class="key">"depth"</span>: <span class="number">0</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 参数缺失</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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 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 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">"driveId"</span>: <span class="number">1</span>,
<span class="key">"parentId"</span>: <span class="number">0</span>,
<span class="key">"name"</span>: <span class="string">"我的电影"</span>,
<span class="key">"description"</span>: <span class="string">""</span>,
<span class="key">"depth"</span>: <span class="number">0</span>,
<span class="key">"childFolderCount"</span>: <span class="number">2</span>,
<span class="key">"fileCount"</span>: <span class="number">5</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
]
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 网盘不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/{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 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">"driveId"</span>: <span class="number">1</span>,
<span class="key">"parentId"</span>: <span class="number">0</span>,
<span class="key">"name"</span>: <span class="string">"我的电影"</span>,
<span class="key">"description"</span>: <span class="string">"收藏的电影"</span>,
<span class="key">"depth"</span>: <span class="number">0</span>,
<span class="key">"childFolderCount"</span>: <span class="number">2</span>,
<span class="key">"fileCount"</span>: <span class="number">5</span>,
<span class="key">"totalSize"</span>: <span class="number">5368709120</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 文件夹不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/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 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">"driveId"</span>: <span class="number">1</span>,
<span class="key">"parentId"</span>: <span class="number">0</span>,
<span class="key">"name"</span>: <span class="string">"新文件夹名称"</span>,
<span class="key">"description"</span>: <span class="string">"新描述"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T11:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 文件夹不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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 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 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">"driveId"</span>: <span class="number">1</span>,
<span class="key">"folderId"</span>: <span class="number">0</span>,
<span class="key">"title"</span>: <span class="string">"电影.mp4"</span>,
<span class="key">"originalName"</span>: <span class="string">"电影.mp4"</span>,
<span class="key">"mimeType"</span>: <span class="string">"video/mp4"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 参数缺失</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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 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 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">"total"</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">"list"</span>: [
{
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"title"</span>: <span class="string">"电影.mp4"</span>,
<span class="key">"originalName"</span>: <span class="string">"电影.mp4"</span>,
<span class="key">"mimeType"</span>: <span class="string">"video/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">"minioUrl"</span>: <span class="string">"http://..."</span>,
<span class="key">"rating"</span>: <span class="number">85</span>,
<span class="key">"tags"</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">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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}/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>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>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>overview</td><td>String</td><td>电影简介TMDB刮削</td></tr>
<tr><td>casts</td><td>String</td><td>主演逗号分隔TMDB刮削</td></tr>
<tr><td>directors</td><td>String</td><td>导演逗号分隔TMDB刮削</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 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">"driveId"</span>: <span class="number">1</span>,
<span class="key">"folderId"</span>: <span class="number">0</span>,
<span class="key">"title"</span>: <span class="string">"电影.mp4"</span>,
<span class="key">"originalName"</span>: <span class="string">"电影.mp4"</span>,
<span class="key">"mimeType"</span>: <span class="string">"video/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">"description"</span>: <span class="string">"电影描述"</span>,
<span class="key">"rating"</span>: <span class="number">85</span>,
<span class="key">"tags"</span>: <span class="string">"动作,科幻"</span>,
<span class="key">"overview"</span>: <span class="string">"电影简介..."</span>,
<span class="key">"casts"</span>: <span class="string">"主演1,主演2"</span>,
<span class="key">"directors"</span>: <span class="string">"导演1"</span>,
<span class="key">"posterUrl"</span>: <span class="string">"http://..."</span>,
<span class="key">"minioUrl"</span>: <span class="string">"http://..."</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 电影不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/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">"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>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 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">"title"</span>: <span class="string">"新电影名称"</span>,
<span class="key">"rating"</span>: <span class="number">90</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T11:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 电影不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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 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 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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">403</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 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>失败响应示例 — Icon 不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</span>,
<span class="key">"message"</span>: <span class="string">"Icon 不存在或无权访问"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 无效"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</span>,
<span class="key">"message"</span>: <span class="string">"不支持的文件格式,仅支持 png/jpg/jpeg/gif/webp/ico"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 文件超过 1MB</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</span>,
<span class="key">"message"</span>: <span class="string">"文件大小超过限制(最大 1MB"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — Icon 不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</span>,
<span class="key">"message"</span>: <span class="string">"Icon 不存在"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</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">"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[].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>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 服务器内部错误</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</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 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[].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[].overview</td><td>String</td><td>电影简介TMDB刮削</td></tr>
<tr><td>folderGroups[].movies[].casts</td><td>String</td><td>主演逗号分隔TMDB刮削</td></tr>
<tr><td>folderGroups[].movies[].directors</td><td>String</td><td>导演逗号分隔TMDB刮削</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 class="detail-section">
<h4>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 参数错误</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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 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 class="detail-section">
<h4>功能说明</h4>
<p>查询单部电影的完整详情信息,包括基本信息、海报、评分、简介、导演、主演等。</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>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>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>scrapeStatus</td><td>String</td><td>刮削状态pending/scraping/success/failed</td></tr>
<tr><td>rating</td><td>Integer</td><td>评分0-100</td></tr>
<tr><td>overview</td><td>String</td><td>电影简介TMDB刮削</td></tr>
<tr><td>casts</td><td>String</td><td>主演逗号分隔TMDB刮削</td></tr>
<tr><td>directors</td><td>String</td><td>导演逗号分隔TMDB刮削</td></tr>
<tr><td>posterUrl</td><td>String</td><td>竖版海报URL2:3 比例)</td></tr>
<tr><td>backdropUrl</td><td>String</td><td>横版背景图URL16:9 比例)</td></tr>
<tr><td>tags</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 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">"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">"originalName"</span>: <span class="string">"肖申克的救赎.mp4"</span>,
<span class="key">"mimeType"</span>: <span class="string">"video/mp4"</span>,
<span class="key">"fileSize"</span>: <span class="number">2147483648</span>,
<span class="key">"extension"</span>: <span class="string">"mp4"</span>,
<span class="key">"description"</span>: <span class="string">"电影描述"</span>,
<span class="key">"scrapeStatus"</span>: <span class="string">"success"</span>,
<span class="key">"rating"</span>: <span class="number">93</span>,
<span class="key">"overview"</span>: <span class="string">"电影简介..."</span>,
<span class="key">"casts"</span>: <span class="string">"Tim Robbins, Morgan Freeman"</span>,
<span class="key">"directors"</span>: <span class="string">"Frank Darabont"</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">"tags"</span>: <span class="string">"剧情,犯罪"</span>,
<span class="key">"createTime"</span>: <span class="string">"2025-01-01T10:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2025-01-15T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 电影不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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}/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>
<tr><td>overview</td><td>String</td><td>电影简介(仅 status=success 时返回)</td></tr>
<tr><td>casts</td><td>String</td><td>主演(仅 status=success 时返回)</td></tr>
<tr><td>directors</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>,
<span class="key">"overview"</span>: <span class="string">"电影简介..."</span>,
<span class="key">"casts"</span>: <span class="string">"主演1,主演2"</span>,
<span class="key">"directors"</span>: <span class="string">"导演1"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 电影不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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 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>功能说明</h4>
<p>创建新的电影记录title 为必填字段。</p>
</div>
<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">"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 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">"title"</span>: <span class="string">"电影名称"</span>,
<span class="key">"createTime"</span>: <span class="string">"2025-01-01T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 缺少必填字段</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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>
<p>更新指定电影的信息,支持部分更新。</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>
<div class="code-block">{
<span class="key">"title"</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 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">"title"</span>: <span class="string">"新电影名称"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2025-01-15T10:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 电影不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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 class="detail-section">
<h4>功能说明</h4>
<p>删除指定的电影记录。</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>
<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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/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>
<p>批量删除多个电影记录。</p>
</div>
<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 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">"deletedCount"</span>: <span class="number">3</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 参数错误</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</span>,
<span class="key">"message"</span>: <span class="string">"电影ID列表不能为空"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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 class="detail-section">
<h4>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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 class="detail-section">
<h4>功能说明</h4>
<p>获取当前用户配置的 TMDB API Key。</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">"tmdbApiKey"</span>: <span class="string">"your_api_key_here"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/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>手动触发单部电影刮削自动匹配TMDB最佳结果。刮削完成后会更新电影的简介、导演、主演、海报等信息。<strong>注意两次请求需间隔5秒防止频繁点击。</strong></p>
</div>
<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>
<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>userId</td><td>Long</td><td>用户ID</td></tr>
<tr><td>tmdbId</td><td>Long</td><td>TMDB电影ID</td></tr>
<tr><td>title</td><td>String</td><td>电影标题来自TMDB</td></tr>
<tr><td>originalTitle</td><td>String</td><td>原始标题(外文)</td></tr>
<tr><td>overview</td><td>String</td><td>电影简介</td></tr>
<tr><td>releaseDate</td><td>String</td><td>上映日期</td></tr>
<tr><td>posterPath</td><td>String</td><td>TMDB海报路径</td></tr>
<tr><td>backdropPath</td><td>String</td><td>TMDB背景图路径</td></tr>
<tr><td>localPosterUrl</td><td>String</td><td>本地竖版海报URLMinIO</td></tr>
<tr><td>localBackdropUrl</td><td>String</td><td>本地横版背景图URLMinIO</td></tr>
<tr><td>tmdbRating</td><td>Integer</td><td>TMDB评分0-100分制</td></tr>
<tr><td>voteCount</td><td>Integer</td><td>投票数</td></tr>
<tr><td>runtime</td><td>Integer</td><td>片长(分钟)</td></tr>
<tr><td>genres</td><td>String</td><td>类型(逗号分隔)</td></tr>
<tr><td>directors</td><td>String</td><td>导演逗号分隔最多2位</td></tr>
<tr><td>casts</td><td>String</td><td>主演逗号分隔最多5位</td></tr>
<tr><td>originalLanguage</td><td>String</td><td>原始语言</td></tr>
<tr><td>productionCountries</td><td>String</td><td>制片国家(逗号分隔)</td></tr>
<tr><td>cacheTime</td><td>String</td><td>缓存时间</td></tr>
<tr><td>status</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">"movieId"</span>: <span class="number">1</span>,
<span class="key">"userId"</span>: <span class="number">1</span>,
<span class="key">"tmdbId"</span>: <span class="number">597</span>,
<span class="key">"title"</span>: <span class="string">"泰坦尼克号"</span>,
<span class="key">"originalTitle"</span>: <span class="string">"Titanic"</span>,
<span class="key">"overview"</span>: <span class="string">"1912年4月10日泰坦尼克号从 Southampton 出发..."</span>,
<span class="key">"releaseDate"</span>: <span class="string">"1997-11-18"</span>,
<span class="key">"posterPath"</span>: <span class="string">"/9xjZS2rlVxm8SFx8kPC3JIGCOY.jpg"</span>,
<span class="key">"backdropPath"</span>: <span class="string">"/rTh4K5uw9HypmpGslcKd4QfHl93.jpg"</span>,
<span class="key">"localPosterUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/poster_1_597.jpg"</span>,
<span class="key">"localBackdropUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/backdrop_1_597.jpg"</span>,
<span class="key">"tmdbRating"</span>: <span class="number">78</span>,
<span class="key">"voteCount"</span>: <span class="number">24563</span>,
<span class="key">"runtime"</span>: <span class="number">194</span>,
<span class="key">"genres"</span>: <span class="string">"剧情, 爱情, 灾难"</span>,
<span class="key">"directors"</span>: <span class="string">"James Cameron"</span>,
<span class="key">"casts"</span>: <span class="string">"Leonardo DiCaprio, Kate Winslet, Billy Zane, Kathy Bates, Frances Fisher"</span>,
<span class="key">"originalLanguage"</span>: <span class="string">"en"</span>,
<span class="key">"productionCountries"</span>: <span class="string">"United States of America"</span>,
<span class="key">"cacheTime"</span>: <span class="string">"2026-06-26T16:30:00"</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 操作过于频繁</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</span>,
<span class="key">"message"</span>: <span class="string">"操作过于频繁,请等待 3 秒后再试"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未找到匹配结果</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</span>,
<span class="key">"message"</span>: <span class="string">"TMDB未找到匹配的电影: xxx"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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/search</span>
<span class="api-desc">搜索TMDB电影返回所有匹配结果供前端选择</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>根据电影ID自动获取标题搜索TMDB返回所有匹配结果。如果查询出多条结果前端应展示列表让用户选择。</p>
</div>
<div class="detail-section">
<h4>查询参数</h4>
<table class="param-table">
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>movieId</td><td>Long</td><td></td><td>电影ID自动获取标题搜索</td></tr>
<tr><td>keyword</td><td>String</td><td></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>movieId</td><td>Long</td><td>电影ID</td></tr>
<tr><td>keyword</td><td>String</td><td>搜索关键词</td></tr>
<tr><td>total</td><td>Integer</td><td>搜索结果总数</td></tr>
<tr><td>list[].tmdbId</td><td>Long</td><td>TMDB电影ID用于确认刮削</td></tr>
<tr><td>list[].title</td><td>String</td><td>电影标题</td></tr>
<tr><td>list[].originalTitle</td><td>String</td><td>原始标题</td></tr>
<tr><td>list[].releaseDate</td><td>String</td><td>上映日期</td></tr>
<tr><td>list[].overview</td><td>String</td><td>电影简介</td></tr>
<tr><td>list[].posterPath</td><td>String</td><td>海报图片URL</td></tr>
<tr><td>list[].voteAverage</td><td>Double</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">"movieId"</span>: <span class="number">23</span>,
<span class="key">"keyword"</span>: <span class="string">"泰坦尼克号"</span>,
<span class="key">"total"</span>: <span class="number">3</span>,
<span class="key">"list"</span>: [
{
<span class="key">"tmdbId"</span>: <span class="number">597</span>,
<span class="key">"title"</span>: <span class="string">"泰坦尼克号"</span>,
<span class="key">"originalTitle"</span>: <span class="string">"Titanic"</span>,
<span class="key">"releaseDate"</span>: <span class="string">"1997-11-18"</span>,
<span class="key">"overview"</span>: <span class="string">"电影简介..."</span>,
<span class="key">"posterPath"</span>: <span class="string">"https://image.tmdb.org/t/p/w500/xxx.jpg"</span>,
<span class="key">"voteAverage"</span>: <span class="number">7.9</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/confirm</span>
<span class="api-desc">确认刮削用户选择TMDB结果后执行</span><span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>功能说明</h4>
<p>用户从搜索结果中选择一部电影后,调用此接口执行刮削。刮削完成后会更新电影的简介、导演、主演、海报等信息。<strong>注意两次请求需间隔5秒防止频繁点击。</strong></p>
</div>
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"movieId"</span>: <span class="number">23</span>,
<span class="key">"tmdbId"</span>: <span class="number">597</span>
}</div>
</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>userId</td><td>Long</td><td>用户ID</td></tr>
<tr><td>tmdbId</td><td>Long</td><td>TMDB电影ID</td></tr>
<tr><td>title</td><td>String</td><td>电影标题来自TMDB</td></tr>
<tr><td>originalTitle</td><td>String</td><td>原始标题(外文)</td></tr>
<tr><td>overview</td><td>String</td><td>电影简介</td></tr>
<tr><td>releaseDate</td><td>String</td><td>上映日期</td></tr>
<tr><td>posterPath</td><td>String</td><td>TMDB海报路径</td></tr>
<tr><td>backdropPath</td><td>String</td><td>TMDB背景图路径</td></tr>
<tr><td>localPosterUrl</td><td>String</td><td>本地竖版海报URLMinIO</td></tr>
<tr><td>localBackdropUrl</td><td>String</td><td>本地横版背景图URLMinIO</td></tr>
<tr><td>tmdbRating</td><td>Integer</td><td>TMDB评分0-100分制</td></tr>
<tr><td>voteCount</td><td>Integer</td><td>投票数</td></tr>
<tr><td>runtime</td><td>Integer</td><td>片长(分钟)</td></tr>
<tr><td>genres</td><td>String</td><td>类型(逗号分隔)</td></tr>
<tr><td>directors</td><td>String</td><td>导演逗号分隔最多2位</td></tr>
<tr><td>casts</td><td>String</td><td>主演逗号分隔最多5位</td></tr>
<tr><td>originalLanguage</td><td>String</td><td>原始语言</td></tr>
<tr><td>productionCountries</td><td>String</td><td>制片国家(逗号分隔)</td></tr>
<tr><td>cacheTime</td><td>String</td><td>缓存时间</td></tr>
<tr><td>status</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">"movieId"</span>: <span class="number">23</span>,
<span class="key">"userId"</span>: <span class="number">1</span>,
<span class="key">"tmdbId"</span>: <span class="number">597</span>,
<span class="key">"title"</span>: <span class="string">"泰坦尼克号"</span>,
<span class="key">"originalTitle"</span>: <span class="string">"Titanic"</span>,
<span class="key">"overview"</span>: <span class="string">"1912年4月10日泰坦尼克号从 Southampton 出发..."</span>,
<span class="key">"releaseDate"</span>: <span class="string">"1997-11-18"</span>,
<span class="key">"posterPath"</span>: <span class="string">"/9xjZS2rlVxm8SFx8kPC3JIGCOY.jpg"</span>,
<span class="key">"backdropPath"</span>: <span class="string">"/rTh4K5uw9HypmpGslcKd4QfHl93.jpg"</span>,
<span class="key">"localPosterUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/poster_23_597.jpg"</span>,
<span class="key">"localBackdropUrl"</span>: <span class="string">"http://...:39000/wangpan/posters/backdrop_23_597.jpg"</span>,
<span class="key">"tmdbRating"</span>: <span class="number">78</span>,
<span class="key">"voteCount"</span>: <span class="number">24563</span>,
<span class="key">"runtime"</span>: <span class="number">194</span>,
<span class="key">"genres"</span>: <span class="string">"剧情, 爱情, 灾难"</span>,
<span class="key">"directors"</span>: <span class="string">"James Cameron"</span>,
<span class="key">"casts"</span>: <span class="string">"Leonardo DiCaprio, Kate Winslet, Billy Zane, Kathy Bates, Frances Fisher"</span>,
<span class="key">"originalLanguage"</span>: <span class="string">"en"</span>,
<span class="key">"productionCountries"</span>: <span class="string">"United States of America"</span>,
<span class="key">"cacheTime"</span>: <span class="string">"2026-06-26T16:30:00"</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 操作过于频繁</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">500</span>,
<span class="key">"message"</span>: <span class="string">"操作过于频繁,请等待 3 秒后再试"</span>,
<span class="key">"data"</span>: <span class="keyword">null</span>
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例 — 电影不存在</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">404</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>失败响应示例 — 未登录或 Token 无效</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">401</span>,
<span class="key">"message"</span>: <span class="string">"未登录或 Token 已过期"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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>
<!-- OpenList 集成 -->
<div class="section" id="openlist">
<div class="service-card">
<h2>OpenList 多网盘聚合服务</h2>
<span class="port">wangpan-drive-service : 8082 · 依赖 OpenList 服务 (http://ldfun.asia:13000)</span>
</div>
<div class="service-card" style="border-left-color: #48bb78;">
<h2>功能说明</h2>
<p style="font-size: 14px; color: #4a5568; line-height: 1.8; margin-top: 8px;">
OpenList 集成模块将 OpenList 作为多网盘聚合后端,支持 80+ 种网盘服务阿里云盘、百度网盘、UC网盘、OneDrive 等)。
Java 服务负责业务逻辑用户、权限、TMDB 刮削OpenList 负责文件操作(上传、下载、预览)。
</p>
<div class="arch-diagram" style="margin-top: 16px;">
<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 drive">Drive:8082</div>
<span class="arch-arrow"></span>
<div class="arch-box" style="background: #38b2ac;">OpenList:13000</div>
<span class="arch-arrow"></span>
<div class="arch-box" style="background: #e53e3e;">各网盘</div>
</div>
</div>
</div>
<div class="service-card" style="border-left-color: #ed8936;">
<h2>前置配置</h2>
<p style="font-size: 14px; color: #4a5568; line-height: 1.8; margin-top: 8px;">
1. 在 OpenList 管理后台 (<code>http://ldfun.asia:13000/@manage</code>) 添加网盘存储<br>
2. 记录存储的 <strong>挂载路径</strong>(如 <code>/uc</code>)和 <strong>存储 ID</strong>(通过 API 获取)<br>
3. 在 <code>application.yml</code> 中配置 OpenList 服务地址和账号密码<br>
4. 创建网盘时传入 <code>openlistStorageId</code><code>openlistMountPath</code> 关联 OpenList 存储
</p>
</div>
<!-- 创建网盘(关联 OpenList -->
<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">创建网盘(关联 OpenList 存储)</span>
<span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求头</h4>
<div class="code-block">Authorization: Bearer &lt;token&gt;
Content-Type: application/json</div>
</div>
<div class="detail-section">
<h4>请求体</h4>
<div class="code-block">{
<span class="key">"name"</span>: <span class="string">"UC网盘电影库"</span>,
<span class="key">"description"</span>: <span class="string">"UC网盘存储"</span>,
<span class="key">"openlistStorageId"</span>: <span class="string">"1"</span>,
<span class="key">"openlistMountPath"</span>: <span class="string">"/uc"</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>openlistStorageId</td><td>String</td><td></td><td>OpenList 存储 ID不传则仅记录元数据</td></tr>
<tr><td>openlistMountPath</td><td>String</td><td></td><td>OpenList 挂载路径(如 "/uc"</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">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"UC网盘电影库"</span>,
<span class="key">"description"</span>: <span class="string">"UC网盘存储"</span>,
<span class="key">"openlistStorageId"</span>: <span class="string">"1"</span>,
<span class="key">"openlistMountPath"</span>: <span class="string">"/uc"</span>,
<span class="key">"folderCount"</span>: <span class="number">0</span>,
<span class="key">"fileCount"</span>: <span class="number">0</span>,
<span class="key">"totalSize"</span>: <span class="number">0</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>,
<span class="key">"createTime"</span>: <span class="string">"2026-06-26T15:00:00"</span>,
<span class="key">"updateTime"</span>: <span class="string">"2026-06-26T15:00:00"</span>
}
}</div>
</div>
<div class="detail-section">
<h4>失败响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</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 class="api-item">
<div class="api-header" onclick="toggleDetail(this)">
<span class="method POST">POST</span>
<span class="api-path">/api/drive/{driveId}/folders/{folderId}/sync-files</span>
<span class="api-desc">从 OpenList 同步文件夹文件</span>
<span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求头</h4>
<div class="code-block">Authorization: Bearer &lt;token&gt;</div>
</div>
<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>
<p style="font-size: 13px; color: #4a5568; line-height: 1.6;">
调用 OpenList API 获取指定路径下的文件列表,自动创建数据库中缺失的电影记录。
已存在的文件不会重复创建。同步后文件可进行 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">"syncCount"</span>: <span class="number">5</span>,
<span class="key">"totalFiles"</span>: <span class="number">10</span>
}
}</div>
</div>
<div class="detail-section">
<h4>响应字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>说明</th></tr>
<tr><td>syncCount</td><td>本次新增的电影记录数量</td></tr>
<tr><td>totalFiles</td><td>OpenList 中该路径下的文件总数</td></tr>
</table>
</div>
<div class="detail-section">
<h4>失败响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</span>,
<span class="key">"message"</span>: <span class="string">"同步失败:网盘未关联 OpenList 存储"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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}/movies/{movieId}/play-url</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">Authorization: Bearer &lt;token&gt;</div>
</div>
<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 style="font-size: 13px; color: #4a5568; line-height: 1.6;">
调用 OpenList API 获取文件的真实下载地址,返回的 URL 可直接用于视频播放器播放。
仅支持 <code>storageType</code><code>openlist</code> 的电影记录。
</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">"playUrl"</span>: <span class="string">"https://uc网盘真实下载地址/xxx.mp4"</span>,
<span class="key">"fileName"</span>: <span class="string">"阿凡达.mp4"</span>,
<span class="key">"fileSize"</span>: <span class="number">1073741824</span>
}
}</div>
</div>
<div class="detail-section">
<h4>响应字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>说明</th></tr>
<tr><td>playUrl</td><td>真实播放/下载地址,可直接用于视频播放器</td></tr>
<tr><td>fileName</td><td>文件名</td></tr>
<tr><td>fileSize</td><td>文件大小(字节)</td></tr>
</table>
</div>
<div class="detail-section">
<h4>失败响应示例</h4>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">400</span>,
<span class="key">"message"</span>: <span class="string">"获取失败:该电影不是 OpenList 管理的文件"</span>,
<span class="key">"data"</span>: <span class="keyword">null</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</span>
<span class="api-desc">查看网盘列表(含 OpenList 信息)</span>
<span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求头</h4>
<div class="code-block">Authorization: Bearer &lt;token&gt;</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">"data"</span>: [
{
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"name"</span>: <span class="string">"UC网盘电影库"</span>,
<span class="key">"openlistStorageId"</span>: <span class="string">"1"</span>,
<span class="key">"openlistMountPath"</span>: <span class="string">"/uc"</span>,
<span class="key">"folderCount"</span>: <span class="number">2</span>,
<span class="key">"fileCount"</span>: <span class="number">10</span>,
<span class="key">"totalSize"</span>: <span class="number">10737418240</span>,
<span class="key">"status"</span>: <span class="string">"active"</span>
}
]
}</div>
</div>
<div class="detail-section">
<h4>新增字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>说明</th></tr>
<tr><td>openlistStorageId</td><td>关联的 OpenList 存储 IDnull 表示未关联)</td></tr>
<tr><td>openlistMountPath</td><td>OpenList 挂载路径(如 "/uc"</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}/movies</span>
<span class="api-desc">查看电影列表(含 OpenList 信息)</span>
<span class="auth-badge">需Token</span>
</div>
<div class="api-detail">
<div class="detail-section">
<h4>请求头</h4>
<div class="code-block">Authorization: Bearer &lt;token&gt;</div>
</div>
<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>
<div class="code-block">{
<span class="key">"code"</span>: <span class="number">200</span>,
<span class="key">"data"</span>: {
<span class="key">"list"</span>: [
{
<span class="key">"id"</span>: <span class="number">1</span>,
<span class="key">"title"</span>: <span class="string">"阿凡达.mp4"</span>,
<span class="key">"openlistFilePath"</span>: <span class="string">"/uc/阿凡达.mp4"</span>,
<span class="key">"storageType"</span>: <span class="string">"openlist"</span>,
<span class="key">"fileSize"</span>: <span class="number">1073741824</span>,
<span class="key">"mimeType"</span>: <span class="string">"video/mp4"</span>
}
],
<span class="key">"total"</span>: <span class="number">10</span>,
<span class="key">"page"</span>: <span class="number">1</span>,
<span class="key">"pageSize"</span>: <span class="number">20</span>
}
}</div>
</div>
<div class="detail-section">
<h4>新增字段说明</h4>
<table class="param-table">
<tr><th>字段</th><th>说明</th></tr>
<tr><td>openlistFilePath</td><td>OpenList 文件路径(如 "/uc/阿凡达.mp4"</td></tr>
<tr><td>storageType</td><td>存储类型:<code>metadata</code>(仅元数据)/ <code>openlist</code>OpenList 管理)</td></tr>
<tr><td>fileSize</td><td>文件大小(字节)</td></tr>
</table>
</div>
</div>
</div>
<!-- 前端调用示例 -->
<div class="service-card" style="border-left-color: #9f7aea;">
<h2>前端调用示例</h2>
<div class="detail-section">
<h4>创建网盘</h4>
<div class="code-block">const response = await fetch('/api/drive', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'UC网盘电影库',
openlistStorageId: '1',
openlistMountPath: '/uc'
})
});</div>
</div>
<div class="detail-section">
<h4>同步文件</h4>
<div class="code-block">const response = await fetch(`/api/drive/${driveId}/folders/${folderId}/sync-files`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
console.log(`同步了 ${data.data.syncCount} 个文件`);</div>
</div>
<div class="detail-section">
<h4>播放视频</h4>
<div class="code-block">const response = await fetch(`/api/drive/${driveId}/movies/${movieId}/play-url`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
videoPlayer.src = data.data.playUrl; // 直接设置视频源
videoPlayer.play();</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>