|
@@ -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: 'YPwa7HZJvMSEG+yKVv7uqzLJMSScf/xlgxMQSALW' // 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;
|
|
|
}
|
|
|
};
|