Written for my wife, automatically pushes heart-warming messages every day


1. Cause

I found an article on Rare Earth Nuggets [Written for my girlfriend, automatically push heart-warming messages every day] , and it is very interesting to automatically send messages every day on a regular basis. I just heard a colleague talk about using the nodejs framework to implement the front-end and back-end at the weekly meeting. Examination system, I also learned through Baidu that the nodejs back-end framework ranks as shown in the figure, so I decided to try a nestjs (nodejs framework ranks second) implementation.

Insert image description here

2. Environmental preparation

Operating system: windows, Linux, MacOs
Operating environment: except Nodejs>=12 v13 version

3. Create nestjs project

Creating a new project is very simple using Nest CLI. After installing npm, you can use the following command to create a Nest project in your OS terminal:

$ npm i -g @nestjs/cli
$ nest new project-name

The project-name directory will be created, node_modules and some other boilerplate will be installed, and a src directory will be created containing several core files.

src
 ├── app.controller.spec.ts
 ├── app.controller.ts
 ├── app.module.ts
 ├── app.service.ts
 └── main.ts

Here is a brief overview of these core documents:

Insert image description here
main.ts contains an asynchronous function that is responsible for bootstrapping our application:

import {
    
     NestFactory } from '@nestjs/core';
import {
    
     AppModule } from './app.module';

