关于我从程序员变成“韭零后“这件事【node实现邮件发送】

一、前情提要

我认识个朋友,一万元起家做股票短短一个月就赚了38万。我问他怎么赚的,他说他给别人推荐股票跌停了,被人打断了腿,对方赔的。。。⊙﹏⊙∥

  以上是段子切勿当真 ๑乛◡乛๑ ,本人作为一个纵横基场三年之久的”韭零后“(”韭韭“年的,某种意义上的绝对韭菜∑(´△`)?!),一名非常合格的韭菜了,经历了疫情期间的疯涨,还有去年(2020年)一年的暴跌,去年初买的中概互联到现在还在手上,从某种角度上达到了“共同富裕”的目的,真的是越补越跌,越跌越补!我就是不卖,只要我不卖就还没亏!凸(`△´+) 有本事清盘啊!(`へ´*)ノ(前面的都是气话,开玩笑的,千万别清盘,我还等着回本呢!ヾ(´▽`;)ゝ)

小前端

  经历了去年一年的“跌跌不休”,我决定开始了智能定投,终于在我不懈的努力下,终于迎来了!微笑曲线(* ̄︿ ̄)反向微笑,尤其是今年开年的一波,而且据我观察智能定投好像也是定期的投入,只是金额有所变动,实际上变动也不是特别大。所以在我心理一直有个想法,就是挑选几只“鸡”,然后观察每日的估值涨跌来决定买入卖出,但是不可能天天这么盯着看吧!所以决定打算用我小前端的优势,来实现这个有亿点简单的需求,正好完成上次发文说要实现的node邮件发送需求,因此用邮件来订阅每天“鸡精”的涨跌,废话不多说开始准备吧!

VsCode上号

