@@ -9,6 +9,7 @@ const sharp = require('sharp');
99const { checkAndCreateTable } = require ( './utils/checkAndCreateTable' ) ;
1010const pool = require ( './utils/db' ) ;
1111const { appendSuffixToFilename } = require ( './utils/appendSuffixToFilename' ) ;
12+ const { v4 : uuidv4 } = require ( 'uuid' ) ;
1213require ( 'dotenv' ) . config ( { path : '.env.local' } ) ;
1314
1415const app = new Koa ( ) ;
@@ -28,6 +29,23 @@ const imageMimeTypes = [
2829 'image/svg+xml'
2930] ;
3031
32+ function getMimeType ( filePath ) {
33+ const ext = path . extname ( filePath ) . toLowerCase ( ) ;
34+ switch ( ext ) {
35+ case '.jpg' :
36+ case '.jpeg' :
37+ return 'image/jpeg' ;
38+ case '.png' :
39+ return 'image/png' ;
40+ case '.gif' :
41+ return 'image/gif' ;
42+ case '.webp' :
43+ return 'image/webp' ;
44+ default :
45+ return 'application/octet-stream' ;
46+ }
47+ }
48+
3149app . use ( require ( 'koa-static' ) ( path . join ( __dirname , 'public' ) ) ) ;
3250
3351const createDirectories = ( ) => {
@@ -63,21 +81,24 @@ router.post('/upload', async (ctx) => {
6381
6482 const compress = ctx . query . compress !== 'false' ; // 默认压缩
6583 const keepTemp = ctx . query . keepTemp === 'true' ; // 默认不保留临时文件
66- const isThumb = ctx . query . isThumb === 'true' ;
84+ const isThumb = Number ( ctx . query . isThumb === 'true' ) ;
85+ const isPublic = Number ( ctx . query . isPublic === 'true' ) ;
6786 const responseType = ctx . query . type ;
6887
6988 for ( const file of fileList ) {
7089 const mimeType = mime . lookup ( file . filepath ) ;
90+ const fileId = uuidv4 ( ) ; // 生成文件唯一ID
7191
7292 const outputFilePath = path . join (
7393 __dirname ,
7494 'public' ,
7595 'files' ,
76- path . basename ( file . filepath )
96+ fileId + path . extname ( file . filepath ) // 使用UUID作为文件名称
7797 ) ;
98+
7899 let outputFileThumbPath = null ;
79100 if ( isThumb && imageMimeTypes . includes ( mimeType ) ) {
80- const fileThumbName = appendSuffixToFilename ( file . newFilename , 'thumb' ) ;
101+ const fileThumbName = ` ${ fileId } _thumb ${ path . extname ( file . filepath ) } ` ; // 缩略图文件名称
81102
82103 outputFileThumbPath = path . join (
83104 __dirname ,
@@ -102,23 +123,25 @@ router.post('/upload', async (ctx) => {
102123 }
103124 }
104125
105- const fileUrl = `${ process . env . PUBLIC_NETWORK_DOMAIN } /files/${ path . basename (
106- outputFilePath
107- ) } `;
108- const thumb_location = outputFileThumbPath ? `${ process . env . PUBLIC_NETWORK_DOMAIN } /files/${ path . basename ( outputFileThumbPath ) } ` : null ;
126+ const fileUrl = `${ process . env . PUBLIC_NETWORK_DOMAIN } /files/${ fileId } ` ;
127+ const thumb_location = outputFileThumbPath ? `${ process . env . PUBLIC_NETWORK_DOMAIN } /files/${ fileId } ?type=thumb` : null ;
109128
110129 await connection . execute (
111130 `INSERT INTO files (
112- filename, filesize, filelocation, created_by, is_public, thumb_location, is_delete
113- ) VALUES (?, ?, ?, ?, ?, ?, ?)` ,
131+ id, filename, filesize, filelocation, real_file_location, created_by, is_public, thumb_location, is_thumb, is_delete, real_file_thumb_location
132+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )` ,
114133 [
134+ fileId , // 使用UUID作为ID
115135 path . basename ( outputFilePath ) ,
116136 fs . statSync ( outputFilePath ) . size ,
117137 fileUrl ,
138+ outputFilePath , // 存储实际文件路径
118139 ctx . query . createdBy || 'anonymous' ,
119- ctx . query . isPublic === 'true' ,
140+ isPublic ,
120141 thumb_location ,
121- 0 // 假设默认的 `is_delete` 状态为 0(未删除)
142+ isThumb ,
143+ 0 ,
144+ outputFileThumbPath
122145 ]
123146 ) ;
124147
@@ -144,14 +167,15 @@ router.post('/upload', async (ctx) => {
144167 }
145168} ) ;
146169
170+
147171router . get ( '/files' , async ( ctx ) => {
148172 const connection = await pool . getConnection ( ) ;
149173 try {
150174 const limit = parseInt ( ctx . query . limit , 10 ) || 10 ; // 每页数量,默认为 10
151175 const offset = parseInt ( ctx . query . offset , 10 ) || 0 ; // 偏移量,默认为 0
152176
153177 const [ rows ] = await connection . execute (
154- `SELECT * FROM files LIMIT ? OFFSET ?` ,
178+ `SELECT created_by, created_at, public_by, public_expiration, updated_at, updated_by, filesize, filename, filelocation, thumb_location, is_public FROM files WHERE is_delete = 0 AND is_public = 1 LIMIT ? OFFSET ?` ,
155179 [ limit , offset ]
156180 ) ;
157181
@@ -164,6 +188,57 @@ router.get('/files', async (ctx) => {
164188 }
165189} ) ;
166190
191+ router . get ( '/files/:id' , async ( ctx ) => {
192+ const { id } = ctx . params ;
193+ const { type } = ctx . query ; // 获取查询参数 'type',可以是 'thumb' 或 'original'
194+ const connection = await pool . getConnection ( ) ;
195+
196+ try {
197+ // 查询文件数据,只获取必要字段
198+ const [ rows ] = await connection . execute (
199+ `SELECT filename, is_delete, is_public, public_expiration, real_file_location, real_file_thumb_location, is_thumb
200+ FROM files
201+ WHERE id = ?
202+ AND is_delete = 0
203+ AND (is_public = 1 AND (public_expiration IS NULL OR public_expiration > NOW()))` ,
204+ [ id ]
205+ ) ;
206+
207+ if ( rows . length === 0 ) {
208+ ctx . status = 404 ;
209+ ctx . body = { message : 'File not found or not accessible' } ;
210+ return ;
211+ }
212+
213+ const file = rows [ 0 ] ;
214+
215+ let fileLocation = file . real_file_location ;
216+ // 根据查询参数 'type' 决定返回原图或缩略图
217+ if ( file . is_thumb && type === 'thumb' ) {
218+ fileLocation = file . real_file_thumb_location ;
219+ }
220+
221+ // 检查文件是否存在
222+ if ( ! fs . existsSync ( fileLocation ) ) {
223+ ctx . status = 404 ;
224+ ctx . body = { message : 'File not found' } ;
225+ return ;
226+ }
227+
228+ // 设置响应头
229+ ctx . set ( 'Content-Type' , getMimeType ( fileLocation ) ) ;
230+ ctx . set ( 'Content-Disposition' , `inline; filename="${ file . filename } "` ) ;
231+
232+ // 返回文件流
233+ ctx . body = fs . createReadStream ( fileLocation ) ;
234+ } catch ( error ) {
235+ ctx . status = 500 ;
236+ ctx . body = { message : 'Internal server error' , error : error . message } ;
237+ } finally {
238+ connection . release ( ) ; // 释放连接
239+ }
240+ } ) ;
241+
167242app . use ( router . routes ( ) ) . use ( router . allowedMethods ( ) ) ;
168243
169244app . listen ( process . env . SERVER_PORT , async ( ) => {
0 commit comments