Workers cloudflare đọc thông tin json được lưu ở r2

Workers cloudflare đọc thông tin json được lưu ở r2

Chào các bạn hôm nay mình xin phép giới thiệu 1 đoạn code đọc ghi xóa dữ liệu json với r2 : 

Lý do mình code code này với mục đích đọc các thông tin json được lưu từ r2 do r2 miễn phí 10gb mà không tận dụng thì hơi phí 

Bước 1 : mình connect đến r2 với đoạn code như sau 

..........
"assets": {
		"binding": "ASSETS",
		"directory": "./public"
	},
	"observability": {
		"enabled": true
	},
"r2_buckets": [
		{
			"binding": "R2",
			"bucket_name": "luubaiviet"
		}
	],
.............

cấu hình quá đơn giản để liên kết với r2 

Với phương châm của workers đơn giản : 

import { ipMiddleware } from './middlewares';
import { fileController } from './controlers/FileController';

// Map route với controller tương ứng
const controllers: Record<string, any> = {
	post: fileController,
};
export default {
	async fetch(request: Request, env: any, ctx: any): Promise<Response> {
		const url = new URL(request.url); // Lấy đối tượng URL
		const parts: string[] = url.pathname.split("/").filter(Boolean); // Tách phần đường dẫn thành mảng
		// VD: /post/123 => ["post", "123"]
		if (parts.length < 2) {
			return new Response("Invalid request", { status: 400 });
		}

		const entity = parts[0]; // VD: "user", "post", "product"
		const itemId = parts[1]; // ID của user, post, product

		// Kiểm tra controller có tồn tại không
		const controller = controllers[entity];
		if (!controller) {
			return new Response("Not Found", { status: 404 });
		}

		// Áp dụng middleware chỉ cho đường dẫn /post/*
		const middlewareResponse = await ipMiddleware(request, env);
		if (middlewareResponse) return middlewareResponse;
		// Xử lý theo HTTP method
		switch (request.method) {
			case "GET":
				return controller.read(env, itemId);

			case "DELETE":
				return controller.delete(env, itemId);

			case "POST":
				if (!request.headers.get("Content-Type")?.includes("application/json")) {
					return new Response("Content-Type must be application/json", { status: 400 });
				}
				try {
					const requestData = await request.json();
					return controller.save(env, itemId, requestData);
				} catch {
					return new Response("Invalid JSON body", { status: 400 });
				}

			default:
				return new Response("Method Not Allowed", { status: 405 });

		}

	},
} satisfies ExportedHandler<Env>;

Trong file : \src\controlers\FileController.ts 

import { CacheService } from '../services/CacheService';

interface Env {
	R2: {
		put: (key: string, value: string, options?: { httpMetadata?: { contentType: string } }) => Promise<void>;
		get: (key: string) => Promise<{ text: () => Promise<string> } | null>;
		delete: (key: string) => Promise<void>;
	};
}

const allowedOrigins = [
	"https://truyenvideo.com",
	"http://127.0.0.1",
];

// Hàm lấy header CORS động
function getCorsHeaders(request?: Request) {
	const origin = request?.headers.get("Origin") || "";
	const corsOrigin = allowedOrigins.includes(origin) ? origin : "";
	console.log("🌐 Origin:", origin);
	return {
		"Content-Type": "application/json",
		// "Access-Control-Allow-Origin": corsOrigin,
		// dùng như này vì Access-Control-Allow-Origin chỉ chấp nhận một giá trị string hoặc * (tất cả các origin), nhưng không hỗ trợ mảng.
		// "Access-Control-Allow-Origin": allowedOrigins.includes(origin ?? "") ? origin : "",
		// "Access-Control-Allow-Origin": "*.truyenvideo.com",
		"Access-Control-Allow-Origin": "*",
		"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
		"Access-Control-Allow-Headers": "Content-Type",
	};
}