async function bootstrap() {
    
    
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

To create a Nest application instance, we use the NestFactory core class. NestFactory exposes some static methods for creating application instances. The create() method returns an object that implements the INestApplication interface. This object provides a set of methods available. In the main.ts example above, we just started the HTTP service and let the application wait for HTTP requests.

4. Controller

@Controller('ymn')
export class YmnController {
    
    
  constructor(private ymnService: YmnService) {
    
    }

  @Get()
  async send(@Res() response: Response) {
    
    
    const result = await this.ymnService.sendOut();
    response.status(HttpStatus.OK).send('ok');
  }
}

5. Service service layer

// 时间处理
const moment = require('moment');

@Injectable()
export class YmnService {
    
    
  constructor(
    private configService: ConfigService,
    private readonly httpService: HttpService,
  ) {
    
    }

  /**
   * @method 发送模板消息给媳妇儿
   * @returns any[]
   */
  async sendOut(): Promise<any[]> {
    
    
    const token = await this.getToken();
    const data = await this.getTemplateData();
    console.log(data);
    // 模板消息接口文档
    const users = this.configService.get('weixin.users');
    const promise = users.map((id) => {
    
    
      data.touser = id;
      return this.toWechart(token, data);
    });
    const results = await Promise.all(promise);
    return results;
  }

  /**
   * @method 获取token
   * @returns string token
   */
  async getToken(): Promise<string> {
    
    
    ...
  }

  /**
   * @method 组装模板消息数据
   * @returns dataType 组装完的数据
   */
  async getTemplateData(): Promise<dataType> {
    
    
    ...
  }

  /**
   * @method 获取距离下次发工资还有多少天
   * @returns number
   */
  getWageDay(): number {
    
    
    ...
  }

  /**
   * @method 获取距离下次结婚纪念日还有多少天
   * @returns number
   */
  getWeddingDay(): number {
    
    
    ...
  }

  /**
   * @method 获取距离下次生日还有多少天
   * @returns number
   */
  getbirthday(): number {
    
    
    ...
  }

  /**
   * @method 获取时间日期
   * @returns string
   */
  getDatetime(): string {
    
    
    ...
  }

  /**
   * @method 获取是第几个结婚纪念日
   * @returns number
   */
  getMarryYear(): number {
    
    
    ...
  }

  /**
   * @method 获取相恋几年了
   * @returns number
   */
  getLoveYear(): number {
    
    
    ...
  }

  /**
   * @method 获取是第几个生日
   * @returns number
   */
  getBirthYear(): number {
    
    
    ...
  }

  /**
   * @method 获取天气
   * @returns
   */
  async getWeather(): Promise<weatherType> {
    
    
    ...
  }

  /**
   * @method 获取每日一句
   * @returns string
   */
  async getOneSentence(): Promise<string> {
    
    
    ...
  }

  /**
   * @method 获取相恋天数
   * @returns number
   */
  getLoveDay(): number {
    
    
    ...
  }

  /**
   * @method 通知微信接口
   * @param token
   * @param data
   * @returns
   */
  async toWechart(token: string, data: dataType): Promise<any> {
    
    
    ...
  }
}

1. Obtain Access token

  /**
   * @method 获取token
   * @returns string token
   */
  async getToken(): Promise<string> {
    
    
    const grantType = this.configService.get('weixin.grant_type');
    const appid = this.configService.get('weixin.appid');
    const secret = this.configService.get('weixin.secret');
    // 模板消息接口文档
    const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=${
      
      grantType}&appid=${
      
      appid}&secret=${
      
      secret}`;
    const result = await firstValueFrom(this.httpService.get(url));
    if (result.status === 200) return result.data.access_token as string;
    else return '';
  }

2. Assemble template message data

  /**
   * @method 组装模板消息数据
   * @returns dataType 组装完的数据
   */
  async getTemplateData(): Promise<dataType> {
    
    
    // 判断所需模板
    // 发工资模板 getWageDay === 0       wageDay
    // 结婚纪念日模板 getWeddingDay === 0  weddingDay
    // 生日模板 getbirthday === 0       birthday
    // 正常模板                         daily
    const wageDay = this.getWageDay();
    const weddingDay = this.getWeddingDay();
    const birthday = this.getbirthday();
    const data = {
    
    
      topcolor: '#FF0000',
      template_id: '',
      data: {
    
    },
      touser: '',
    };
    // 发工资模板
    if (!wageDay) {
    
    
      data.template_id = this.configService.get('weixin.wage_day');
      data.data = {
    
    
        dateTime: {
    
    
          value: this.getDatetime(),
          color: '#cc33cc',
        },
      };
    } else if (!weddingDay) {
    
    
      // 结婚纪念日模板
      data.template_id = this.configService.get('weixin.wedding_day');
      data.data = {
    
    
        dateTime: {
    
    
          value: this.getDatetime(),
          color: '#cc33cc',
        },
        anniversary: {
    
    
          value: this.getMarryYear(),
          color: '#ff3399',
        },
        year: {
    
    
          value: this.getLoveYear(),
          color: '#ff3399',
        },
      };
    } else if (!birthday) {
    
    
      // 生日模板
      data.template_id = this.configService.get('weixin.birthdate');
      data.data = {
    
    
        dateTime: {
    
    
          value: this.getDatetime(),
          color: '#cc33cc',
        },
        individual: {
    
    
          value: this.getBirthYear(),
          color: '#ff3399',
        },
      };
    } else {
    
    
      // 正常模板
      data.template_id = this.configService.get('weixin.daily');
      // 获取天气
      const getWeather = await this.getWeather();
      // 获取每日一句
      const message = await this.getOneSentence();
      data.data = {
    
    
        dateTime: {
    
    
          value: this.getDatetime(),
          color: '#cc33cc',
        },
        love: {
    
    
          value: this.getLoveDay(),
          color: '#ff3399',
        },
        wage: {
    
    
          value: wageDay,
          color: '#66ff00',
        },
        birthday: {
    
    
          value: birthday,
          color: '#ff0033',
        },
        marry: {
    
    
          value: weddingDay,
          color: '#ff0033',
        },
        wea: {
    
    
          value: getWeather.wea,
          color: '#33ff33',
        },
        tem: {
    
    
          value: getWeather.tem,
          color: '#0066ff',
        },
        wind_class: {
    
    
          value: getWeather.wind_class,
          color: '#ff0033',
        },
        tem1: {
    
    
          value: getWeather.tem1,
          color: '#ff0000',
        },
        tem2: {
    
    
          value: getWeather.tem2,
          color: '#33ff33',
        },
        win: {
    
    
          value: getWeather.win,
          color: '#3399ff',
        },
        message: {
    
    
          value: message,
          color: '#8C8C8C',
        },
      };
    }
    return data;
  }

3. Get the number of days until the next salary payment

  /**
   * @method 获取距离下次发工资还有多少天
   * @returns number
   */
  getWageDay(): number {
    
    
    const wageDay = this.configService.get('time.wage_day');
    // 获取日期 day
    // 如果在wage号之前或等于wage时,那么就用wage-day
    // 如果在wage号之后,那么就用wage +(当前月总天数 - day)
    // 当日日期day
    const day = moment().date();
    // 当月总天数
    const nowDayTotal = moment().daysInMonth();
    // // 下个月总天数
    let resultDay = 0;
    if (day <= wageDay) resultDay = wageDay - day;
    else resultDay = wageDay + (nowDayTotal - day);
    return resultDay;
  }

4. Get the number of days until the next wedding anniversary

  /**
   * @method 获取距离下次结婚纪念日还有多少天
   * @returns number
   */
  getWeddingDay(): number {
    
    
    const weddingDay = this.configService.get('time.wedding_day');
    // 获取当前时间戳
    const now = moment(moment().format('YYYY-MM-DD')).valueOf();
    // 获取纪念日 月-日
    const mmdd = moment(weddingDay).format('-MM-DD');
    // 获取当年
    const y = moment().year();
    // 获取今年结婚纪念日时间戳
    const nowTimeNumber = moment(y + mmdd).valueOf();
    // 判断今天的结婚纪念日有没有过,如果已经过去(now>nowTimeNumber),resultMarry日期为明年的结婚纪念日
    // 如果还没到,则结束日期为今年的结婚纪念日
    let resultMarry = nowTimeNumber;
    if (now > nowTimeNumber) {
    
    
      // 获取明年纪念日
      resultMarry = moment(y + 1 + mmdd).valueOf();
    }
    return moment(moment(resultMarry).format()).diff(
      moment(now).format(),
      'day',
    );
  }

5. Get how many days are left until the next birthday

  /**
   * @method 获取距离下次生日还有多少天
   * @returns number
   */
  getbirthday(): number {
    
    
    const birthdate = this.configService.get('time.birthdate');
    // 获取当前时间戳
    const now = moment(moment().format('YYYY-MM-DD')).valueOf();
    // 获取当年
    const y = moment().year();
    // 获取纪念日 月
    const m = moment(birthdate).month();
    // 获取纪念日 日
    const d = moment(birthdate).date();
    // 获取今年生日阳历
    const nowBirthday = lunisolar
      .fromLunar({
    
    
        year: y,
        month: m + 1,
        day: d,
      })
      .format('YYYY-MM-DD');
    // 获取今年生日时间戳
    const nowTimeNumber = moment(nowBirthday).valueOf();
    // 判断生日有没有过,如果已经过去(now>nowTimeNumber),resultBirthday日期为明年的生日日期
    // 如果还没到,则结束日期为今年的目标日期
    let resultBirthday = nowTimeNumber;
    if (now > nowTimeNumber) {
    
    
      // 获取明年生日阳历
      const nextBirthday = lunisolar
        .fromLunar({
    
    
          year: y + 1,
          month: m + 1,
          day: d,
        })
        .format('YYYY-MM-DD');
      // 获取明年目标日期
      resultBirthday = moment(nextBirthday).valueOf();
    }
    return moment(moment(resultBirthday).format()).diff(
      moment(now).format(),
      'day',
    );
  }

6. Get the time and date

  /**
   * @method 获取时间日期
   * @returns string
   */
  getDatetime(): string {
    
    
    const week = {
    
    
      1: '星期一',
      2: '星期二',
      3: '星期三',
      4: '星期四',
      5: '星期五',
      6: '星期六',
      0: '星期日',
    };
    return moment().format('YYYY年MM月DD日 ') + week[moment().weekday()];
  }

7. Get the wedding anniversary

  /**
   * @method 获取是第几个结婚纪念日
   * @returns number
   */
  getMarryYear(): number {
    
    
    const weddingDay = this.configService.get('time.wedding_day');
    return moment().year() - moment(weddingDay).year();
  }

8. How many years have we been in love?

  /**
   * @method 获取相恋几年了
   * @returns number
   */
  getLoveYear(): number {
    
    
    const loveDay = this.configService.get('time.love');
    return moment().year() - moment(loveDay).year();
  }

9. Get the birthday number

  /**
   * @method 获取是第几个生日
   * @returns number
   */
  getBirthYear(): number {
    
    
    const birthdate = this.configService.get('time.birthdate');
    return moment().year() - moment(birthdate).year();
  }

10. Get the weather

  /**
   * @method 获取天气
   * @returns
   */
  async getWeather(): Promise<weatherType> {
    
    
    try {
    
    
      const dataType = this.configService.get('weather.data_type');
      const ak = this.configService.get('weather.ak');
      const districtId = this.configService.get('weather.district_id');
      // https://api.map.baidu.com/weather/v1/?district_id=130128&data_type=all&ak=bGjmaBLLzlBZXTiAkOwSqiVjftZlg17O
      const url = `https://api.map.baidu.com/weather/v1/?district_id=${
      
      districtId}&data_type=${
      
      dataType}&ak=${
      
      ak}`;
      const result: any = await firstValueFrom(this.httpService.get(url));
      // "wea": "多云",
      // "tem": "27", 实时温度
      // "tem1": "27", 高温
      // "tem2": "17", 低温
      // "win": "西风",
      // "air_level": "优",
      if (result && result.data && result.data.status === 0) {
    
    
        const now = result.data.result.now;
        const forecasts = result.data.result.forecasts[0];
        return {
    
    
          wea: now.text,
          tem: now.temp,
          tem1: forecasts.high,
          tem2: forecasts.low,
          win: now.wind_dir,
          wind_class: now.wind_class,
        };
      }
    } catch (error) {
    
    
      return {
    
    
        wea: '未知',
        tem: '未知',
        tem1: '未知',
        tem2: '未知',
        win: '未知',
        wind_class: '未知',
      };
    }
  }

11. Get a daily sentence

  /**
   * @method 获取每日一句
   * @returns string
   */
  async getOneSentence(): Promise<string> {
    
    
    const url = 'https://v1.hitokoto.cn/';
    const result = await firstValueFrom(this.httpService.get(url));
    if (result && result.status === 200) return result.data.hitokoto;
    else return '今日只有我爱你!';
  }

12. Get the number of days in love

  /**
   * @method 获取相恋天数
   * @returns number
   */
  getLoveDay(): number {
    
    
    const loveDay = this.configService.get('time.love');
    return moment(moment().format('YYYY-MM-DD')).diff(loveDay, 'day');
  }

13. Send template message

  /**
   * @method 通知微信接口
   * @param token
   * @param data
   * @returns
   */
  async toWechart(token: string, data: dataType): Promise<any> {
    
    
    // 模板消息接口文档
    const url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${
      
      token}`;
    const result = await firstValueFrom(this.httpService.post(url, data));
    return result;
  }

