很久很久没有提笔写货色了,也意味着很久很久没有瞎折腾 Copy 大法了。我是谁?我是谁并不重要,江湖必定没有 Copy 攻城狮的传说,不过,兴许这是一篇真情露出的踩坑文。以前,据说过“If I have seen further,it is by standing on the shoulders of giants.”,而此刻我正站在Ryan Dahl 和 乂乂又又的肩膀上,体验万物皆可 Serverless 的 Serverless Deno ,从零到一开(kao)发(bei)然并卵的铝盆友彩虹屁 bot(目前仅仅是定时发送邮件)。
伪需要剖析
- 最好的恋情就是我知 TA 冷暖,我懂 TA 情意 —— 定时天气预报外加心灵鸡汤;
- 最好的陪伴就是安心地和 TA 一起倒数最重要的日子 —— 倒计时揭示;
- 最好的情绪就是 TA 每天第一次睁开眼睛看到的是我的问候,夕阳下在我的晚安声中进入梦乡 —— 早安晚安问候;
- 当然最重要的是学习理解一下陈腐事物,比方 Deno、比方 Serverless。
实现构想
- 缘起于大佬的创意和代码实现,所以代码不必思考太多,照搬就行;
- 邮箱服务间接 Github 搜一波,当初的年轻人不讲武德,什么数据库、秘钥、邮箱账号密码、公司我的项目源码等等统统一股脑丢到 Github,我也想康康(不晓得会不会喝茶);
- 再想下代码实现,波及到日期工夫计算、邮件发送,是不是得找个巨佬的肩垫垫脚?插件拿过去就是刚!
- 怎么部署呢?略微比照了一下,就鹅厂云了,如同几个月前就反对 Deno 部署了,应该比拟成熟(没想到还是栽坑里了)。
- 最初, Just Do IT!
热气腾腾
看小标题是不是猜到什么恶心的货色了?是的,正是在下!本大狮,历经九九八十一分钟(理论折腾了一宿,次要卡在 Serverless 局部了),翻阅了多处 API 文档,几经挫折之后,具备辣眼睛的陈腐代码进去了:
/** Copyer huqi* https://github.com/hu-qi*/import * as log from "https://deno.land/std@0.79.0/log/mod.ts";import { SmtpClient } from "https://deno.land/x/smtp/mod.ts";import { differenceInDays, format,} from "https://deno.land/x/date_fns@v2.15.0/index.js";import { zhCN } from "https://deno.land/x/date_fns@v2.15.0/locale/index.js";import "https://deno.land/x/dotenv/load.ts";// 很随便的入参,来自.envconst { SEND_EMAIL, PASSWORD, RECV_EMAIL, NAME_GIRL, CITY, CUTDOWNDATE, CUTDOWNTHINGS,} = Deno.env.toObject();// 很随便的API,来自掘金const URL = { weather: `http://wthrcdn.etouch.cn/weather_mini?city=${CITY}`, soup: "https://www.iowen.cn/jitang/api/", pi: "https://chp.shadiao.app/api.php",};// 先配置下邮箱服务,管他行不行const client = new SmtpClient();const connectConfig: any = { hostname: "smtp.163.com", port: 25, username: SEND_EMAIL, password: PASSWORD,};// 权且认为返回的都是构造数据async function _html(url: string): Promise<string> { return await (await fetch(url)).text();}// 指标城市的天气async function getWeather(url: string) { let data = await _html(url); if (data.indexOf("OK") > -1) { let _data = JSON.parse(data).data; const { ganmao, wendu, forecast } = _data; const weather = forecast[0].type; return `天气:${weather} 以后温度:${wendu} ${ganmao}`; } else { return "敬爱的,今天天气真微妙!"; }}// 倒计时function getTime() { const today = format(new Date(), "PPPP", { locale: zhCN }); const days = differenceInDays(new Date(CUTDOWNDATE), new Date()); return `明天是 ${today} ${CUTDOWNTHINGS}倒计时:${days}天`;}// 心灵鸡汤async function getSoup(url: string) { let data = await _html(url); if (data.indexOf("数据获取胜利") > -1) { let _data = JSON.parse(data).data; const { content } = _data.content; return content; } else { return `高考在昨天,${CUTDOWNTHINGS}在今天,明天没有什么事儿!`; }}// 彩虹????屁?async function getPi(url: string) { let data = await _html(url); return data.length > 3 ? data : "你上辈子肯定是碳酸饮料吧,为什么我一看到你就开心的冒泡";}// 早安async function morning() { return ` <p>${getTime()}</p> <p>${await getSoup(URL.soup)} </p> <p>${await getWeather(URL.weather)} </p> <p>${await getPi(URL.pi)}</p> `;}// 晚安async function ngiht() { return ` <p>${await getSoup(URL.soup)} </p> <p>${await getPi(URL.pi)} </p> <p>晚安,${NAME_GIRL}同学,明天你也是最棒的,持续加油鸭!</p> `;}// 日期插件有点屌function getTimeX() { // 返回 “上午” 或者 “下午” return format(new Date(), "aaaa", { locale: zhCN });}// 入口函数async function main_handler() { // 邮件注释 const content = getTimeX() === "上午" ? await morning() : await ngiht(); // 邮件题目 const greeting = getTimeX() === "上午" ? `早安, ${NAME_GIRL}` : `晚安,${NAME_GIRL} `; // "及时关注可能会产生的谬误" try { await client.connect(connectConfig); await client.send({ from: SEND_EMAIL, to: RECV_EMAIL, subject: greeting, content: content, }); await client.close(); log.info("send email success"); } catch (error) { // "当初开始执行B打算", // "与其关怀程序的异样,不如多关注下身边的女孩子吧" log.error(error); log.info("Error: send email fail"); } log.info(content); return content;}// 立刻执行(宫刑?)main_handler();
不得不感叹 Deno 的生态真牛掰,想用什么插件就有什么插件,刚好满足了上边这么多需要。像这个日期库,非常丰盛,无论是日期格式化、国际化还是日期罕用的函数等等,思考得很周到,像这么好用的插件,Copy 攻城狮就别学了,我是学不会的,这辈子都不可能学会的。
冰封万里
第一个夜晚叫初夜,第一场雪叫初雪,新闻上说这几天全国很多中央迎来了初雪,我在广州也感触到了阵阵寒意,昨晚感觉像露宿街头,冬风呼呼地吹,仿佛在讥笑我弱不经吹的技术,啪啪啪地扇了我一整宿……还好,通过腾讯云工程师的指导,我如梦初醒,终于走出了“千里冰封,万里雪飘”,迎来了部署胜利的喜悦。
先说说部署 Deno 云函数大略的流程:
- 首先明确一点,腾讯云云函数和云开发 CloudBase 都反对 Deno 利用部署,通过摸索,我认为以后的这个 bot 更适宜云函数,所以咱们通过新建云函数来部署 Deno;
- 在新建云函数的时候,咱们先抉择模板函数-Deno 创立,次要是因为咱们须要官网模板提供的代码和 deno 以及 bootrap 这两个命令工具;而后不必批改,间接把模板代码下载到本地,等下咱们把大的文件如 deno 放到云函数的层外面,因为这里有个“巨坑”--官网模板代码及命令行工具总大小超过了云函数在线编辑模式所要求的 10M,所以不反对在线编辑(好比主动给咱们生成了环境和代码,然而没法间接批改);
- 鉴于这个“巨坑”,咱们想到的方法是将代码(环境)下载到本地,把大的文件作为层上传(其实把名为 deno 的文件独自作为层就够了,占了 50 多 M,也能够把一些 Deno 的依赖包再放到层了),而后把残余的文件上传到函数代码根本就能避坑;
- 还有一个“坑”,是我技术不到家,不理解云函数的相干常识,如同是这个云函数要有返回能力算调用胜利(只管调用失败也能执行入口函数,然而始终是超时的报错),经排查,加上官网模板中对于 event 触发的一系列代码就能失常调用了,
- 另外一个感觉很实用的中央就是环境变量,云函数函数配置中设置的环境变量键值对,在代码中能通过
Deno.env.toObject()
捕捉到;当然测试事件中的传参在官网模板提供的代码中也能捕捉到,这样就做到了简略的可配置,改下环境变量或者输入的事件参数,我就能给其余“铝盆友”发送暖心的邮件了,甚至还能够一次配置 10 个“铝盆友”,同时发送邮件,“爱拼才会赢”!
没图说个
为了填这些“坑”,我差点跟鹅厂的工程师怼上了,还好不是大佬的 bug,不然我也不讲武德,在大佬的倾情解说和急躁解答下,我也只能耗子尾汁,悻悻离去!我还年老的时候,江湖就有“没图说个”的传说,当初老了,幸好有云平台的工单零碎,还能和各个大厂的工程师进行“攻城狮和工程师的交换”。
为了避坑,我去掉了最初那行立刻执行的函数,退出了官网模板中的如下代码,看样纸是捕捉触发函数参数的:
// do initializeconst scf_host: string | undefined = Deno.env.get("SCF_RUNTIME_API");const scf_port: string | undefined = Deno.env.get("SCF_RUNTIME_API_PORT");const func_name: string | undefined = Deno.env.get("_HANDLER");const ready_url = `http://${scf_host}:${scf_port}/runtime/init/ready`;const event_url = `http://${scf_host}:${scf_port}/runtime/invocation/next`;const response_url = `http://${scf_host}:${scf_port}/runtime/invocation/response`;const error_url = `http://${scf_host}:${scf_port}/runtime/invocation/error`;// post ready -- finish initializationconsole.log(`post ${ready_url}`);postData(ready_url, { msg: "deno ready" }).then((data) => { console.log(`Initialize finish`);});async function processEvent(evt='') { if (evt.length === 0) { postData(error_url, {msg: "error handling event"}).then(data => { console.log(`Error response: ${data}`); }); } else { postData(response_url, {msg:`finish process event`}).then(data => { console.log(`invoke response: ${data}`); }); }}// Example POST method implementation:async function postData(url = '', data = {}) { // Default options are marked with * const response = await fetch(url, { method: 'POST', // *GET, POST, PUT, DELETE, etc. body: JSON.stringify(data) // body data type must match "Content-Type" header }); return response.text(); // parses JSON response into native JavaScript objects}while (true) { // get event // 立刻执行改判si缓 const responseEmail = await main_handler(); const response = await fetch(event_url); response.text().then(function(text) { console.log(`get event: ${text}`); processEvent(text); });}
值得提一下官网模板提供的文件,请看截图,十恶不赦的就是这个deno
文件,50 多 M 大小导致无奈敌对地批改在线代码:
此次部署能得以胜利,层这里解决得过后第一步,我的了解是大文件如 NodeJS 的 node_modules 之类的文件有必要放到层里,实践上 Deno 的依赖包也是同理,好在 Deno 依赖比拟轻量。
其次,依据官网文档“层中的文件将会增加到 /opt 目录中,此目录在函数执行期间可拜访”,咱们将启动文件稍作批改:
此外,就是咱们的“铝盆友”配置啦,入参得心应手了,看您想怎么用就怎么定义,完事了代码里接一下就 OK:
不过,最终遇到时区的问题,只能临时放一放了:
心愿各位大佬能解答一下!
最初附上Copy的代码,欢送指教: hu-qi/deno-serverless