二、“操盘”准备

  写代码前一定要先理清需求,我的需求很简单,就是订阅一些基金,并且每天监听它们的估值,符合涨跌的条件则通过邮件告知我,所以抽象一下数据,就是一个基金的列表,然后每天定时遍历它们当天的估值,最后筛选出符合条件的通过邮件告知我。

  需求理清楚了,就要关注输入输出,输出很简单,就是符合条件的数据,通过邮件告知我,而输入则需要一些网上开放的接口,通过接口获取到我需要的数据。

  1. 所以第一步便是搜索基金相关信息的API(接口)了,这里我找到了一篇博客对天天基金的接口数据说明的文章,当然厉害的大佬,可以直接去一些基金相关的网站上获取到数据接口,也很简单ヽ( ̄▽ ̄)و。
  2. 第二步,选择语言,作为一个小前端,这里必然是用node来实现目标需求,当然光选择语言肯定还不行,还需要各种大佬们发的工具包,不然纯手写造轮子会非常累(真·大佬除外 ┐(´∇`)┌)。
  3. 第三步,查找工具包,首先是发送邮件的功能,因为使用的是邮箱的smtp服务,所以这里使用nodemailernodemailer-smtp-transport,然后就是请求相关处理的,这里用axios,最后需要定时发送订阅的信息,这里定时器用node-schedule
  4. 第四步,打开VsCode开始编写代码。(什么?你说你不用VsCode!!!∑(゚Д゚ノ)ノ,VsCode这么好用居然不用。当然用熟悉的编辑器即可,这里本人比较喜欢VsCode)

总结下来就是用了nodemailernodemailer-smtp-transportaxiosnode-schedule这四个npm包。

三、“操盘助理”实现

  这里的需求可以分为两个部分,分别是邮件发送基金信息获取,所以可以将这里两部分的代码抽出来写,放在两个文件,只关注同功能模块的需求实现(业务逻辑上解耦聚合),项目的文件结构如下。

|——						// 根目录 ./node_modules npm i下载即可
|—— src 				// 业务逻辑代码
|   |——app.js 			// 脚本文件入口,启动的文件 交互逻辑
|   |——fundMarket.js 	// 基金相关接口逻辑
|   |——sendMail.js 		// 邮件发送逻辑
|   |——util.js			// 一些工具方法
|——config.js			// 配置文件
|——package-lock.json	// 本地文件 安装npm包所产生的,不重要
|——package.json 		// npm包管理文件,可以看到一些项目的配置和所用依赖版本

(1)SMTP邮件服务配置

  首先来实现发送邮件的需求(我这里使用的是qq邮箱,所以需要先开启qq邮箱的SMTP服务)

  1. 首先选用拥有能开通smtp服务的邮箱,这里打开qq邮箱网页版,点击“设置”—》“账户”,进入下图页面。

进入qq邮箱

  1. 进入“账户”页面后,往下翻然后看到下图的内容,然后选择其中一个SMTP服务,二选一即可,点击“开启”。

开启smtp服务

  1. 开启邮箱需要密保手机号发送短信到下方的号码,这里应该是为了认证个人身份,只能通过如下方法设置。

发送验证码

  1. 发送完短信后,点击“我已发送”,然后就会弹出下方的弹框,这里的授权码要保存下来,等会邮件服务会用到它。

获取到授权码

  1. 完成以上步骤,就算是开启了邮箱的SMTP服务,其他邮箱开启服务也应该是大同小异,这里就不多介绍了。

(2)邮件发送code实现

  这里我将一些固定的参数配置抽取到了./config.js文件夹内,方便修改使用,下面是配置文件。

/** 韭菜提醒的相关参数配置 */
module.exports = {
  //邮箱相关的参数
  mailboxParameters: {
    host: `smtp.qq.com`, //服务地址 如果是qq邮箱这里不需要更改
    port: 465, //smtp端口 默认无需改动
    secureConnection: true, // 使用SSL方式(安全方式,防止被窃取信息)
    auth: {
      user: `***这里写你自己的***@foxmail.com`, // 用户名
      pass: `***上面开启SMTP服务得到的授权码***` // SMTP授权码 qq授权码 
    }
  }
}
// 上面auth下的user和pass用你的邮箱和授权码填入即可

  编写完参数后就是邮件发送部分的代码实现了,在./src下创建sendMail.js文件,在里面编写相关业务逻辑,下面是代码实现的部分。

const nodemailer = require('nodemailer')
const smtpTransport = require('nodemailer-smtp-transport')
//从配置文件获取参数
const { mailboxParameters } = require('../config');

//创建smtp服务连接对象
const transport = nodemailer.createTransport(smtpTransport(mailboxParameters))

/**
 * 发送邮件函数
 * @param {JSON} body 发送的邮件结构 
 */
const sendMail = async (body) => {
  try {
    const res = await transport.sendMail({
      from: mailboxParameters.auth.user, //发送人 邮箱
      ...body
    });
    console.log(`\n------ 发送成功 ------\n`, res, `\n------ 时间 ${new Date().toLocaleString()} ------\n`);
  } catch (e) {
    console.log(`\n------ 发送失败------\n`, e, `\n------ 时间 ${new Date().toLocaleString()} ------\n`);
  }
}

module.exports = sendMail;

/* 
// 示例
sendMail({
  to: '*******@qq.com', // to: "***@qq.com,***2@qq.com", //目标邮箱,多个邮箱用逗号隔开
  subject: '绿油油韭菜告知', // 标题
  text: '一封来zero-d的邮件', //当没有html时,会使用text发送内容
  html: `
    <p>你好!</p>
    <p>${'中证指数暴跌,抄底'}</p>
    <p>跌幅超:<strong style="color: #ff4e2a;">${2.0}</strong> </p>
    <p>***你想抄它的底,它想抄你的家!基金有风险,买基要谨慎!***</p>
    ` // html 内容
})
 */

  编写完成代码后如果想在代码内测试,可以将上面示例部分解注,将接收邮箱填在to哪里,然后输入node sendMail.js就会运行将示例邮件发送到目标邮箱,测试成功后就完成了邮件发送部分的功能,可喜可贺ヾ(◍°∇°◍)ノ゙。

(3)基金接口数据爬取

  之前已经介绍过了接口获取的方式天天基金数据接口说明文章或者自己获取,当然有现成的肯定使用现成的了φ( ) ;同邮件一样,这里基金一些参数也写在了config.js配置文件内(ps:邮箱和基金的相关参数都写在一起了,这里讲解分开展示),基金参数配置如下:

/** 韭菜提醒的相关参数配置 */
module.exports = {
  //天天基金相关接口参数
  fundParameters: {
    /**
     * 获取所有基金列表的接口
     * 返回值:
     * 基金代码、基金名称单拼、基金名称中文、基金类型、基金名称全拼 的json数组形式。
     */
    allFundUrl: `http://fund.eastmoney.com/js/fundcode_search.js`,
    /**
     * 获取单个基金实时信息接口
     * 这里只保存不变的部分地址
     * 基金接口:http://fundgz.1234567.com.cn/js/519983.js?rt=1463558676006
     * 注:上面网址中519983为基金代码,rt=1463558676006为时间戳避免缓存
     * 返回值格式是:
     * jsonpgz(JSON格式数据);
     * fundcode  =>  "519983"             //基金代码
     * name      =>  "长信量化先锋混合A"   //基金名称
     * jzrq      =>  "2018-09-21"         //净值日期
     * dwjz      =>  "1.2440"             //当日净值
     * gsz       =>  "1.2388"             //估算净值
     * gszzl     =>  "-0.42"              //估算涨跌百分比 即-0.42%
     * gztime    =>  "2018-09-25 15:00"   //估值时间
     */
    realTimeInfoUrl: `http://fundgz.1234567.com.cn/js/`,
    /**
     * 基金详细信息地址
     * http://fund.eastmoney.com/pingzhongdata/519983.js?v=20160518155842
     * 注:上面网址中红色部分519983为基金代码;v=20160518155842是时间戳避免缓存。
     * 返回值有中文注释
     */
    detailUrl: `http://fund.eastmoney.com/pingzhongdata/`,
    /**
     * 所有基金公司名称代码列表接口
     * http://fund.eastmoney.com/js/jjjz_gs.js?dt=1463791574015
     * 返回值:基金公司代码、基金公司名称中文
     * 这里暂时用不到
     * 
     * 以上接口参照 天天基金网 http://www.27ba.com/post/683.html
     */
  }
}

  上面写了三个和基金相关的URL接口,但是本次只用了获取单个基金实时信息的接口,剩下两个暂时没有用,想做B/S模式,通过网页灵活选择基金和调整参数,后面有余力再实现吧!目前只实现了node脚本的模式,接下来看看基金信息获取的代码实现,在./src下创建fundMarket.js文件,在里面编写基金接口数据获取方法,下面是代码实现的部分。