6. Defined data types

interface dataItemType {
    
    
  value: string;
  color: string;
}

export interface dataType {
    
    
  topcolor: string;
  template_id: string;
  touser: string;
  data: Record<any, dataItemType>;
}

export interface weatherType {
    
    
  wea: string;
  tem: string;
  tem1: string;
  tem2: string;
  win: string;
  wind_class: string;
}

7. Configuration file

Configuration method reference configuration

1. WeChat public account configuration

Because individuals can only apply for a subscription number, and the subscription number does not support sending template messages, so the WeChat public account used here for testing can be applied for with a WeChat account, free of registration, and scan the code to log in

No public account required, quick application for interface test account

Directly experience and test all advanced interfaces of the public platform

Application address

import {
    
     registerAs } from '@nestjs/config';

export default registerAs('weixin', () => ({
    
    
  grant_type: '*',
  appid: '*',
  secret: '*',
  users: ['*'],// 用户的openid
  wage_day: '*',// 工资日模板
  wedding_day: '*',// 结婚纪念日模板
  birthdate: '*',// 生日模板
  daily: '*',// 普通模板
}));

2. Special time point settings

import {
    
     registerAs } from '@nestjs/config';

// 时间
export default registerAs('time', () => ({
    
    
  wage_day: 10, // 工资日
  wedding_day: '*',// 结婚纪念日
  birthdate: '*',// 生日阴历
  love: '*',// 相爱日期
}));

