Files
loongyan/bin/router.dart
2026-03-13 15:03:36 +08:00

166 lines
4.4 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'domain/repositories/photo_repository.dart';
import 'domain/repositories/album_repository.dart';
final albumRepository = AlbumRepository(basePath: 'data');
final photoRepository = PhotoRepository('data');
Response jsonResponse(dynamic data) {
return Response.ok(
jsonEncode(data),
headers: {'content-type': 'application/json'},
);
}
/// 创建 API v1 版本的路由
Router createApiV1Router() {
final router = Router()
..get('/', _rootHandler)
..get("/album", _listAlbumHandler)
..get("/album/<id>", _getAlbumHandler)
..get("/album/<id>/photo", _getPhotosOfAlbumHander)
..get('/photo/<id>', _getPhotoHandler)
..get('/photo/<id>/file', _getPhotoFileHandler)
..get('/echo/<message>', _echoHandler);
return router;
}
/// 创建主路由器,将 API v1 挂载到 /api/v1 路径下
Router createRouter() {
final router = Router();
// 将 API v1 路由挂载到 /api/v1 路径下
router.mount('/api/v1', createApiV1Router().call);
return router;
}
Response _rootHandler(Request req) {
return Response.ok('Hello, World!\n');
}
Response _echoHandler(Request request) {
final message = request.params['message'];
return Response.ok('$message\n');
}
Future<Response> _listAlbumHandler(Request req) async {
final albums = await albumRepository.getAllAlbums();
final albumList = albums
.map(
(a) => {
'id': a.id,
'name': a.name,
'createdAt': a.createdAt.toIso8601String(),
'updatedAt': a.updatedAt.toIso8601String(),
},
)
.toList();
return jsonResponse(albumList);
}
Future<Response> _getAlbumHandler(Request req) async {
final idParam = req.params['id'];
if (idParam == null) {
return Response.badRequest(body: 'Missing album id');
}
final id = int.tryParse(idParam);
if (id == null) {
return Response.badRequest(body: 'Invalid album id: $idParam');
}
final album = await albumRepository.getAlbumById(id);
if (album == null) {
return Response.notFound('Album not found');
}
return jsonResponse({
'id': album.id,
'name': album.name,
});
}
Future<Response> _getPhotoHandler(Request req) async {
final idParam = req.params['id'];
if (idParam == null) {
return Response.badRequest(body: 'Missing photo id');
}
final id = int.tryParse(idParam);
if (id == null) {
return Response.badRequest(body: 'Invalid photo id: $idParam');
}
final photo = await photoRepository.getPhotoById(id);
if (photo == null) {
return Response.notFound('Photo not found');
}
return jsonResponse(photo);
}
Future<Response> _getPhotoFileHandler(Request req) async {
final idParam = req.params['id'];
if (idParam == null) {
return Response.badRequest(body: 'Missing photo id');
}
final id = int.tryParse(idParam);
if (id == null) {
return Response.badRequest(body: 'Invalid photo id: $idParam');
}
final photo = await photoRepository.getPhotoById(id);
if (photo == null) {
return Response.notFound('Photo not found');
}
final filePath = photo.filePath;
final file = File(filePath);
if (!await file.exists()) {
return Response.notFound('File not found');
}
// ✅ 修复:使用 StreamController 包装,确保在客户端断开时关闭文件流
final streamController = StreamController<List<int>>();
final fileStream = file.openRead();
// 订阅文件流,并将数据转发到响应流
final subscription = fileStream.listen(
streamController.add,
onError: streamController.addError,
onDone: streamController.close,
cancelOnError: true,
);
// 当客户端取消订阅时(断开连接),取消文件流订阅并关闭文件
streamController.onCancel = () {
subscription.cancel();
return Future.value();
};
return Response.ok(
streamController.stream,
headers: {
'Content-Type': photo.mimeType,
'Content-Length': photo.fileSize.toString(),
},
);
}
Future<Response> _getPhotosOfAlbumHander(Request req) async {
final idParam = req.params['id'];
if (idParam == null) {
return Response.badRequest(body: 'Missing album id');
}
final albumId = int.tryParse(idParam);
if (albumId == null) {
return Response.badRequest(body: 'Invalid album id: $idParam');
}
final photos = await photoRepository.getPhotosByAlbumId(albumId);
return jsonResponse(photos);
}