const axios = require("axios")
//基金参数
const { fundParameters: { allFundUrl, realTimeInfoUrl, detailUrl } } = require("../config")

/**
 * 获取所有基金的简要信息
 * @returns {JSON} 基金列表
 */
const getFundList = async () => {
  try {
    const res = await axios({ url: allFundUrl, method: `get` });
    //返回数据是二维数组的字符串 内容配置文件有对应 var r = = [["000001","HXCZHH","华夏成长混合","混合型-偏股","HUAXIACHENGZHANGHUNHE"],["000002","HXCZHH"
    eval(res.data);
    return r;
  } catch (e) {
    console.error(`\n------ 获取基金列表信息错误 ------\n`, e, `\n------  时间 ${new Date().toLocaleString()}  ------\n`);
    return null;
  }
}

/**
 * 获取单个基金的数据
 * 基金接口:http://fundgz.1234567.com.cn/js/519983.js?rt=1463558676006
 * 注:上面网址中519983为基金代码,rt=1463558676006为时间戳避免缓存。
 * 返回值格式是:
 * jsonpgz(JSON格式数据);
 * @param {String} code 基金代码 
 * @returns {JSON} 基金信息对象
 */
const getFundData = async (code) => {
  /** 解析返回参数的json */
  const jsonpgz = (jsonStr) => {
    return jsonStr;
  }

  try {
    const url = `${realTimeInfoUrl + code}.js?rt=${new Date().getTime()}`;
    const res = await axios({ url, method: `get` });
    let data = eval(res.data);
    return data;
  } catch (e) {
    console.error(`\n------ 获取单个基金信息错误 ------\n`, e, `\n------  时间 ${new Date().toLocaleString()}  ------\n`);
    return null;
  }
}