3. Weather configuration

import {
    
     registerAs } from '@nestjs/config';

export default registerAs('weather', () => ({
    
    
  data_type: 'all',
  ak: '*',
  district_id: '110100',
}));

8. WeChat message template

This needs to be set up separately in the WeChat public platform test account mentioned above

The following is the template I use

1. Normal template

{
    
    {
    
    dateTime.DATA}} 
今天是 我们相恋的第{
    
    {
    
    love.DATA}}天 
距离上交工资还有{
    
    {
    
    wage.DATA}}天 
距离你的生日还有{
    
    {
    
    birthday.DATA}}天 
距离我们结婚纪念日还有{
    
    {
    
    marry.DATA}}天 
今日天气 {
    
    {
    
    wea.DATA}} 
当前温度 {
    
    {
    
    tem.DATA}}度 
最高温度 {
    
    {
    
    tem1.DATA}}度 
最低温度 {
    
    {
    
    tem2.DATA}}度 
风向 {
    
    {
    
    win.DATA}} 
风力等级 {
    
    {
    
    wind_class.DATA}} 
每日一句 
{
    
    {
    
    message.DATA}}

2. Salary template

{
    
    {
    
    dateTime.DATA}}
老婆大人,今天要发工资了,预计晚九点前会准时上交,记得查收!

3. Birthday template

{
    
    {
    
    dateTime.DATA}}
听说今天是你人生当中第 {
    
    {
    
    individual.DATA}} 个生日?天呐,
我差点忘记!因为岁月没有在你脸上留下任何痕迹。
尽管,日历告诉我:你又涨了一岁,但你还是那个天真可爱的小妖女,生日快乐!

4. Wedding anniversary

{
    
    {
    
    dateTime.DATA}}
今天是结婚{
    
    {
    
    anniversary.DATA}}周年纪念日,在一起{
    
    {
    
    year.DATA}}年了,
经历了风风雨雨,最终依然走在一起,很幸运,很幸福!我们的小家庭要一直幸福下去。

9. Local development

$ yarn
$ yarn start:dev

10. Display effect

Insert image description here
Insert image description here
Insert image description here
Insert image description here

reference

  1. Node.js backend framework star ranking updated in February 2023
  2. Written for my girlfriend, automatically push heart-warming messages every day

write at the end

If you feel that the article is not good //(ㄒoㄒ)//, just leave a message in the comment section, and the author will continue to improve; o_O???
if you think the article is a little bit useful, you can give the author a like; \\*^o^*//
if you want to make progress with the author, you can scan the QR code on WeChat , pay attention to the front-end old L ; ~~~///(^v^)\\\~~~
thank you readers (^_^)∠※! ! !

Guess you like

Origin blog.csdn.net/weixin_62277266/article/details/132724488