ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Instant Storage 文件上传与托管实战指南:从图片网格到权限控制

Instant Storage 文件上传与托管实战指南:从图片网格到权限控制 后端数据库【免费下载链接】instantInstant is the best backend for AI-coded apps. You get auth, permissions, storage, presence, and streams — everything you need to ship apps your users will love.项目地址https://gitcode.com/gh_mirrors/inst/instant点击查看免费下载Instant Storage 是 Instant 提供的开箱即用文件存储能力让应用可以轻松上传并托管图片、视频、文档等任意类型文件。本文基于仓库中的官方文档client/www/app/docs/storage/page.md整理成文并结合 client/packages/core/src/StorageAPI.ts、client/packages/core/src/index.ts 与 client/packages/admin/src/index.ts 中的真实实现深入讲解上传、覆盖、查询、删除、更新、链接、权限控制以及 React Native / Admin SDK 的完整用法。读完本文你将能在自己的 Instant 应用中实现一个可实时同步、带权限校验的文件上传与展示功能。Storage 快速上手构建一个实时图片墙Instant Storage 与数据库深度集成文件上传后会自动写入$files命名空间查询、排序、关联与权限规则都可以像操作普通实体一样使用。下面从零开始构建一个上传并展示图片网格的完整示例。1. 创建 Next.js 项目并安装依赖npx create-next-app instant-storage --tailwind --yes cd instant-storage npm i instantdb/react2. 初始化 schema 与权限通过 CLI 初始化 Instant 配置CLI 的完整用法见 client/www/app/docs/cli/page.mdnpx instant-clilatest init打开生成的instant.schema.ts替换为以下内容。核心是声明了一个$files实体——这是 Instant Storage 内置的特殊命名空间用于描述文件元数据import { i } from instantdb/react; const _schema i.schema({ entities: { $files: i.entity({ path: i.string().unique().indexed(), url: i.string(), }), $users: i.entity({ email: i.string().unique().indexed(), }), }, links: {}, rooms: {}, }); // This helps TypeScript display nicer IntelliSense type _AppSchema typeof _schema; interface AppSchema extends _AppSchema {} const schema: AppSchema _schema; export type { AppSchema }; export default schema;其中path是文件在存储目录中的路径unique().indexed()保证路径唯一并支持按路径查询url是服务端生成的可直接用于展示文件内容的下载地址。$users是 Instant 内置的用户实体在后续权限示例中会用到。同样打开instant.perms.ts替换为以下权限规则import type { InstantRules } from instantdb/react; // Not recommended for production since this allows anyone to // upload/delete, but good for getting started const rules { $files: { allow: { view: true, create: true, delete: true } } } satisfies InstantRules; export default rules;该规则允许任何人上传与删除文件仅适合快速体验生产环境请参考文末Storage 权限控制一节收紧。把 schema 与权限推送到你的 Instant 应用npx instant-clilatest push3. 实现上传与图片网格将app/page.tsx替换为以下代码。它演示了 Instant Storage 的三个核心 APIdb.storage.uploadFile(file.name, file, opts)执行实际上传db.useQuery({ $files: {...} })查询文件列表上传完成后查询结果会自动更新实时同步db.transact(db.tx.$files[image.id].delete())删除文件。use client; import { init, InstaQLEntity } from instantdb/react; import schema, { AppSchema } from ../instant.schema; import React from react; type InstantFile InstaQLEntityAppSchema, $files const APP_ID process.env.NEXT_PUBLIC_INSTANT_APP_ID; const db init({ appId: APP_ID, schema }); // uploadFile is what we use to do the actual upload! // The $files query will automatically update once the upload is complete async function uploadImage(file: File) { try { // Optional metadata you can set for uploads const opts { // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type // Default: application/octet-stream contentType: file.type, // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition // Default: inline contentDisposition: attachment, }; await db.storage.uploadFile(file.name, file, opts); } catch (error) { console.error(Error uploading image:, error); } } function App() { // $files is the special namespace for querying storage data const { isLoading, error, data } db.useQuery({ $files: { $: { order: { serverCreatedAt: asc }, }, }, }); if (isLoading) { return null; } if (error) { return divError fetching data: {error.message}/div; } // The result of a $files query will contain objects with // metadata and a download URL you can use for serving files! const { $files: images } data return ( div classNamebox-border bg-gray-50 font-mono min-h-screen p-5 flex items-center flex-col div classNametracking-wider text-5xl text-gray-300 mb-8 Image Feed /div ImageUpload / div classNametext-xs text-center py-4 Upload some images and they will appear below! Open another tab and see the changes in real-time! /div ImageGrid images{images} / /div ); } interface SelectedFile { file: File; previewURL: string; } function ImageUpload() { const [selectedFile, setSelectedFile] React.useStateSelectedFile | null(null); const [isUploading, setIsUploading] React.useState(false); const fileInputRef React.useRefHTMLInputElement(null); const { previewURL } selectedFile || {}; const handleFileSelect (e: React.ChangeEventHTMLInputElement) { const file e.target.files?.[0]; if (file) { const previewURL URL.createObjectURL(file); setSelectedFile({ file, previewURL }); } }; const handleUpload async () { if (selectedFile) { setIsUploading(true); await uploadImage(selectedFile.file); URL.revokeObjectURL(selectedFile.previewURL); setSelectedFile(null); fileInputRef.current?.value (fileInputRef.current.value ); setIsUploading(false); } }; return ( div classNamemb-8 p-5 border-2 border-dashed border-gray-300 rounded-lg input ref{fileInputRef} typefile acceptimage/* onChange{handleFileSelect} classNamefont-mono / {isUploading ? ( div classNamemt-5 flex flex-col items-center div classNamew-8 h-8 border-2 border-t-2 border-gray-200 border-t-green-500 rounded-full animate-spin/div p classNamemt-2 text-sm text-gray-600Uploading.../p /div ) : previewURL ( div classNamemt-5 flex flex-col items-center gap-3 img src{previewURL} altPreview classNamemax-w-xs max-h-xs object-contain / button onClick{handleUpload} classNamepy-2 px-4 bg-green-500 text-white border-none rounded-sm cursor-pointer font-mono Upload Image /button /div )} /div ); } function ImageGrid({ images }: { images: InstantFile[] }) { // Use db.transact to delete files const handleDelete async (image: InstantFile) { db.transact(db.tx.$files[image.id].delete()); } return ( div classNamegrid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5 w-full max-w-6xl {images.map((image) { return ( div key{image.id} classNameborder border-gray-300 rounded-lg overflow-hidden div classNamerelative {/* $files entities come with a url property */} img src{image.url} alt{image.path} classNamew-full h-64 object-cover / /div div classNamep-3 flex justify-between items-center bg-white span{image.path}/span span onClick{() handleDelete(image)} classNamecursor-pointer text-gray-300 px-1 /span /div /div ) })} /div ); } export default App;4. 启动应用npm run dev访问localhost:3000即可看到一个支持上传、实时展示与删除的图片墙。打开另一个浏览器标签页新上传的图片会自动出现——这就是$files查询随数据变更实时更新的体现。Storage 客户端 SDK 详解以下从 React 客户端角度详细说明 Storage API 的各个操作。上传文件使用db.storage.uploadFile(path, file, opts?)上传文件path决定文件在存储中的位置同时可以与权限规则配合限制对特定目录下文件的访问file应为File类型通常来自input typefile文件选择框opts用于设置额外的元数据如contentType与contentDisposition。// use the files current name as the path await db.storage.uploadFile(file.name, file); // or, give the file a custom name const path ${user.id}/avatar.png; await db.storage.uploadFile(path, file); // or, set the content type and content disposition const path ${user.id}/orders/${orderId}.pdf; await db.storage.uploadFile(path, file, { contentType: application/pdf, contentDisposition: attachment; filename${orderId}-confirmation.pdf, });从底层实现看uploadFile最终通过PUT请求发送到${apiURI}/storage/upload请求头中携带app-id、path、authorization: Bearer refreshToken并默认把content-type设为file.type见 client/packages/core/src/StorageAPI.ts。因此opts.contentType的默认值实际是application/octet-stream或文件的 MIME 类型而contentDisposition默认是inline即浏览器内联展示设置为attachment或带filename的完整值则会触发下载行为。上传成功后返回的响应结构为type UploadFileResponse { data: { id: string; }; };其中的文件id可用于后续的删除与关联操作。覆盖文件如果上传的path在存储目录中已经存在会被直接覆盖// Uploads a file to demo.png await db.storage.uploadFile(demo.png, file); // Overwrites the file at demo.png await db.storage.uploadFile(demo.png, file);如果不想覆盖文件需要自行保证每次上传使用唯一的path例如在路径中加入用户 id 或时间戳。查看文件通过查询$files命名空间获取文件列表。文件实体的核心属性包括id文件的唯一标识path文件在存储中的路径url可用于直接展示/下载文件的地址服务端签名生成content-type与content-disposition上传时设置的元数据。// Fetch all files from earliest to latest upload const query { $files: { $: { order: { serverCreatedAt: asc }, }, }, }); const { isLoading, error, data } db.useQuery(query);查询结果示例console.log(data) { $files: [ { id: fileId, path: demo.png // You can use this URL to serve the file url: https://instant-storage.s3.amazonaws.com/..., content-type: image/png, content-disposition: attachment; filename\demo.png\, }, // ... ] }$files与其他命名空间一样支持查询过滤与排序可以对文件进行条件筛选和按字段排序InstaQL 查询语法详见 client/www/app/docs/instaql/page.md。还可以借助关联links把文件与业务实体绑定。例如下面 schema 定义了profiles与$files之间的多对一关联// instant.schema.ts // --------------- import { i } from instantdb/core; const _schema i.schema({ entities: { $files: i.entity({ path: i.string().unique().indexed(), url: i.string(), }), $users: i.entity({ email: i.string().unique().indexed(), }), profiles: i.entity({ nickname: i.string(), createdAt: i.date(), }), }, links: { profileUser: { forward: { on: profiles, has: one, label: $user }, reverse: { on: $users, has: one, label: profile }, }, profileUploads: { forward: { on: profiles, has: many, label: $files }, reverse: { on: $files, has: one, label: profile }, }, }, });随后即可查询某个用户 profile 下的所有文件// app/page.tsx // --------------- // Find files associated with a profile const { user } db.useAuth(); const query { profiles: { $: { where: {$user.id: user.id} }, $files: {}, }, }); // Defer until weve fetched the user and then query associated files const { isLoading, error, data } db.useQuery(user ? query : null);删除文件使用db.transact删除文件支持按id、按path通过lookup唯一属性查找以及批量删除// Delete by id db.transact(db.tx.$files[fileId].delete()); // Delete by path db.transact(db.tx.$files[lookup(path, photos/demo.png)].delete()); // Delete multiple files db.transact(fileIds.map((id) db.tx.$files[id].delete()));lookup(path, ...)是 InstaML 提供的按唯一属性定位实体的机制——仓库中 client/packages/core/src/instaml.ts 会校验 lookup 必须包含唯一的属性path恰好声明为unique()再将其改写为内部实体 id 引用。客户端db.storage.delete虽然仍可用但已在源码中标记为deprecated官方推荐统一使用db.transact删除见 client/packages/core/src/index.ts。更新文件可以通过db.transact更新文件的path以及任何你为$files自定义添加的列。例如 schema 中包含自定义列isFavoriteimport { i } from instantdb/react; const _schema i.schema({ entities: { $files: i.entity({ path: i.string().unique().indexed(), isFavorite: i.boolean().optional() url: i.string(), }), }, });下面的事务将所有documents/my-video-project/下的文件移动到videos/my-video-project/并标记为收藏// Move all files under documents/my-video-project/ to videos/my-video-project/ and make them favorites const { data } await db.query({ $files: { $: { where: { path: { $like: documents/my-video-project/% } } } }, }); await db.transact( data.$files.map((file) db.tx.$files[file.id].update({ path: file.path.replace( documents/my-video-project/, videos/my-video-project/, ), isFavorite: true, }), ), );需要注意两点path是唯一属性如果目标path已被其他文件占用事务会失败目前只允许更新$files的path属性和自定义列尝试更新content-type之类的系统属性会导致事务失败。链接文件上传成功后uploadFile返回的data对象中包含该文件的 id可用来把文件与其他命名空间实体建立关联。下面实现一个用户头像上传场景——上传后把文件链接到对应 profileasync function uploadImage(file: File) { try { const path ${user.id}/avatar; const { data } await db.storage.uploadFile(path, file); await db.transact(db.tx.profiles[profileId].link({ avatar: data.id })); } catch (error) { console.error(Error uploading image:, error); } }仓库中的sandbox/react-nextjs、examples等示例工程均基于instantdb/react构建可参考其中的 schema 与事务写法来扩展这类文件 业务实体的关联模型。在 React Native 中使用 Storagedb.storage.uploadFile期望传入File或Blob。根据 Expo SDK 版本不同获取方式有所差异Expo SDK 56 及以上expo/fetch作为全局fetch可直接读取本地文件因此传入expo-file-system提供的Fileimport { File } from expo-file-system; const localFilePath file:///var/mobile/Containers/Data/my_file.m4a; const file new File(localFilePath); await db.storage.uploadFile(my_file.m4a, file, { contentType: audio/x-m4a, });Expo SDK 55 及以下或裸 React Native内置fetch返回原生Blob可包装成File再上传const localFilePath file:///var/mobile/Containers/Data/my_file.m4a; const res await fetch(localFilePath); const blob await res.blob(); const file new File([blob], my_file.m4a, { type: audio/x-m4a }); await db.storage.uploadFile(my_file.m4a, file);Storage Admin SDK服务端Admin SDK 提供了与服务端管理场景匹配的存储 API。与客户端 SDK 不同Admin SDK 不执行权限校验因此可以绕过认证直接在服务端管理文件——这也意味着使用时要格外注意只在自己的受信后端调用。上传文件服务端同样调用db.storage.uploadFile(path, file, opts?)但file参数必须是Buffer缓冲区或流Streamimport fs from fs; const fp path/to/your/file.png; const dest images/demo.png; // Upload a file from a buffer const buffer fs.readFileSync(filepath); const { data } await db.storage.uploadFile(dest, buffer); // Upload a file from a stream // IMPORTANT: You must provide fileSize as an option when uploading via stream const stream fs.createReadStream(fp); const fileSize fs.statSync(fp).size; const { data } await db.storage.uploadFile(dest, stream, { contentType: contentType, fileSize, });从实现看client/packages/admin/src/index.tsAdmin SDK 的上传请求发送到${apiURI}/admin/storage/upload?app_id...。当检测到file是 Node 可读流或 WebReadableStream时必须提供fileSize否则会直接抛出fileSize is required in metadata when uploading streams错误内部会把fileSize写入content-length请求头并为 Node 流设置duplex: half单向流。查看文件与客户端类似但使用db.query()而非db.useQuery()无 React Hooks 环境const query { $files: { $: { order: { serverCreatedAt: asc }, }, }, }); const data db.query(query);删除文件同样通过db.transact完成// Delete by id await db.transact(db.tx.$files[fileId].delete()); // Delete by path await db.transact(db.tx.$files[lookup(path, photos/demo.png)].delete()); // Delete multiple files await db.transact(fileIds.map((id) db.tx.$files[id].delete()));链接文件服务端也可以用上传返回的文件 id 建立关联// Assume we have a user ID and a buffer for the file const { data } await db.storage.uploadFile(images/demo.png, buffer); db.transact([db.tx.$users[userId].link({ avatar: data.id })]);Storage 权限控制默认情况下 Storage 权限是关闭的在显式配置权限之前任何上传与下载都不会成功。各权限关键字的作用如下create权限允许上传$filesview权限允许查看$filesupdate权限允许更新$filesdelete权限允许删除$files对$files的view权限加上对正向实体的update权限才能对$files进行链接link与取消链接unlink。在权限规则中可以使用auth访问当前认证用户使用data访问文件元数据。目前可用的文件元数据仅有data.path即文件在 Storage 中的路径。以下是一些典型的权限配置允许任何人上传与查看文件便于快速尝试不推荐用于生产{ $files: { allow: { view: true, create: true } } }仅允许已登录用户查看与上传{ $files: { allow: { view: isLoggedIn, create: isLoggedIn }, bind: [isLoggedIn, auth.id ! null] } }仅允许用户在自己的子目录内上传、查看与更新利用data.path前缀校验{ $files: { allow: { view: isOwner, update: isOwner, create: isOwner }, bind: [isOwner, data.path.startsWith(auth.id /)] } }最后一条规则是典型的按用户隔离存储方案每个用户上传时把path前缀设为auth.id例如uploadFile(\${user.id}/avatar.png, file)配合data.path.startsWith(auth.id /) 即可确保用户只能访问自己的文件。权限规则的完整语法可进一步参考 client/www/app/docs/permissions/page.md。小结Instant Storage 将文件存储纳入了与普通数据一致的查询与权限体系$files命名空间让文件像实体一样可查询、排序、关联与实时同步db.storage.uploadFile一行代码完成上传db.transact统一处理删除、更新与关联权限规则基于auth与data.path实现细粒度的访问控制。无论是 Next.js / React 客户端、React Native 移动端还是需要绕过权限校验的服务端 Admin SDKInstant Storage 都提供了对应的一体化 API让你专注业务本身而非存储基础设施。赞分享后端数据库【免费下载链接】instantInstant is the best backend for AI-coded apps. You get auth, permissions, storage, presence, and streams — everything you need to ship apps your users will love.项目地址https://gitcode.com/gh_mirrors/inst/instant点击查看免费下载相关推荐零信任防护CodeIgniter文件上传与权限控制实战指南零信任防护CodeIgniter文件上传与权限控制实战指南 在Web应用开发中文件上传功能是最常见的攻击入口之一。作为一款轻量级PHP框架CodeIgni后端Web框架VRCX终极指南如何用这款免费工具让VRChat社交管理效率提升300%VRCX终极指南如何用这款免费工具让VRChat社交管理效率提升300% VRCX是一款专门为VRChat玩家设计的免费社交管理工具它通过智能化的好友关系管桌面应用突破上传限制lowcode-engine大文件分片上传终极实战指南突破上传限制lowcode engine大文件分片上传终极实战指南 在低代码开发领域 lowcode engine 作为一套面向扩展设计的企业级低代码技术体前端低代码上一篇3个高效革新Zotero Style插件完全指南下一篇EGO-Planner-v2无人机集群路径规划的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表