/**
 * 获取基金详细信息的接口
 * @param {String} code 基金代码 
 * @returns {String} 返回一段基金信息代码,内涵中文注释
 */
const getFundDetail = async (code) => {
  try {
    const res = await axios({ url: `${detailUrl + code}.js?v=${new Date().getTime()}`, method: `get` });
    return res.data;
  } catch (e) {
    console.error(`\n------ 获取详细基金信息错误 ------\n`, e, `\n------  时间 ${new Date().toLocaleString()}  ------\n`);
    return null;
  }
}

module.exports = {
  getFundList,
  getFundData,
  getFundDetail
}
//抛出上面三个方法用于外部调用

(4)订阅基金估值

   终于到了“鸡冻人心”的时刻了,编写主业务代码,再来理一下需求,想关注一些基金的估值涨跌,涨跌到某个阈值则发邮件通知,由于基金每天是3点闭市,所以一般最好的操作时机在2点30到3点之间,因为这个时候估值和最后结果一般不会相差太多,所以在2点30的时候来定时发送今天达到条件的基金估值信息,在./src下创建app.js文件,编写主要业务逻辑,下面是代码实现。

const schedule = require("node-schedule");
const sendMail = require("./sendMail"); //邮件发送
const { getDay } = require("./utils"); //获取当天日期 这里就不展示了,到时候会上传整个项目源码
const { getFundData } = require("./fundMarket"); //基金接口信息

// early warn 定义涨跌幅度的阈值 第一个是低于多少的通知,第二个是高于多少的通知
const EW = [-1, 1];
//定义要订阅的基金列表的代码
const codeList = ['161725', '013278', '163406', '166002', '163402', '001410', '260108', '004241', '002697', '003096', '005242'];
// 订阅的邮箱号  目标邮箱,多个邮箱用逗号隔开
const users = `*****@qq.com`;

/**
 * 获取当天订阅的基金行情
 */
const fundEarlyWarning = async () => {
  try {
    //获取列表内的基金信息
    const resList = await Promise.all(codeList.map(code => getFundData(code)));
    if (!resList.length) {
      console.log(`\n------ ${new Date().toLocaleString()}-未获取到基金信息 ------\n`);
      return;
    }
    //考虑是否开市 这里getDay函数获取时间格式是 XXXX-XX-XX 代码并未在文章内展示,因为比较简单。可以查看源码
    if (resList[0].gztime.split(" ")[0] !== getDay()) {
      console.log(`\n------ ${new Date().toLocaleString()}-今天未开市 ------\n`);
      return;
    }
    // 过滤出符合条件的基金
    const fundList = resList.filter(fd => (fd.gszzl < EW[0] || fd.gszzl > EW[1]))
    if (!fundList.length) {
      //发送邮件
      sendMail({
        to: users, // to: "***@qq.com,***2@qq.com", //目标邮箱,多个邮箱用逗号隔开
        subject: '【韭零后通告】今日行情', // 标题
        text: `今日涨跌幅度都在${EW}内,建议当一只安分的韭菜!`, //当没有html时,会使用text发送内容
      })
      console.log(`\n------ ${new Date().toLocaleString()}-没有符合通知条件的基金 ------\n`);
      return;
    }
    //邮箱发送
    emailNotify(fundList);
  } catch (e) {
    console.error(`\n------ 基金通知错误 ------\n`, e, `\n------  时间 ${new Date().toLocaleString()}  ------\n`);
  }
}

/**
 * 依据传递过来订阅基金的涨跌幅,发送邮件通知
 * @param {Array} data 
 */
