3 Commits 757b8221ba ... 3f249ec233

Author SHA1 Message Date
  世祺 3f249ec233 添加README文档 10 months ago
  世祺 715a14972a 支持环境变量 10 months ago
  世祺 ce5c6d4319 支持预签名 10 months ago
4 changed files with 304 additions and 210 deletions
  1. 2 0
      .dev.vars
  2. 84 0
      README.md
  3. 214 209
      src/worker.js
  4. 4 1
      wrangler.toml

+ 2 - 0
.dev.vars

@@ -0,0 +1,2 @@
+accessKeyId = "AKIAYP36OB6MRRIOTBFV"
+secretAccessKey = "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"

+ 84 - 0
README.md

@@ -0,0 +1,84 @@
+# Workers部署文档
+
+[toc]
+
+## **环境要求**
+
+- image-processing
+
+  - node >=16.20.0
+
+  - wrangler >=3.5.0
+
+- text-to-image
+
+  - rust
+
+  - wrangler
+
+
+## **环境安装**
+
+### node 安装
+
+​	可从此处下载nvm[[GitHub - coreybutler/nvm-windows: A node.js version management utility for Windows. Ironically written in Go.](https://github.com/coreybutler/nvm-windows)]
+
+```bash
+nvm use 21
+```
+
+### wrangler 安装
+
+```bash
+npm install wrangler -g
+```
+
+### Rust安装
+
+参见此教程[入门 - Rust 程序设计语言 (rust-lang.org)](https://www.rust-lang.org/zh-CN/learn/get-started)
+
+## **安装基本依赖**
+
+- ImageProcessing
+
+    ```bash
+    npm install
+    ```
+- text-to-image
+
+	此项目在部署时会自动安装所需依赖
+
+## **[可选]更改配置文件**
+
+​	修改`wrangler.toml`文件内的`[vars]`节,以修改环境变量。以下列出了常用的配置项。
+
+| 属性 | 默认值 | 作用 |
+| ---- | ------ | ---- |
+|aws_base_url|'plat-sg-cloudflare-testing.s3.ap-southeast-1.amazonaws.com'|未使用预签名URL时,图片回源的AWS地址|
+|self_host|'cf-test.hoyoverse.com'|自身部署的Host|
+|text_to_image_url|'https://text-to-image.hoyoverse.workers.dev/'|提供文字转图片的Workers URL,仅用于添加水印|
+
+## **部署**
+
+1. 执行以下命令进行部署
+   ```bash
+   npm run deploy
+   ```
+
+2. 在弹出的网页中进行登陆(可能需要关闭VPN)
+
+3. 在CLI中选择要部署的账户
+
+4. 等待上传完成
+
+   ![image-20231122194557100](https://gitee.com/shiqiguo/figurebed/raw/master/img/image-20231122194557100.png)
+   
+5. 更新密钥
+
+   1. ```bash
+      wrangler secret put accessKeyId
+      ```
+
+   2. ```bash
+      wrangler secret put secretAccessKey
+      ```

+ 214 - 209
src/worker.js

@@ -2,240 +2,245 @@ import { AwsClient } from 'aws4fetch';
 
 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)
 
+export default {
+	async fetch(request, env, _ctx) {
+
+		const aws = new AwsClient({
+			accessKeyId: env.accessKeyId,  // required, akin to AWS_ACCESS_KEY_ID
+			secretAccessKey: env.secretAccessKey // 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: env.aws_base_url, //'plat-sg-cloudflare-testing.s3.ap-southeast-1.amazonaws.com',
+			self_host: env.self_host,//'cf-test.hoyoverse.com',
+			text_to_image_url: env.text_to_image_url,//'https://text-to-image.hoyoverse.workers.dev/',
+			watermark_fg: 'fff',
+			watermark_bg: '000',
+			watermark_shadow_offset: 2
+		};
 
-	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));
+		async function build_request(request) {
+			let dstUrl = new URL(request.url);
+			//预签名URL支持
+			if (request.headers.has('signed-url')) {
+				return new Request(request.headers.get('signed-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)
 
-}
 
-function parseArg(arg, default_conf) {
-	let str_arg = arg.reduce((obj, str) => {
-		let pair = str.split('_');
-		if (pair.length === 2) {
-			obj[pair[0].toLowerCase()] = pair[1].toLowerCase();
+			let req = await aws.sign(dstUrl.toString());
+			dstUrl.host = globalConfiguration.self_host;
+			return new Request(dstUrl.toString(), { headers: req.headers });
 		}
-		return obj;
-	}, {});
-	if (!default_conf) return str_arg;
 
-	let conf_raw = { ...default_conf, ...str_arg };
+		async function getDimensions(req) {
+			// return {
+			// 	width: 4096,
+			// 	height: 4096
+			// };
+			let response = await fetch(req);
+			let buffer = await response.arrayBuffer();
+			return sizeOf(Buffer.from(buffer));
 
-	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);
+		function parseArg(arg, default_conf) {
+			let str_arg = arg.reduce((obj, str) => {
+				let pair = str.split('_');
+				if (pair.length === 2) {
+					obj[pair[0].toLowerCase()] = pair[1].toLowerCase();
+				}
+				return obj;
+			}, {});
+			if (!default_conf) return str_arg;
 
-}
+			let conf_raw = { ...default_conf, ...str_arg };
 
-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]
+			let numberFields = Object.keys(default_conf).filter(k => typeof default_conf[k] === 'number');
 
-		//text:'required'
-		// type: 'ZHJvaWRzYW5zZmFsbGJhY2s',//字体,默认DroidSansFallback
-		color: '000000',//水印的文字颜色
-		size: 40,//文字大小
-		shadow: 0,//阴影透明度
-		rotate: 0//字顺时针旋转角度
-	};
+			return numberFields.reduce((obj, field) => {
+				obj[field] = Number(obj[field]);
+				return obj;
+			}, conf_raw);
 
+		}
 
-	let conf = parseArg(arg, default_conf);
+		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 (!conf.text || conf.t === 0) return {};
+			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
+				}]
+			};
+		}
 
-	let base = {};
+		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 (['nw', 'west', 'sw'].includes(conf.g)) {
-		base.left = conf.x;
-	} else if (['ne', 'east', 'se'].includes(conf.g)) {
-		base.right = conf.x;
-	}
+				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 {};
+		}
 
-	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'
-		};
+		async function parseQuality(arg, ctx) {
+			console.log('parseQuality');
+			let conf = parseArg(arg);
+			return {
+				quality: conf.q
+			};
+		}
 
-		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;
+		async function parseFormat(args, ctx) {
+			console.log('parseFormat');
+			switch (args[0]) {
+				case 'jpg':
+					return { format: 'jpeg' };
+				case 'gif':
+					return {};
+				default:
+					return { format: args[0] };
+			}
 		}
 
-		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;
+		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
 				}
-			}
+			};
 		}
-		return base;
-	}
-	if (conf.p) {
-		let dimensions = await getDimensions(req);
-		return {
-			fit: 'scale-down',
-			height: Math.round(dimensions.height * Number(conf.p) / 100)
+
+		const processMap = {
+			'watermark': parseWaterMark,
+			'resize': parseResize,
+			'quality': parseQuality,
+			'format': parseFormat,
+			'crop': parseCrop
 		};
-	}
-	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);
 

+ 4 - 1
wrangler.toml

@@ -6,7 +6,10 @@ node_compat = true
 # Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
 # Note: Use secrets to store sensitive data.
 # Docs: https://developers.cloudflare.com/workers/platform/environment-variables
-# [vars]
+[vars]
+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/'
 # MY_VARIABLE = "production_value"
 
 # Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.