如何制作一个发送每日一图的小工具?

前言

之前看到一个博客,介绍了nodemailer库,使用nodemailer可以给自己发送每日一言。我突发奇想,是不是可以每天给自己和其他小伙伴发送每日一图呢?说干就干,每日一图的主要难点在以下四部分:

  • 图片如何获取
  • 如何存储图片
  • 如何将图片地址存入数据库,以供每日读取
  • 如何可配置的给目标邮箱每日发送图片

因为我不希望在以后打开邮件看的时候会出现图片404的情况,所以不打算使用现成的随机图片的接口或者使用三方的图床用于图片存储,所以我们从头获取图片,存储到自己图床、将图片地址写入数据库、每天读取数据库的数据发送给指定邮箱。

友情提醒,我学习JavaScript为主,以下所有代码均为nodejs,可以通过别的语言达成同样效果,js程序员可以直接使用我的代码,其余语言可以借鉴一下思路。

第一步:获取图片

此处我使用了两种方法来下载图片:通过现成的随机图片接口和下载Pixiv上的图片。

方法一:使用随机图片接口

最开始,我接触到一些随机的图片接口,每次调用都能返回随机的图片,我就写了一个小程序反复调用这类接口,然后直接上传至OSS(OSS将在下面提及,如果使用此方法请自行替换OSS相关参数),因为采用数字命名,导入数据库非常方便,当然你可以将地址直接存入数据库,但是怕网速限制或者后面资源不在的情况,我选择转存一次。以下是nodejs代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const https = require('https');
const axios = require('axios');
const oss = require('ali-oss');

const client = oss({//设置OSS参数
accessKeyId: '[OSS accessKeyId]',
accessKeySecret: '[OSS accessKeySecret]',
bucket: '[OSS bucket]',
region: '[OSS region]'
});

(async function begin() {
let re = await axios.post('https://api.lolicon.app/setu/v2', {num: 5})
let array = re.data.data.map(i => i.urls.original)
console.log(array)
for (let i = 0; i < 5; i++) {
let res = await axios.get(array[i], {responseType: 'arraybuffer'})
let file = res.data
let result = await client.put(`/PLMM/${i + 1}.png`, file)
console.log(`第${i + 1}张上传成功`)
}
})()

PS:此处使用了Tsuk1ko (神代綺凛) 大佬的随机图片接口,具体接口参数可以参考这里

方法二:从Pixiv网站获取好看的插图

Pixiv是个优秀的插画分享网站,上面有很多优秀的插画可供查看下载,当然一张一张下载肯定不符合程序员的逻辑,我使用了pxder这个工具,这个工具的好处在于你登录过后可以一键下载你关注的画师的所有作品以及你收藏的所有插画作品,而且它通过多线程进行下载,速度还是非常快的(这个工具也是上面那个大佬写的)。

第二步:存储图片

之前提到,我不想在以后无法访问图片,所以,我们需要使用自己的图床,此处我使用的是阿里云的OSS服务。如果第一次接触OSS的朋友可以在这里查看搭建方法或者查看官方文档,我在这里不过多赘述了。

使用官方提供的OSS图形化管理工具ossbrowser上传我们整个图片文件夹

image-20220617144939569

第三步:将图片链接存储至数据库

之前提到,我们将整个文件夹统一放至OSS,所以在OSS上文件的路径和本地路径是一致的,我们只需读取出本地的图片路径再将基地址替换成OSS地址就可以获取到所有图片的地址了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const fs = require('fs')
const path = require('path')
function getFiles(dirname) {
fs.readdir(dirname, {withFileTypes: true}, (err, files) => {
files.forEach(file => {
const pathName = path.resolve(dirname, file.name)
if (file.isDirectory()) {
getFiles(pathName)
} else {
if(['.png','.jpg'].includes(path.extname(pathName))){
let originPath=pathName.replace('/Users/dell/Pictures/','[Your OSS Address]')
fs.writeFileSync('file.sql', `INSERT INTO emailurl (url) VALUES ("${originPath}");` + '\n', {flag: 'a+'})
console.log(originPath)
}
}
})
})
}