export const fileController = {
	// Ghi dữ liệu vào R2 (Lưu bài viết)
	async save(env: Env, fileName: string, data: unknown, request?: Request): Promise<Response> {
		const fileKey = `articles/${fileName}.json`;
		const jsonData = JSON.stringify(data);

		await env.R2.put(fileKey, jsonData, {
			httpMetadata: { contentType: "application/json" },
		});

		return new Response("File saved successfully!", {
			status: 200,
			headers: getCorsHeaders(request),
		});
	},

	// Đọc dữ liệu từ R2
	async read(env: Env, fileName: string, request?: Request): Promise<Response> {
		const cacheKey = `file_${fileName}`;

		// 🔍 Kiểm tra cache trước
		const cachedResponse = await CacheService.get(cacheKey);
		if (cachedResponse) {
			console.log("✅ Cache hit:", cacheKey);
			return new Response(JSON.stringify(cachedResponse), {
				status: 200,
				headers: getCorsHeaders(request),
			});
		}

		console.log("⚡ Fetching from R2...");
		const fileKey = `articles/${fileName}.json`;
		const object = await env.R2.get(fileKey);
		if (!object) return new Response("File not found", { status: 404 });

		// Đọc nội dung file từ R2
		const content = await object.text();

		try {
			const parsedContent = JSON.parse(content);
			// 💾 Lưu vào cache dưới dạng object JSON
			await CacheService.set(cacheKey, parsedContent);
			return new Response(content, {
				status: 200,
				headers: getCorsHeaders(request),
			});
		} catch (error) {
			return new Response("Invalid JSON format", { status: 500 });
		}
	},

	// Xóa cache file
	async deletecache(env: Env, fileName: string, request?: Request): Promise<Response> {
		const cacheKey = `file_${fileName}`;
		await CacheService.delete(cacheKey);
		return new Response("Cache deleted successfully!", {
			status: 200,
			headers: getCorsHeaders(request),
		});
	},

	// Xóa file trong R2
	async delete(env: Env, fileName: string, request?: Request): Promise<Response> {
		const fileKey = `articles/${fileName}.json`;
		await env.R2.delete(fileKey);
		return new Response("File deleted successfully!", {
			status: 200,
			headers: getCorsHeaders(request),
		});
	}
};

Ở đây mình dùng cache ở 1 file : src\services\CacheService.ts

export const CacheService = {
	async get<T = unknown>(cacheKey: string): Promise<T | null> {
		const cache = caches.default;
		const request = new Request(`https://cache.local/${cacheKey}`); // Fake URL hợp lệ
		const response = await cache.match(request);
		if (!response) return null;
		return await response.json() as T;
	},

	// Mặc định là 5 phút (300 giây)
	async set<T>(cacheKey: string, data: T, maxAge: number = 300): Promise<Response> {
		const request = new Request(`https://cache.local/${cacheKey}`); // Fake URL hợp lệ

		const response = new Response(JSON.stringify(data), {
			status: 200,
			headers: {
				"Content-Type": "application/json",
				"Cache-Control": `s-maxage=${maxAge}`,
			},
		});

		const cache = caches.default;
		await cache.put(request, response.clone());

		return response;
	},

	async delete(cacheKey: string): Promise<boolean> {
		const cache = caches.default;
		const request = new Request(`https://cache.local/${cacheKey}`); // Fake URL hợp lệ
		return await cache.delete(request);
	},
};

Trong file src\middlewares.ts

const ALLOWED_DOMAINS = [
	"https://example.com",
	"https://admin.example.com",
	"http://localhost",          // Cho phép gọi từ localhost
	"http://127.0.0.1",          // Một số trình duyệt sử dụng 127.0.0.1 thay vì localhost
	"https://truyenvideo.com",   // Cho phép từ truyenvideo.com
	"https://www.truyenvideo.com",// Nếu có subdomain www
	"*",			// Cho phép từ mọi domain
	"https://dash.cloudflare.com",// Cho phép từ Cloudflare Dashboard
];
const ALLOWED_IPS = ["*", "113.190.232.233", "203.0.113.42"]; // Danh sách IP cho phép

// Middleware kiểm tra domain (CORS)
export async function domainMiddleware(request: any, env: any) {
	const origin = request.headers.get("Origin");
	console.log("Origin:", origin);
	// Nếu cho phép tất cả domain, bỏ qua kiểm tra
	if (ALLOWED_DOMAINS.includes("*")) {
		return null;
	}
	// Nếu Origin không hợp lệ, chặn request
	if (!ALLOWED_DOMAINS.includes(origin)) {
		return new Response("Forbidden: Invalid Domain", { status: 403 });
	}
	return null; // Tiếp tục xử lý
}

// Middleware kiểm tra IP
export async function ipMiddleware(request: any, env: any) {
	const ip = request.headers.get("CF-Connecting-IP"); // Lấy IP từ Cloudflare
	if (ALLOWED_IPS.includes("*")) {
		return null;
	}

	// Nếu IP không hợp lệ, chặn request
	if (!ip || !ALLOWED_IPS.includes(ip)) {
		return new Response("Forbidden: Invalid IP", { status: 403 });
	}
	return null; // Tiếp tục xử lý
}