const emailNotify = (data) => {
  try {
    //首先编写内容
    let notifyStr = "";
    data.forEach(fd => {
      let flag = fd.gszzl < 0; //判断是跌是涨
      notifyStr += `
        <tr>
          <td style="border: 1px solid #6495ed;">${fd.name + '-' + fd.fundcode}</td>
          <td style="border: 1px solid #6495ed;"><strong style="color: ${flag ? 'green' : 'red'};">${fd.gszzl}%</strong></td>
          <td style="border: 1px solid #6495ed;color: ${flag ? 'green' : 'red'};">${flag ? '卖房梭哈!' : '落袋为安!'}</td>
          <td style="border: 1px solid #6495ed;">${fd.gztime}</td>
        </tr>
      `
    });
    //整体的html内容
    const html = `
    <table cellspacing="0" cellpadding="5px" style="font-family:'微软雅黑',Helvetica,Arial,sans-serif;color: #2C3E50;font-size:10px;">
      <tbody>
        <tr>
          <th style="border: 1px solid #6495ed;">基金名&代码</th>
          <th style="border: 1px solid #6495ed;">估算涨跌百分比</th>
          <th style="border: 1px solid #6495ed;">建议操作</th>
          <th style="border: 1px solid #6495ed;">估值时间</th>
        </tr>
        ${notifyStr}
      </tbody>
    </table>
    `;
    //发送邮件
    sendMail({
      to: users, // to: "***@qq.com,***2@qq.com", //目标邮箱,多个邮箱用逗号隔开
      subject: '【韭零后通告】今日行情', // 标题
      text: '没有html,所以出现了txt', //当没有html时,会使用text发送内容
      html
    })
  } catch (e) {
    console.error(`\n------ 发送邮件错误 ------\n`, e, `\n------  时间 ${new Date().toLocaleString()}  ------\n`);
  }
}

//定时触发任务
const signTask = () => {
  //每天在2:30通知今天行情
  schedule.scheduleJob("0 30 14 * * *", () => {
    fundEarlyWarning();
  })
}

//启动定时任务
console.clear()
console.log(`\n------ 开始执行脚本-时间:${new Date().toLocaleString()}  ------\n`);
signTask();

  虽然要实现的需求比较简单,但是代码写起来还是花了一些功夫的,尤其是那个表格,js里面写html代码真的有点搞心态(。-_-。),但最多也就是花了点时间,我将订阅基金的代码数据放在一个数组内codeList,如果要使用,将自己关注的基金代码放在里面即可,然后设置一个涨跌阈值EW,如果估值超出阈值就将基金列表内符合条件的基金筛选出来发送到订阅的邮箱内users,发送邮件的时间可以依据自己需要设置,我一般喜欢在下午2点30看一下基金的估值变化。这里增减订阅的基金是控制codeList来实现,阈值EW来控制发送的基金条数,users就是需要接收的邮箱,这些参数其实都可以放在一个界面内来勾选,减少代码修改带来的麻烦,但是工作量偏大,所以有空再去实现吧!当然有兴趣的小伙伴可以尝试实现一下!ヾ(o・ω・)ノ 下面是今天订阅的基金信息,都跌了!(T▽T) ”跌跌不休“了属于是。

绿色基金

四、结语

  上一次发node自动化签到文章的时候,就已经想着要写一篇关于node发送邮件的文章了,但是写完邮件发送的代码后发现太过于简陋了,正好最近买的基金跌的厉害,刚好想起来以前有这样一个想法,所以打算结合邮件发送来实现基金估值订阅的想法。没想到今天的基金又绿了,太惨了!惨绝人寰!(T▽T) 总之代码实现了还是非常开心的,又get到了一个被"噶韭菜"的新姿势,我躺平了,让它随风去吧!

韭零后

  股市有风险,投资虚谨慎!本文内容不构成任何投资建议,只是对获取基金信息的代码实现技术分享文章。欢迎热爱技术和乐于当“韭菜”的小伙伴讨论,如果本文有不妥之处还望指正,ヾ(o・ω・)ノ感谢各位看官的耐心阅读,如有帮助不防动动你的小手指点个赞,非常感谢!φ(>ω<*)

欺负辣鸡

五、源码&参考

  1. 基金信息相关参考:http://www.27ba.com/post/683.html
  2. 源代码放在gitee上了,欢迎clone,地址:https://gitee.com/zero-dg/fund_subscription.git
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZERO丶X

托马斯回旋360°飞旋感谢!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值