getFiles('/Users/dell/Pictures/pixiv/')

在指定数据库运行file.sql文件,就可以将所有的图片地址导入进数据库啦。

具体构建数据库结构语句如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for emailconfig
-- ----------------------------
DROP TABLE IF EXISTS `emailconfig`;
CREATE TABLE `emailconfig` (
`id` int(3) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`value` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of emailconfig
-- ----------------------------
INSERT INTO `emailconfig` VALUES (1, 'alreadyNo', '0');
INSERT INTO `emailconfig` VALUES (2, 'pageSize', '5');

-- ----------------------------
-- Table structure for emailurl
-- ----------------------------
DROP TABLE IF EXISTS `emailurl`;
CREATE TABLE `emailurl` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of emailurl
-- ----------------------------

-- ----------------------------
-- Table structure for emailuser
-- ----------------------------
DROP TABLE IF EXISTS `emailuser`;
CREATE TABLE `emailuser` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`emailAddress` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`del_flag` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
INDEX `id`(`id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of emailuser
-- ----------------------------

SET FOREIGN_KEY_CHECKS = 1;

你可以在用户表配置要发送给的用户列表和对他们的称呼,并在配置表里配置当前发送的位置和每天需要发送的图片数量。

第四步:设置定时任务,发送每日一图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
const mysql = require('mysql');
const nodemailer = require("nodemailer")
const schedule = require("node-schedule")
const config = {
host: '[database url]',//mysql数据库域名/ip
port: '3306',//mysql数据库端口,默认3306
user: '[database user]',//数据库用户
password: '[database password]',//数据库密码
database: '[database name]',//数据库名称
connectTimeout: 5000, //连接超时
multipleStatements: false //是否允许一个query中包含多条sql语句
}

const user = "[send user]"//发件人邮箱
const pass = "[send user pass]"//发件人授权码

//定义发件人
const transporter = nodemailer.createTransport({
host: "smtp.qq.com",
port: 587,
secure: false,
auth: {
user: user,
pass: pass
},
});


async function sendMail() {
//定义数据库连接
let connection = mysql.createConnection(config);
connection.connect();

//封装数据库请求方法
function asyncSql(sql) {
return new Promise(resolve => {
connection.query(sql, (err, result) => {
if (err) throw err
resolve(result)
})
})
}

//查询需要发送的用户
const users = await asyncSql('SELECT * from EmailUser where del_flag=0')
//获取发送条数和开始位置
const emailConfig = await asyncSql('select * from emailconfig')
let alreadyNo = +emailConfig.find(i => i.name === 'alreadyNo').value
let pageSize = +emailConfig.find(i => i.name === 'pageSize').value
//获取将要发送的图片地址
const paths = await asyncSql(`select * from emailurl where id Between ${+alreadyNo} AND ${+alreadyNo+pageSize-1}`)
let imgs = ''
paths.forEach(i => imgs += `<img src="${i.url}" alt="">\n`)
//更新开始位置
await asyncSql(`UPDATE emailconfig SET value = ${alreadyNo+pageSize+''} WHERE name='alreadyNo'`)
//遍历查询到的用户列表发送邮件
users.map(async (i) => {
await transporter.sendMail({
from: `每日快乐源泉<${user}>`, // sender address
to: i.emailAddress, // list of receivers
subject: `${i.userName},今天也要元气满满哦`, // Subject line
html: `
<h2>亲爱的${i.userName},这是今日美图分享,今天也要开开心心的哦(*^▽^*)</h2>
${imgs}
<h5 style="color: #ccc">退订:退订?退订是不可能退订的,这辈子都不可能的(\`へ´*)ノ</h5>
`
});
console.log(`${new Date()}---------${i.userName}已发送`)
})
//关闭数据库连接
connection.end();
}

console.log("服务启动")
//开启每天九点半的定时任务
schedule.scheduleJob('0 30 9 * * *', () => {
sendMail()
})

将代码放到服务器上,使用forever进行node进程守护,然后在数据库配置好对应收件人信息就可以等待每天收到美美的图片啦。