简介:在IT领域,实现用户能够一次上传多张图片的功能对提高工作效率至关重要。文章详细解释了实现多图上传功能的前端处理、文件分块、异步上传、后端存储、安全性和错误处理等关键技术环节。同时,还强调了优化上传性能、提升用户体验和API设计的重要性,为开发高效且用户友好的上传功能提供全面指导。
1. 图片上传功能的前端设计与实现
在现代Web应用中,图片上传功能是一个基础且至关重要的组成部分。本章将探讨如何设计并实现一个高效、用户友好的图片上传组件。我们将从基本的HTML表单上传讲起,逐渐过渡到使用JavaScript和现代前端框架来提升用户体验和功能丰富性。
1.1 图片上传的基本原理
图片上传功能的核心是将用户选定的文件发送到服务器。最简单的方式是使用HTML中的 <input type="file"> 元素,并将其 formenctype 属性设置为 multipart/form-data 。用户选择文件后,表单被提交,文件则以二进制流的形式发送到服务器。
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*">
<input type="submit" value="Upload">
</form>
1.2 使用JavaScript增强交互性
尽管基础的HTML表单可以处理图片上传,但为了提供更好的用户体验,我们通常会使用JavaScript来增强前端的交互性。例如,我们可以使用 FormData 对象配合 XMLHttpRequest 或 fetch API来实现文件上传。
const formData = new FormData();
formData.append('image', fileInput.files[0]);
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => {
console.log(data);
}).catch(error => {
console.error(error);
});
1.3 前端框架的集成
现代前端框架如React、Vue或Angular提供了更高级的状态管理和组件化方法来实现文件上传功能。这些框架通常拥有第三方库和组件,可以简化开发流程,并提供一系列预置功能,如拖拽上传、进度条显示等。
以React为例,我们可以使用 react-dropzone 来创建一个可拖拽的上传区域:
import React from 'react';
import { useDropzone } from 'react-dropzone';
function DropzoneComponent() {
const { getRootProps, getInputProps } = useDropzone({
onDrop: acceptedFiles => {
console.log(acceptedFiles);
}
});
return (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
);
}
在本章中,我们介绍了图片上传功能的基础知识,并逐步通过原生JavaScript和前端框架对上传功能进行了增强。在接下来的章节中,我们将深入探讨多张图片的文件选择与预览技术、图片分块上传与异步处理技术以及后端的数据处理与存储解决方案等更多高级主题。
2. 多张图片的文件选择与预览技术
2.1 用户界面设计要点
2.1.1 设计原则与用户体验
在设计多张图片的文件选择与预览界面时,我们需要遵循几个关键的设计原则来确保最佳的用户体验。首先,界面必须简洁明了,确保用户能够轻松地理解如何进行文件选择和查看预览。我们可以通过清晰的视觉引导和简洁的提示文案来实现这一点。例如,使用大而清晰的按钮以及“点击此处”或“拖放图片到这里”的直接指引。
此外,界面元素的布局应考虑用户的视觉流程,将最常用的操作放在容易到达的位置。比如,文件选择按钮应该在屏幕的中间偏上位置,预览区域则紧随其后,让用户不需要花费太多时间去寻找接下来的操作步骤。
2.1.2 响应式布局与交互逻辑
为了适应不同设备的屏幕尺寸,前端界面需要实现响应式设计。这意味着无论用户是在桌面浏览器、平板电脑还是手机上,界面都应该保持良好的布局和可操作性。为此,可以采用媒体查询(Media Queries)技术,动态地调整布局和字体大小,确保在所有设备上都能提供舒适的浏览体验。
交互逻辑的实现同样重要。当用户点击文件选择按钮时,应立刻打开操作系统的文件选择对话框;上传图片后,系统应自动在预览区域显示图片缩略图。同时,如果上传失败,需要有明确的错误提示信息,告知用户下一步该如何操作。
2.2 文件选择组件的实现
2.2.1 前端JavaScript框架选择
为了实现一个多图片文件选择与预览的功能,前端框架的选择至关重要。目前市面上有许多流行的前端JavaScript框架,如React、Vue.js和Angular等,它们各有特色,但都能提供高效的组件化开发模式。
例如,使用Vue.js可以利用其组件系统快速构建文件选择和图片预览的组件,通过单文件组件(.vue文件)的方式将HTML、CSS和JavaScript代码组织在一起。Vue.js还提供了双向数据绑定和响应式系统,可以更加方便地管理图片文件的状态和渲染预览。
2.2.2 多文件选择功能的实现方法
多文件选择功能的核心在于HTML5的 元素,它支持multiple属性,允许用户一次选择多个文件。在实现该功能时,首先需要在HTML中添加一个文件输入元素,并设置其multiple属性。
<input type="file" id="image-input" multiple>
然后,在JavaScript中,我们可以通过监听该输入元素的change事件来获取用户选择的文件列表。这通常涉及到使用原生DOM API或框架提供的特定方法。
在Vue.js中,我们可以这样做:
new Vue({
el: '#app',
data: {
selectedFiles: []
},
methods: {
handleFileSelect(event) {
const files = event.target.files;
this.selectedFiles = Array.from(files);
// 接下来可以将选中的文件进行预览处理
}
}
});
这段代码中,当用户选择文件后, handleFileSelect 方法会被触发,它将用户选择的文件存储在组件的数据对象中,以便后续进行操作,如预览。
2.3 图片预览功能的构建
2.3.1 实现图片即时预览的技术路径
为了实现图片的即时预览功能,我们通常会在用户选择文件后,利用JavaScript创建一个img元素,并将用户所选文件的URL设置为该元素的source。这一过程可以通过以下代码实现:
function createImageElement(file) {
const img = document.createElement('img');
img.classList.add('img-preview');
img.file = file; // 保存文件信息以供后续使用
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result; // 设置图片URL
};
reader.readAsDataURL(file); // 将文件读取为DataURL
return img;
}
const fileInput = document.getElementById('image-input');
fileInput.addEventListener('change', (event) => {
const selectedFiles = event.target.files;
const previewContainer = document.getElementById('preview-container');
previewContainer.innerHTML = '';
selectedFiles.forEach(file => {
const imgElement = createImageElement(file);
previewContainer.appendChild(imgElement);
});
});
在上述代码中, createImageElement 函数负责创建一个img元素,并使用FileReader对象读取文件内容。当文件读取完成,它的onload事件触发,并将DataURL设置为img的src属性。然后,这个img元素被添加到页面上的预览容器中。
2.3.2 预览功能的优化与兼容性处理
为了优化用户界面,我们需要对预览功能进行一些优化。例如,可以限制预览区域能够显示的图片数量,只展示最近选择的几张图片。此外,还应考虑到浏览器的兼容性问题,一些旧版浏览器可能不支持DataURL或者有加载延迟。为了应对这些情况,我们可以提供一个备用的加载提示或者为不支持DataURL的浏览器提供一个下载链接。
在预览图片时,还应处理图片的尺寸。过大的图片可能会导致页面加载缓慢,影响用户体验。因此,我们可以使用HTMLCanvasElement的drawImage方法将图片缩放到合适的尺寸。
function resizeImage(file, maxWidth, maxHeight) {
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = function() {
const canvas = document.createElement('canvas');
const scale = Math.min(maxWidth / img.width, maxHeight / img.height);
canvas.width = img.width * scale;
canvas.height = img.height * scale;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(img.src); // 释放创建的URL对象
return canvas.toDataURL();
};
}
这段代码会创建一个图片的缩放版本,并返回缩放后图片的DataURL。在实际应用中,可以在 createImageElement 函数中使用 resizeImage 函数来处理图片尺寸,从而提高页面性能和用户体验。
3. 图片分块上传与异步处理技术
在现代的Web应用中,处理大型文件上传是常见的需求之一。图片分块上传技术能够通过将大文件切割成小块进行单独上传,从而提高上传效率,减少失败重传的成本,并且能在不稳定或低带宽的网络环境下,提升用户体验。在本章节,我们将深入了解图片分块上传的原理与实现,以及如何结合异步处理技术来优化整个上传过程。
3.1 图片分块上传的原理与实现
3.1.1 分块上传的优势与应用场景
分块上传主要应用于需要上传大文件的场景,如图像、视频等。这种方法相较于传统的单一文件上传,具有以下优势:
提升上传效率 :大文件被分成多个小块,每个小块可以并行上传,从而显著提高上传速度。
减少重传成本 :当网络中断或上传失败时,只需重新上传未成功的小块,而不需要重新上传整个文件。
增强用户反馈 :通过显示每个小块的上传进度,可以提供更流畅的用户体验。
负载均衡 :后端可以更好地控制资源分配,将文件块分别处理,提高系统的整体效率和稳定性。
3.1.2 分块逻辑的前端实现方法
在前端实现图片分块上传,通常需要遵循以下步骤:
文件选择与读取 :允许用户选择一个大文件,并使用JavaScript的File API读取文件。
分块处理 :将文件按设定的大小进行切割,每块生成一个Blob对象。
上传单个文件块 :对每个文件块进行异步上传,可以使用XMLHttpRequest或Fetch API。
错误处理与重传 :对上传过程中发生的错误进行处理,必要时重新上传失败的文件块。
上传顺序控制 :可以通过队列管理上传顺序,确保文件块的正确组合。
接下来,我们来展示一个简单的分块上传的前端实现代码:
// 假设分块大小为1MB
const chunkSize = 1024 * 1024;
const file = document.querySelector('input[type="file"]').files[0];
const chunks = [];
function createChunk(file, start, end) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file.slice(start, end));
reader.onload = () => resolve({ chunk: reader.result, start, end });
reader.onerror = reject;
});
}
async function uploadChunks(file) {
for (let start = 0; start < file.size; start += chunkSize) {
const end = Math.min(file.size, start + chunkSize);
const chunk = await createChunk(file, start, end);
const res = await fetch('/upload', {
method: 'POST',
body: chunk.chunk
});
if (!res.ok) {
throw new Error('Upload failed');
}
chunks.push({ start, end });
}
// 发送所有分块信息到后端
await fetch('/commit', {
method: 'POST',
body: JSON.stringify(chunks)
});
}
uploadChunks(file).then(() => {
console.log('Upload completed');
}).catch((error) => {
console.error('Upload error:', error);
});
在上述代码中, createChunk 函数负责生成每个文件块的Promise, uploadChunks 函数则异步地上传每个块,并在所有块上传完成后,通知后端提交整个文件。
3.2 异步上传技术的细节处理
3.2.1 异步上传的前端策略
在实施异步上传时,前端应采取以下策略:
并发控制 :限制同时上传的文件块数量,防止对服务器造成过大压力。
队列管理 :在多个文件上传时,管理好各个上传任务的优先级和状态。
用户反馈 :提供上传进度的实时反馈,比如进度条、剩余时间等。
稳定重试 :遇到网络波动或服务器错误时,可以设置重试机制,而不是让用户重新上传整个文件。
3.2.2 上传进度的实时显示技术
要实现上传进度的实时显示,前端代码需要能获取到每块文件上传的状态,并更新UI:
// 假设这是上传单个块的函数
async function uploadChunk(chunk, index) {
const res = await fetch('/upload', {
method: 'POST',
body: chunk
});
return {
index,
status: res.ok ? 'success' : 'failed'
};
}
async function uploadFileWithProgress(file) {
const chunkPromises = [];
for (let i = 0; i < chunks.length; i++) {
chunkPromises.push(uploadChunk(chunks[i], i));
}
const results = await Promise.all(chunkPromises);
// 更新UI显示每个块的上传状态
results.forEach((result) => {
updateUIForChunkUpload(result.index, result.status);
});
}
function updateUIForChunkUpload(chunkIndex, status) {
// 更新进度条或UI元素,展示对应块的上传状态
}
3.3 后端异步处理机制
3.3.1 后端异步处理的架构设计
后端在处理分块上传时需要一个健壮的架构,以处理并发请求和任务状态。通常,这种架构会包含以下几个部分:
任务队列 :接收并排队所有上传的文件块。
任务处理器 :处理每个上传块的具体逻辑,如存储到临时文件。
状态记录器 :记录每个文件块的状态和进度信息。
组合器 :负责将所有上传成功的小块组合成最终的文件。
3.3.2 异步任务的状态管理与监控
为了管理异步任务的状态,后端系统需要实现以下功能:
进度追踪 :记录每个文件块的上传进度和状态。
任务恢复 :在失败后能恢复上传状态,确保不会重复上传已经上传成功的部分。
监控与报警 :监控任务执行状态,对于长时间未完成的任务进行报警。
在实现这些功能时,通常可以采用数据库或缓存系统来存储状态信息,并设计RESTful API或WebSocket接口供前端调用。
以上就是本章关于图片分块上传与异步处理技术的内容。通过这些技术手段,我们能够有效地解决大文件上传中遇到的性能和稳定性问题,为用户带来更加可靠和流畅的上传体验。
4. 后端数据处理与存储解决方案
4.1 图片上传的数据接收与处理
4.1.1 后端接收机制与安全性考量
在处理图片上传功能时,后端接收机制的设计至关重要。它不仅需要高效处理数据流,还要确保整个过程的安全性。首先,后端服务需要能够接收来自前端的HTTP请求,并从中提取图片文件。
通常,这一步骤涉及到设置一个处理文件上传的API接口。例如,在使用Node.js开发时,可以使用 multer 这个中间件来处理 multipart/form-data 类型的数据,它专为处理文件上传而设计。 multer 会在请求中解析文件和非文件数据,然后我们可以获取到上传的文件对象。
const multer = require('multer');
const express = require('express');
const upload = multer({ dest: 'uploads/' });
const app = express();
app.post('/upload', upload.single('image'), (req, res) => {
// 文件保存在服务器上,处理逻辑放在这里
});
在安全性方面,需要考虑的点包括验证上传文件的类型(如防止恶意文件上传),限制上传文件的大小(防止服务端被大文件拖垮),以及设置合适的HTTP头部来避免常见的安全问题(例如使用 Content-Type 来防止内容被篡改)。
4.1.2 图片数据的预处理流程
一旦后端成功接收图片文件,接下来就需要对图片数据进行预处理。预处理的目的通常是为了优化图片在应用中的使用,例如调整大小以适应不同的显示需求,或者转换图片格式以节省存储空间。
在Node.js中,我们可以利用图像处理库如 sharp 来实现这些预处理步骤。 sharp 支持多种图片格式,并且针对性能优化进行了大量工作,使其成为处理图片的不二之选。
const sharp = require('sharp');
app.post('/process-image', upload.single('image'), async (req, res) => {
const inputPath = req.file.path;
const outputPath = './processedImages/' + req.file.originalname;
await sharp(inputPath)
.resize(800, 600, { fit: 'inside' }) // 调整大小
.toFormat('jpeg') // 转换格式
.toFile(outputPath) // 保存新图片
.then(info => {
res.status(200).send('Image processed successfully!');
})
.catch(err => {
res.status(500).send('Error processing image.');
});
});
在处理过程中, sharp 库提供了一系列功能,包括图片的缩放、裁剪、旋转和格式转换。预处理可以显著减少存储需求,并提高加载图片时的效率。
4.2 图片存储策略与管理
4.2.1 图片存储的文件系统选择
在处理图片存储时,选择合适的文件系统非常关键。传统的文件系统如ext4或XFS可以工作得很好,但对于高并发的图片存储场景,这些文件系统可能无法提供最优的性能。
对于需要处理高并发读写的场景,可以考虑使用高性能文件系统,如 Facebook 的 Haystack 系统,它专门为图片和视频存储设计,优化了存储和检索速度。另一种选择是对象存储服务,如Amazon S3或阿里云OSS,它们提供水平扩展能力和高可用性,能够很好地应对大规模的图片存储需求。
4.2.2 高效的图片存储与检索机制
图片存储和检索的效率直接决定了应用的响应速度和用户体验。为了高效存储图片,可以采用一些优化措施,比如:
使用存储分片技术将图片均匀分散到不同的服务器上。
利用缓存机制,如Redis,将热门图片缓存到内存中,减少磁盘I/O。
实现一个负载均衡机制,根据服务器负载情况智能分配图片存储请求。
在图片检索方面,除了直接的文件名检索外,还可以根据内容进行搜索。使用内容感知哈希技术(例如 pHash ),可以根据图片内容生成唯一标识,通过比较图片的哈希值来识别相似图片,从而实现高效的图片检索。
4.3 数据库处理与数据完整性保障
4.3.1 数据库操作的优化技巧
在涉及到图片上传功能的应用中,通常会有一个数据库来存储图片的元数据信息,比如文件名、上传时间、文件大小等。为了提高数据库操作的效率,可以采取一些优化措施:
为可能的查询操作创建索引,比如对于图片名、上传时间等字段。
优化SQL查询语句,避免全表扫描。
使用批量操作和事务处理来减少数据库的I/O操作次数。
实施合理的缓存策略,以减少对数据库的读取频率。
在后端代码中,合理使用数据库连接池也是提高数据库操作效率的关键。例如,在Node.js中使用 pg-pool 进行PostgreSQL数据库的连接池管理。
const Pool = require('pg').Pool;
const pool = new Pool({
user: 'your-username',
host: 'localhost',
database: 'your-database',
password: 'your-password',
port: 5432
});
app.post('/save-metadata', async (req, res) => {
const client = await pool.connect();
try {
await client.query('INSERT INTO images (name, size, upload_date) VALUES ($1, $2, $3)', [
req.body.name,
req.body.size,
new Date()
]);
res.status(200).send('Metadata saved successfully.');
} catch (err) {
res.status(500).send('Error saving metadata.');
} finally {
client.release();
}
});
4.3.2 事务处理与数据一致性维护
在存储图片元数据时,为了保证数据的一致性,需要进行事务处理。事务处理确保了多个操作要么全部成功,要么全部失败,不会留下不一致的数据状态。
在数据库操作中,利用事务可以确保相关的数据操作要么全部完成,要么全部回滚。以Node.js为例,可以使用 pg 模块来进行事务处理,保证元数据和图片文件的一致性。
app.post('/upload-image', upload.single('image'), async (req, res) => {
const client = await pool.connect();
try {
await client.query('BEGIN'); // 开始事务
// 插入元数据
await client.query('INSERT INTO images (name, size, upload_date) VALUES ($1, $2, $3)', [
req.file.originalname,
req.file.size,
new Date()
]);
// 文件存储操作(此处省略文件存储逻辑)
await client.query('COMMIT'); // 提交事务
res.status(200).send('Image and metadata uploaded successfully.');
} catch (err) {
await client.query('ROLLBACK'); // 出错时回滚事务
res.status(500).send('Error uploading image or metadata.');
} finally {
client.release();
}
});
通过事务处理,我们确保了图片的元数据和实际文件都被正确地存储,或者在出现错误时都不会留下不完整或不一致的数据记录。这种数据完整性保障对于维护整个应用的可靠性至关重要。
5. 图片上传功能的安全性与用户体验优化
5.1 上传过程中的安全性措施
在实现图片上传功能时,安全性是一个不可忽视的话题。用户上传的图片可能会被恶意利用,例如作为攻击媒介来执行跨站脚本(XSS)、跨站请求伪造(CSRF)等攻击。因此,对上传过程中的安全性措施进行周密的考虑至关重要。
5.1.1 常见安全威胁与防护策略
首先,对于XSS攻击的防护,可以通过对上传的图片进行内容检测,排除包含恶意脚本的图片。同时,在用户界面上提供合适的图片预览功能,避免使用内嵌脚本处理图片预览,减少潜在风险。
对于CSRF攻击,确保所有的上传操作都需要验证用户的身份。一般采用的策略是生成一个安全令牌(如CSRF token),这个令牌需要在用户提交请求时附加在表单中。服务器端接收到请求后,验证这个令牌的有效性,以此确保请求是由验证过的用户发起。
5.1.2 跨站请求伪造(CSRF)与跨站脚本(XSS)防护
在前端实现上,可以在生成表单时,嵌入一个隐藏字段,内容是服务器端生成的CSRF token,示例如下:
<form id="uploadForm" action="/upload" method="post">
<!-- 其他表单元素 -->
<input type="hidden" name="csrf_token" value="生成的CSRF令牌">
<input type="submit" value="上传图片">
</form>
在服务器端,需要验证每次请求中CSRF token是否正确。
对于XSS防护,可以采用内容安全策略(CSP),限制网页可以加载的内容类型,确保脚本只能来源于服务器或白名单内的可信资源。服务器在响应头中添加 Content-Security-Policy 字段,指定允许加载的内容来源。
Content-Security-Policy: script-src 'self'; object-src 'none'; img-src 'self'; style-src cdn.example.org third-party.org; child-src https:
5.2 错误处理机制与用户反馈
用户体验往往在错误处理时显得尤为关键。合理的错误提示和反馈机制,能够有效地帮助用户理解问题所在,并采取相应的措施。
5.2.1 错误检测与用户提示设计
在图片上传过程中,可能会出现各种错误,如文件格式不支持、文件过大、网络问题等。对于这些错误,需要有清晰的提示信息。
例如,可以使用JavaScript捕捉上传时的异常,并显示相应的错误消息:
function uploadFile(file) {
// 假设是一个上传函数
try {
// 上传文件的逻辑
} catch(error) {
// 显示错误信息
alert('上传失败: ' + error.message);
}
}
5.2.2 用户友好的错误处理流程
为了提供更好的用户体验,错误处理流程不应仅仅停留在显示错误消息的层面。应该引导用户进行错误纠正,如提示用户选择不同格式的图片、压缩图片以减小大小,或者重试上传等。
可以设计一个友好的错误处理组件,根据不同类型的错误,给出明确的解决建议:
<div id="errorHandling">
<p>上传失败,请检查以下情况:</p>
<ul>
<li>确保文件格式是.jpg或.png。</li>
<li>文件大小不超过5MB。</li>
<li>检查您的网络连接是否稳定。</li>
</ul>
</div>
5.3 用户体验的提升方法与开发者API设计
用户体验的优化是一个全方位的过程,包括前端的用户界面和交互设计,也包括后端的API设计,确保简洁、直观且易于开发者使用。
5.3.1 提升用户体验的设计要点
设计一个简洁的图片上传界面,使用直观的按钮和进度条来指示上传状态。进度条是提升用户体验的重要元素,它应该显示准确的上传进度,以及当前的上传状态。例如,可以使用HTML5的 <progress> 标签来实现:
<label for="fileInput">选择文件上传:</label>
<input type="file" id="fileInput" name="file">
<progress id="progressBar" max="100">0%</progress>
5.3.2 开发者友好的API接口规范与文档编写
对于API的设计,应该遵循RESTful原则,提供简单直观的接口路径,以及清晰的请求方法。例如,图片上传接口的API可以设计为:
POST /api/v1/images/upload
在文档编写方面,提供详尽的API描述、请求参数说明、返回值解释、成功与错误的响应示例,以及可能的错误代码表。这样的文档能够帮助开发者快速理解和使用API。
### 图片上传接口
#### 请求方式
`POST /api/v1/images/upload`
#### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
|------------|--------|------|----------|
| file | File | 是 | 要上传的图片文件 |
| csrf_token | String | 是 | CSRF安全令牌 |
#### 成功响应
- 状态码: `200 OK`
- 内容: `{"message": "文件上传成功", "url": "图片访问地址"}`
#### 错误响应
- 状态码: `400 Bad Request`
- 内容: `{"error": "上传失败的详细信息"}`
> 注意:所有参数都需要在请求体中以`multipart/form-data`格式发送。
以上内容通过实践指导、表格列举、代码示例和Markdown格式的清晰性,帮助IT专业人士更好地理解和优化图片上传功能的安全性和用户体验。
本文还有配套的精品资源,点击获取
简介:在IT领域,实现用户能够一次上传多张图片的功能对提高工作效率至关重要。文章详细解释了实现多图上传功能的前端处理、文件分块、异步上传、后端存储、安全性和错误处理等关键技术环节。同时,还强调了优化上传性能、提升用户体验和API设计的重要性,为开发高效且用户友好的上传功能提供全面指导。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_29885875/article/details/144439613