ソースを参照

实现ezbuy需求

世祺 1 年間 前
コミット
07ed6302e2
5 ファイル変更134 行追加287 行削除
  1. 0 0
      .idea/ezbuy.iml
  2. 1 1
      .idea/modules.xml
  3. 1 1
      package.json
  4. 131 284
      src/worker.js
  5. 1 1
      wrangler.toml

.idea/image-processing.iml → .idea/ezbuy.iml


+ 1 - 1
.idea/modules.xml

@@ -2,7 +2,7 @@
 <project version="4">
   <component name="ProjectModuleManager">
     <modules>
-      <module fileurl="file://$PROJECT_DIR$/.idea/image-processing.iml" filepath="$PROJECT_DIR$/.idea/image-processing.iml" />
+      <module fileurl="file://$PROJECT_DIR$/.idea/ezbuy.iml" filepath="$PROJECT_DIR$/.idea/ezbuy.iml" />
     </modules>
   </component>
 </project>

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "image-processing",
+  "name": "ezbuy-image-processing",
   "version": "0.0.0",
   "private": true,
   "scripts": {

+ 131 - 284
src/worker.js

@@ -1,301 +1,148 @@
-import { AwsClient } from 'aws4fetch';
-
+const default_host = 'litb-live-image.s3.amazonaws.com';
+const excludePath = /.*\/.*desc_images\/.*/;
 const sizeOf = require('image-size');
-
-const aws = new AwsClient({
-	accessKeyId: 'AKIAYP36OB6MRRIOTBFV',     // required, akin to AWS_ACCESS_KEY_ID
-	secretAccessKey: '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' // required, akin to AWS_SECRET_ACCESS_KEY
-	// sessionToken,    // akin to AWS_SESSION_TOKEN if using temp credentials
-	// service,         // AWS service, by default parsed at fetch time
-	// region: "ap-southeast-1",          // AWS region, by default parsed at fetch time
-	// cache,           // credential cache, defaults to `new Map()`
-	// retries,         // number of retries before giving up, defaults to 10, set to 0 for no retrying
-	// initRetryMs,     // defaults to 50 – timeout doubles each retry
-});
-const globalConfiguration = {
-	aws_base_url: 'plat-sg-cloudflare-testing.s3.ap-southeast-1.amazonaws.com',
-	self_host: 'cf-test.hoyoverse.com',
-	text_to_image_url: 'https://text-to-image.hoyoverse.workers.dev/',
-	watermark_fg: 'fff',
-	watermark_bg: '000',
-	watermark_shadow_offset: 2
-};
-
-
-async function build_request(request) {
-	let dstUrl = new URL(request.url);
-
-	dstUrl.host = request.headers.has('x-host') ? request.headers.get('x-host') : globalConfiguration.aws_base_url;
-	dstUrl.search = '';
-	// dstUrl.pathname = url.pathname.substring('/imgprocessingtest'.length)
-
-
-	let req = await aws.sign(dstUrl.toString());
-	dstUrl.host = globalConfiguration.self_host;
-	return new Request(dstUrl.toString(), { headers: req.headers });
-}
-
-async function getDimensions(req) {
-	// return {
-	// 	width: 4096,
-	// 	height: 4096
-	// };
-	let response = await fetch(req);
-	let buffer = await response.arrayBuffer();
-	return sizeOf(Buffer.from(buffer));
-
-}
-
-function parseArg(arg, default_conf) {
-	let str_arg = arg.reduce((obj, str) => {
-		let pair = str.split('_');
-		if (pair.length === 2) {
-			obj[pair[0]] = pair[1];
-		}
-		return obj;
-	}, {});
-	if (!default_conf) return str_arg;
-
-	let conf_raw = { ...default_conf, ...str_arg };
-
-	let numberFields = Object.keys(default_conf).filter(k => typeof default_conf[k] === 'number');
-
-	return numberFields.reduce((obj, field) => {
-		obj[field] = Number(obj[field]);
-		return obj;
-	}, conf_raw);
-
-}
-
-async function parseWaterMark(arg, ctx) {
-	let { req } = ctx;
-	console.log('parseWaterMark');
-	const default_conf = {
-		t: 100,//透明度。[0,100]
-		g: 'se',//位置。nw:左上north:中上ne:右上west:左中center:中部east:右中sw:左下south:中下se
-		x: 10,//水平边距。[0,4096]
-		y: 10,//垂直边距。[0,4096]
-		// voffset: 0,//中线垂直偏移。[-1000,1000]
-		// fill: 0,//1:将图片水印或文字水印铺满原图。0(默认值):不将图片水印或文字水印铺满全图。
-		// padx: 0,//水印平铺时单个水印间的水平间隔。仅在水印平铺开启时有效。[0,4096]
-		// pady: 0,//水印平铺时单个水印间的垂直间隔。仅在水印平铺开启时有效。[0,4096]
-
-		//text:'required'
-		// type: 'ZHJvaWRzYW5zZmFsbGJhY2s',//字体,默认DroidSansFallback
-		color: '000000',//水印的文字颜色
-		size: 40,//文字大小
-		shadow: 0,//阴影透明度
-		rotate: 0//字顺时针旋转角度
-	};
-
-
-	let conf = parseArg(arg, default_conf);
-
-	if (!conf.text || conf.t === 0) return {};
-
-	let base = {};
-
-	if (['nw', 'west', 'sw'].includes(conf.g)) {
-		base.left = conf.x;
-	} else if (['ne', 'east', 'se'].includes(conf.g)) {
-		base.right = conf.x;
-	}
-
-	if (['nw', 'north', 'ne'].includes(conf.g)) {
-		base.top = conf.y;
-	} else if (['sw', 'south', 'se'].includes(conf.g)) {
-		base.bottom = conf.y;
-	}
-	if (conf.rotate / 90 % 2) base.width = conf.size * 0.9;
-	else base.height = conf.size * 0.9;
-	return {
-		draw: [{
-			url: `${globalConfiguration.text_to_image_url}${conf.text}?size=${conf.size * 2}&color=${conf.color}&shadow=${conf.shadow}&offset=${globalConfiguration.watermark_shadow_offset * 2}`,
-			...base,
-			opacity: conf.t / 100,
-			rotate: conf.rotate
-		}]
-	};
-}
-
-async function parseResize(arg, { req }) {
-	console.log('parseResize');
-	const default_conf = {
-		m: 'lfit',//缩放的模式。lfit、mfit、fill、pad、fixed
-		w: 0,//宽度。[1,16384]
-		h: 0,//高度。
-		l: 0,//指定目标缩放图的最长边。
-		s: 0,//指定目标缩放图的最短边。
-		limit: 1,//当目标图片分辨率大于原图分辨率时,是否进行缩放。
-		color: 'FFFFFF',//当缩放模式选择为pad(缩放填充)时,可以设置填充的颜色。
-		p: 0//按百分比缩放图片。
-	};
-	let conf = parseArg(arg, default_conf);
-	if (conf.w || conf.h || conf.l || conf.s) {
-		let base = {
-			width: conf.w === 0 ? undefined : conf.w,
-			height: conf.h === 0 ? undefined : conf.h,
-			fit: 'scale-down'
-		};
-
-		switch (conf.m) {
-			case 'lfit':
-				base.fit = conf.limit === 1 ? 'scale-down' : 'contain';
-				break;
-			case 'mfit':
-			case 'fill':
-				base.fit = conf.limit === 1 ? 'crop' : 'contain';
-				break;
-			case 'pad':
-				base.fit = 'pad';
-				base.background = conf.color;
-				break;
-			default:
-				break;
-		}
-
-		if (conf.l || conf.s) {
-			let dimensions = await getDimensions(req);
-			if (conf.s) {
-				if (dimensions.width < dimensions.height) {
-					base.width = conf.s;
-					base.height = undefined;
-				} else {
-					base.height = conf.s;
-					base.width = undefined;
-				}
-			} else {
-				if (dimensions.width < dimensions.height) {
-					base.height = conf.l;
-					base.width = undefined;
-				} else {
-					base.width = conf.l;
-					base.height = undefined;
-				}
-			}
-		}
-		return base;
-	}
-	if (conf.p) {
-		let dimensions = await getDimensions(req);
-		return {
-			fit: 'scale-down',
-			height: Math.round(dimensions.height * Number(conf.p) / 100)
-		};
-	}
-	return {};
-}
-
-async function parseQuality(arg, ctx) {
-	console.log('parseQuality');
-	let conf = parseArg(arg);
-	return {
-		quality: conf.q
-	};
-}
-
-async function parseFormat(args, ctx) {
-	console.log('parseFormat');
-	switch (args[0]) {
-		case 'jpg':
-			return { format: 'jpeg' };
-		case 'gif':
-			return {};
-		default:
-			return { format: args[0] };
-	}
-}
-
-async function parseCrop(arg, ctx) {
-	console.log('parseCrop');
-	const default_conf = {
-		w: 0,//宽度。
-		h: 0,//高度。
-		x: 0,//指定裁剪起点横坐标(默认左上角为原点)。
-		y: 0,//指定裁剪起点纵坐标(默认左上角为原点)。
-		g: 'nw'//裁剪原点位置。nw:左上,north:中上,ne:右上,west:左中,center:中部,east:右中,sw:左下,south:中下,se:右下
-	};
-	let conf = parseArg(arg, default_conf);
-	return {
-		trim: {
-			width: conf.w,
-			height: conf.h,
-			left: conf.x,
-			top: conf.y
-		}
-	};
-}
-
-const processMap = {
-	'watermark': parseWaterMark,
-	'resize': parseResize,
-	'quality': parseQuality,
-	'format': parseFormat,
-	'crop': parseCrop
-};
-
 export default {
 	async fetch(request, _env, _ctx) {
 
-		let req = await build_request(request);
-
-
 		if (/image-resizing/.test(request.headers.get('Via'))) {
-			return fetch(req);
+			return fetch(request);
 		}
+		let image = {};
+		let url = new URL(request.url);
 
+		let path = url.pathname;
 
-		let process_arg = [];
-		let url = new URL(request.url);
-		for (const [key, value] of url.searchParams) {
-			if (key === 'x-oss-process') {
-				let processes_raw = value.split('/');
-				process_arg = processes_raw.map(process => process.split(','));
-			}
-		}
+		let newUrl = new URL(request.url);
 
-		//无image参数,返回原图
-		if (!process_arg.some(processes => processes[0] === 'image')) {
-			return await fetch(req);
-		}
+		let reg3 = [/\/images\/middle(\/.*)$/, /\/images\/small(\/.*)$/, /\/images\/l(\/.*)$/, /\/images\/f(\/.*)$/, /\/images\/m(\/.*)$/, /\/images\/p(\/.*)$/, /\/images\/s(\/.*)$/];
 
-		//过滤不可用选项
-		process_arg = process_arg.filter(process => !!processMap[process[0]]);
-		let ctx = { req, process_arg };
-		//解析参数并构造图像变换选项
-		let image = (await Promise.all(
-				process_arg.map(process => {
-					let process_func = processMap[process[0]];
-					return process_func(process.slice(1), ctx);
-				}))
-		).reduce((a, b) => ({ ...a, ...b }), {});
-		//Crop与Resize综合处理
-		let resize_index = ctx.process_arg.findIndex(item => item[0] === 'resize');
-		let crop_index = ctx.process_arg.findIndex(item => item[0] === 'crop');
-		if (resize_index !== -1 && crop_index !== -1) {
-			let resize_conf = parseArg(ctx.process_arg[resize_index], { p: 0 });
-			if (resize_conf.p) {
-				if (resize_index < crop_index) {
-					image.height = image.trim.height;
-					Object.keys(image.trim).forEach(key => image.trim[key] /= resize_conf.p / 100);
-				} else {
-					image.height = image.trim.height * resize_conf.p / 100;
-				}
-			}
-		}
+		let func = (reg, width, height, tp, wp = null, hp = null) => {
+			newUrl.host = 'd1453ta0frklga.cloudfront.net';
+			let match = path.match(reg);
 
-		console.log('image:', JSON.stringify(image));
+			if (wp) width = Number(match[wp]);
+			if (hp) height = Number(match[hp]);
+			let suffix = (width >= 500 || height >= 500) ? '_1500' : '';
+			let tail = match[tp];
+			image = {
+				height,
+				width,
+				fit: 'pad'
+			};
+			let index = tail.lastIndexOf('.');
+			newUrl.pathname = tail.slice(0, index) + suffix + tail.slice(index);
+		};
 
 
-		//获取返回经过处理的图片
-		let originalResponse = await fetch(req, {
+		if (/\/images\/(\d+)x(\d+)(\/.*)$/.test(path) && (!excludePath.test(path))) {
+
+			func(/\/images\/(\d+)x(\d+)(\/.*)$/, null, null, 3, 1, 2);
+
+		} else if (/\/images\/x(\/.*)$/.test(path) && (!excludePath.test(path))) {
+
+			func(/\/images\/x(\/.*)$/, 1140, 1500, 1);
+
+		} else if (reg3.some(reg => reg.test(path)) && (!excludePath.test(path))) {
+
+			let regExp = reg3.find(reg => reg.test(path));
+			func(regExp, 280, 280, 1);
+
+		} else if (/\/images\/v(\/.*)$/.test(path) && (!excludePath.test(path))) {
+
+			func(/\/images\/v(\/.*)$/, 500, 500, 1);
+
+		} else if (/\/images\/\?(\/.*)$/.test(url.pathname + url.search) && (!excludePath.test(url.pathname + url.search))) {
+			let match = (url.pathname + url.search).match(/\/images\/\?(\/.*)$/);
+
+			let height = 500;
+			let width = 380;
+			let suffix = (width >= 500 || height >= 500) ? '_1500' : '';
+			let tail = match[1];
+
+			image = {
+				height,
+				width,
+				fit: 'pad'
+			};
+
+			let index = tail.lastIndexOf('.');
+
+			newUrl.host = 'd1453ta0frklga.cloudfront.net';
+			newUrl.pathname = tail.slice(0, index) + suffix + tail.slice(index);
+			newUrl.search = '';
+		} else if (/\/images(_litb)?(_mini|_ouku)?\/review(_new)?\/(\d+)x(\d+)(\/.*)$/.test(path)) {
+			let match = path.match(/\/images(_litb)?(_mini|_ouku)?\/review(_new)?\/(\d+)x(\d+)(\/.*)$/);
+
+			let width = Number(match[4]);
+			let height = Number(match[5]);
+			let suffix = (width >= 500 || height >= 500) ? '_1500' : '';
+			let tail = match[6];
+			image = {
+				height,
+				width,
+				fit: 'pad'
+			};
+			let index = tail.lastIndexOf('.');
+			newUrl.pathname = '/images' + (match[2] ? match[2] : '') + '/review/mobile' + tail.slice(0, index) + suffix + tail.slice(index);
+			newUrl.host = 'dlvvnch56i9xa.cloudfront.net';
+
+		} else if (/\/images\/(dfp|brand|wholesale)(\/.*)$/.test(path)) {
+			let match = url.pathname.match(/\/images\/(dfp|brand|wholesale)(\/.*)$/);
+			newUrl.host = 'dlvvnch56i9xa.cloudfront.net';
+			newUrl.pathname = '/images/' + match[1] + match[2];
+
+		} else if (/\/images\/cust_img_bg(\/.*)$/.test(path)) {
+			let match = url.pathname.match(/\/images\/cust_img_bg(\/.*)$/);
+			newUrl.host = url.host;
+			newUrl.pathname = '/cust_img_bg' + match[1];
+		} else if (/\/(webp_)?desc_image(\/.*)$/.test(path)) {
+			let match = url.pathname.match(/\/(webp_)?desc_image(\/.*)$/);
+			newUrl.host = 'dlvvnch56i9xa.cloudfront.net';
+			newUrl.pathname = '/desc_image' + match[2];
+			console.log(newUrl);
+			let response = await fetch(newUrl);
+
+			let buffer = await response.arrayBuffer();
+			let size = sizeOf(Buffer.from(buffer));
+			let largeSize = size.width > 6000 || size.height > 6000;
+			let requireWebp = url.searchParams.get('fmt') === 'webp';
+			console.log(size.width,"x",size.height);
+			console.log(requireWebp);
+			if (largeSize && requireWebp) {
+				return await fetch(newUrl, {
+					cf: {
+						image: {
+							height: 1500,
+							width: 1500,
+							fit: 'pad',
+							format: 'webp'
+						}
+					}
+				});
+			} else if (largeSize && !requireWebp) {
+				return await fetch(newUrl, {
+					cf: {
+						image: {
+							height: 1500,
+							width: 1500,
+							fit: 'pad'
+						}
+					}
+				});
+			} else if (!largeSize && requireWebp) {
+				return await fetch(newUrl, {
+					cf: {
+						image: {
+							format: 'webp'
+						}
+					}
+				});
+			} else return await fetch(newUrl);
+		}
+		console.log(newUrl);
+		console.log(image);
+		return await fetch(newUrl, {
 			cf: { image }
 		});
-
-		//GIF图片过大,处理失败,返回原图
-		if ((request.url.includes('.gif')) && (originalResponse.headers.get('cf-resized') === 'err=9413')) {
-			return await fetch(req);
-		}
-		return originalResponse;
 	}
 };

+ 1 - 1
wrangler.toml

@@ -1,4 +1,4 @@
-name = "image-processing"
+name = "ezbuy-image-processing"
 main = "src/worker.js"
 compatibility_date = "2023-08-21"
 node_compat = true