通过EJS模板生成图片的过程是先使用EJS生成模板内容,然后利用Puppeteer将该模板内容渲染成图片。

[scode type="green" size=""]此方法仅在Win系统上使用[/scode]

安装依赖

npm install koa koa-router koa-views koa-static ejs puppeteer

目录结构

-views
 -template.ejs
-public
 -images
 -fonts
index.js

代码

[collapse status="false" title="template.ejs 代码"]

代码中字体必须保证在非跨域状态下才可以使用
<!-- views/template.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Generation</title>
</head>
<style>
    * {
        margin: 0;
        padding: 0;
    }

    @font-face {
        font-family: 'YouSheBiaoTiHei';
        src: url('https://webapi.domcer.com/font/YouSheBiaoTiHei-2.ttf');
    }

    @font-face {
        font-family: 'ZhanKuWenYiTi';
        src: url('https://webapi.domcer.com/font/ZhanKuWenYiTi-2.ttf');
    }

    .ZhanKuWenYiTi {
        font-family: ZhanKuWenYiTi;
    }

    .YouSheBiaoTiHei {
        font-family: YouSheBiaoTiHei;
    }
    .box{
        position: relative;
    }

    .dateBox {
        font-size: 60px;
        color: rgba(56, 56, 56, 1);
        font-family: ZhanKuWenYiTi;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 86.88px;
        text-align: left;
        vertical-align: top;
        position: fixed;
        top: 38px;
        left: 56px;
    }

    div.box {
        width: 2020px;
        /* filter: blur(25px);*/
        height: 1000px;
        background: url(https://img.js.design/assets/img/66027a8d4a1c4bf1a98b9df9.png) no-repeat;
        background-size: 100% 100%;
    }

    .userBox img {
        border-radius: 50%;
        border: 3px solid rgba(46, 46, 46, 0.9);
        width: 150px;
        height: 150px;
        position: fixed;
        top: 158px;
        left: 56px;
    }

    .userBox .userMsg {
        position: fixed;
        top: 155px;
        left: 243px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 78px;
        color: rgba(0, 0, 0, 1);
        margin: 0;
        padding: 0;
        font-size: 60px;
        font-family: YouSheBiaoTiHei;
    }

    .fortuneBox {
        font-size: 60px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 59.82px;
    }

    .fortuneBox .fortuneTitle {
        position: fixed;
        top: 440px;
        left: 281px;
        color: rgba(0, 0, 0, 1);
    }

    .fortuneBox .fortuneMsg {
        position: fixed;
        top: 527px;
        left: 50px;
        font-size: 50px;
        color: rgba(128, 128, 128, 1);
        width: 945px;
        height: 75px;
        text-align: center;
        padding-top: 13px;
        padding-bottom: 12px;
    }

    .characterBox {
        font-size: 60px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 59.82px;
    }

    .characterBox .characterTitle {
        position: fixed;
        top: 659px;
        left: 281px;
        color: rgba(0, 0, 0, 1);
    }

    .characterBox .characterMsg {
        position: fixed;
        top: 755px;
        left: 50px;
        font-size: 50px;
        color: rgba(128, 128, 128, 1);
        width: 945px;
        height: 75px;
        padding-top: 13px;
        padding-bottom: 12px;
        text-align: center;
    }

    .boxRight {
        font-family: YouSheBiaoTiHei;
    }

    .signInBoxTitle {
        font-size: 60px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 78px;
        color: rgba(0, 0, 0, 1);
        text-align: center;
        vertical-align: top;
        position: fixed;
        top: 198px;
        left: 1196px;
    }

    .signInBoxMsg {
        height: 99px;
        width: 945px;
        position: fixed;
        top: 281px;
        left: 929px;
        text-align: center;
        padding: 11px 0;
        font-size: 60px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 78px;
        color: rgba(128, 128, 128, 1);
    }

    .awardBoxTitle {
        font-size: 60px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 78px;
        color: rgba(0, 0, 0, 1);
        text-align: center;
        vertical-align: top;
        position: fixed;
        top: 447px;
        left: 1195px;
    }

    .awardBoxMsg p {
        margin: 0;
        padding: 0;
    }

    .awardBoxMsg {
        position: fixed;
        top: 540px;
        left: 1126px;
        font-size: 50px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 65px;
        color: rgba(128, 128, 128, 1);
        text-align: left;
    }

    .balanceBox {
        position: absolute;
        top: 737px;
        right: 80px;
        font-size: 50px;
        font-weight: 400;
        letter-spacing: 0px;
        line-height: 65px;
        color: rgba(56, 56, 56, 1);
        text-align: left;
        vertical-align: top;
    }
</style>

<body style="background-color: black;position: relative;">
<div class="bgImg">

</div>
<div class="box">
    <div class="boxLeft">
        <div class="dateBox">
            <%= obj.date %> <%= obj.week %> 已签到<%= obj.userSignInDays%>天
        </div>
        <div class="userBox">
            <img src="<%= obj.userAvatar %>" alt="">
            <div class="userMsg">
                <div>
                    HI <%= obj.userName %>
                </div>
                <div>
                    <%= obj.helloMsg %>
                </div>
            </div>
        </div>
        <div class="fortuneBox ZhanKuWenYiTi">
            <div class="fortuneTitle">
                查收您的今日运势
            </div>
            <div class="fortuneMsg">
                <%= obj.fortuneMsg %>
            </div>
        </div>
        <div class="characterBox ZhanKuWenYiTi">
            <div class="characterTitle">
                查收您的今日人品
            </div>
            <div class="characterMsg">
                <%= obj.characterMsg %>
            </div>
        </div>
    </div>
    <div class="boxRight">
        <div class="signInBox">
            <div class="signInBoxTitle">
                --- 签到成功 ---
            </div>
            <div class="signInBoxMsg">
                <%= obj.signInMsg %>
            </div>
        </div>
        <div class="awardBox">
            <div class="awardBoxTitle">
                --- 额外奖励---
            </div>
            <div class="awardBoxMsg">
                <% for (const key in obj.awardList) { %>
                    <p><%= obj.awardList[key] %></p>
                <% } %>

            </div>
        </div>
        <div class="balanceBox">
            <div class="balanceBoxTitle">
                余额:
            </div>
            <div class="balanceBoxMsg">
                <p><%= obj.balance.coin %>硬币</p>
                <p><%= obj.balance.flower %>小花</p>
                <p><%= obj.balance.point %>点数</p>
            </div>
        </div>
    </div>
</div>
</body>
</html>

[/collapse]

[collapse status="false" title="index.js 代码"]

大部分代码是由chatgpt生成
const Koa = require('koa');
const Router = require('koa-router');
const views = require('koa-views');
const path = require('path');
const {writeFileSync} = require('fs');
const static = require('koa-static');
const app = new Koa();
const router = new Router();

// 使用koa-views中间件来配置模板引擎
app.use(views(__dirname + '/views', {
    extension: 'ejs', // 使用你喜欢的模板引擎,这里以ejs为例
}));
const staticDir = path.join(__dirname, 'public');
app.use(static(staticDir));
router.get('/generate-image', async (ctx) => {
    // 获取请求参数
    const today = new Date();
    const month = today.getMonth() + 1; // 月份是从 0 开始计数的,因此需要加 1
    const day = today.getDate();
    let week=today.toLocaleDateString("zh",{weekday:'long'});
    let obj = {
        date: `${month}/${day}`,
        week: '周'+week.charAt(week.length - 1),
        userSignInDays: 30,
        userAvatar:"http://localhost:3000/images/1.jpg",
        userName:"TongHui",
        helloMsg:"早上好,今天心情如何?",
        fortuneMsg:"宜 出门 忌 躺平",
        characterMsg:"[53] 运气一般般呢",
        signInMsg:"+3 硬币(每日签到)",
        awardList:[
            "+1 小花 (每月首次签到)",
            "+10 硬币 (每月首次签到)",
            "+8 硬币 (每周首次签到)",
            "+6 硬币 (连续签到)"
        ],
        balance:{
            coin:300,
            flower:1,
            point:0
        }
    }
    // 渲染HTML模板,并将参数传递给模板
    await ctx.render('template', {obj});

    // 使用puppeteer导出HTML为图片
    const puppeteer = require('puppeteer');
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    // 设置页面为透明背景
    await page.setViewport({width: 2020, height: 1000, deviceScaleFactor: 0.8});
    await page.evaluate(() => {
        document.body.style.background = 'transparent';
    });

    // 替换成你的HTML内容
    const htmlContent = await ctx.render('template', {obj});

    await page.setContent(htmlContent);
    const screenshotBuffer = await page.screenshot({
        clip: {x: 0, y: 0, width: 2020, height: 1000},
        omitBackground: true
    });

    // 生成图片名字
    const filename = `${Date.now()}_image.png`;
    // 图片路径
    const filePath = `./public/images/${filename}`;
    // 生成图片
    writeFileSync(filePath, screenshotBuffer);

    // 关闭浏览器
    await browser.close();

    // 返回地址
    ctx.body = {imagePath: `/images/${filename}`};
});
// 将路由应用到Koa实例
app.use(router.routes());

// 启动服务器
const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

[/collapse]

获取Api

http://127.0.0.1:3000/generate-image

返回

{
"imagePath": "/images/1711952806672_image.png"
}

获取图片

http://127.0.0.1:3000/images/1711952806672_image.png

效果图

1711705505655_image.png

@Configuration
public class GlobalCorsConfig {

    /**
     * 允许跨域调用的过滤器
     */
    @Bean
    public CorsFilter corsFilter() {
        // 创建一个跨域的配置类
        CorsConfiguration config = new CorsConfiguration();
        // 允许白名单域名进行跨域调用
        // 这个是配置允许哪些域名进行跨域调用 *代表所有域名
        // 这个配置可以写多个
        config.addAllowedOrigin("*");
        // 允许跨越发送 cookie
        config.setAllowCredentials(true);
        // 放行全部原始头信息
        config.addAllowedHeader("*");
        // 允许所有请求方法跨域调用
        // 这个配置也可以写多个 POST PUT GET DELETE OPTIONS
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 访问这个项目的所有请求都会跨域调用
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

<!DOCTYPE html>
<html>
<head>
  <title>Div to Image with Width</title>
  <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
</head>
<body>
  <div id="content" style="width: 300px; border: 1px solid #000;">
    <h1>Hello, Div to Image!</h1>
    <p>This is a simple example.</p>
  </div>

  <button id="convertButton">Convert to Image</button>

  <script>
    const convertButton = document.getElementById('convertButton');
    const contentDiv = document.getElementById('content');

    convertButton.addEventListener('click', async () => {
      const contentWidth = contentDiv.offsetWidth; // Get the width of the content div

      const canvas = await html2canvas(contentDiv, {
        width: contentWidth, // Set the canvas width to match the content div's width
      });

      // Convert canvas to image
      const image = canvas.toDataURL('image/png');

      const a = document.createElement('a');
      a.href = image;
      a.download = 'content.png';
      a.click();
    });
  </script>
</body>
</html>

多线程基础

相关概念

多线程

多线程就是同时执行多个应用程序 需要硬件支持

效果上来说就是CPU在多个程序之间快速切换 给人一种多个软件同时执行的感觉

并发: 某一个时刻 多个任务同时在执行 在同一时刻 有多个指令在多个CPU上同时执行

并发: 某个时间段 多个任务同时执行 但是某个时刻只有一个任务在执行 在某一时间段 有多个指令在单个CPU上交替执行

当下电脑执行过程中两种情况会同时存在

并发: 是因为目前的cpu都是多核心的 每个核心执行一个指令多个核心就同时在执行多个指令

并发:每个核心在某一刻 只能执行一个程序指令 之后某一刻 可能在执行其他软件的命令

进程

正在运行的软件

独立性 进程是一个能独立运行的基本单位 同时也是系统分配资源和调度的独立单位

动态性 进程的是指是程序的一次执行过程 进程是动态产生的 动态消亡的

并发性 任何进程都可以同其他进程一起并发执行

线程

是进程中的单个循序控制流 是一条执行路径 应用程序中能做的某个事情

随机性: 一个宪曾在某个时间段内在执行 但是这段时间的某个时刻不一定在执行 此时cpu可以能在执行另一个软件 cpu可以在多个线程 随机快速切换执行

多线程的实现方式

jdk原生支持的

1.继承thread类

2.实现runnable接口

3.实现callable接口

继承Thread类

方法

void run()

想要多线程中执行的代码写在这个方法中 在线程启动后此方法将自动被调用执行

void start()

启动线程的方法 之后java虚拟机会调用run()方法

实现步骤

定义一个类MyThread继承Thread类

在MyThread类中重写run()方法

创建MyThread类的对象

使用线程对象的start()启动线程

public class ThreadDemo01 {
    public static void main(String[] args) {


        // 3. 创建MyThread类的对象
        MyThread mth1 = new MyThread();
        MyThread mth2 = new MyThread();

        // 4. 使用线程对象的start()启动线程
        mth1.start();
        mth2.start();


    }
}

// 1. 定义一个类MyThread继承Thread类
class MyThread extends Thread{
    @Override
    // 2. 在MyThread类中重写run()方法
    public void run() {
        // 这里的代码会在对应的线程启动后自动被执行
        for (int i = 0; i < 100; i++) {
            System.out.println("新的线程开始执行代码了"+i);
        }
    }
}

重点解析

问:为什么要冲洗run()方法

因为run()是用封装被线程执行代码的 我们可以想要在多线程环境下运行的代码写在run()方法中 这些代码可以称之为任务

问: run()方法和start()方法的区别

run() 封装多线程执行的代码 封装任务代码的如果直接调用该方法 就是main方法线程中执行普通方法 也就是普通方法的调用

start() 启动线程开启一个新线程 在新县城中执行代码 由jvm调用此线程的run方法执行run中的任务代码

实现Runnalbe接口

Thread构造方法

Thread(Runnable target) 分配一个新的Thread对象

Thread(Runnable target,String name) 分配一个新的Thread对象

Runnable接口中方法

run() 任务方法内部编写要在多线程环境下执行的代码

实现步骤

定义一个类MyRunnable实现Runnale接口

在Myrunnable类中重写run()方法 编写需要在多线程环境下运行的代码

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class NetworkAPIUtils {
    /**
     * 发送GET请求
     * @param url 请求的URL
     * @return 响应结果
     * @throws IOException
     */
    public static String sendGetRequest(String url) throws IOException {
        String result = "";
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000); // 设置连接超时
            conn.setReadTimeout(5000); // 设置读取超时
            conn.connect(); // 建立连接
            if (conn.getResponseCode() == 200) {
                is = conn.getInputStream();
                byte[] buffer = new byte[1024];
                int len = -1;
                while ((len = is.read(buffer)) != -1) {
                    result += new String(buffer, 0, len);
                }
            }
        } finally {
            if (is != null) {
                is.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
        return result;
    }

    /**
     * 发送POST请求
     * @param url 请求的URL
     * @param postData POST请求体数据
     * @return 响应结果
     * @throws IOException
     */
    public static String sendPostRequest(String url, String postData) throws IOException {
        String result = "";
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(5000); // 设置连接超时
            conn.setReadTimeout(5000); // 设置读取超时
            conn.setDoOutput(true); // 设置该连接是可以输出数据
            conn.setRequestProperty("Content-Type", "application/json"); // 设置请求头的内容类型
            conn.connect(); // 建立连接
            conn.getOutputStream().write(postData.getBytes()); // 发送POST请求体数据
            if (conn.getResponseCode() == 200) {
                is = conn.getInputStream();
                byte[] buffer = new byte[1024];
                int len = -1;
                while ((len = is.read(buffer)) != -1) {
                    result += new String(buffer, 0, len);
                }
            }
        } finally {
            if (is != null) {
                is.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        }
        return result;
    }
}

使用方法

try {
   // 发送GET请求
   String url1 = "https://webapi.domcer.com/match/mw";
   String response1 = NetworkAPIUtils.sendGetRequest(url1);
   System.out.println(response1);

   // 发送POST请求
   String url2 = "https://jsonplaceholder.typicode.com/posts";
   String postData = "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}";
   String response2 = NetworkAPIUtils.sendPostRequest(url2, postData);
   System.out.println(response2);
} catch (IOException e) {
       e.printStackTrace();
}


import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

export default class ApiClient {
  private axiosInstance: AxiosInstance;

  constructor(config?: AxiosRequestConfig) {
    this.axiosInstance = axios.create(config);
  }

  public async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.axiosInstance.get(url, config);
    return response.data;
  }

  public async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.axiosInstance.post(url, data, config);
    return response.data;
  }

  public async put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.axiosInstance.put(url, data, config);
    return response.data;
  }

  public async delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.axiosInstance.delete(url, config);
    return response.data;
  }
}

使用

import ApiClient from "../utils/apiClient";
 const api = new ApiClient({ baseURL: 'https://api.example.com' });
 export default async function getUser() {
     return await api.get('/users');
}

import nodemailer from "nodemailer";

interface MailOptions {
  to: string;
  subject: string;
  text?: string;
  html?: string;
}

export class EmailUtil {
  private static readonly transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      user: process.env.EMAIL_USER,
      pass: process.env.EMAIL_PASSWORD,
    },
  });

  public static async sendEmail(options: MailOptions): Promise<void> {
    const mailOptions: nodemailer.SendMailOptions = {
      from: process.env.EMAIL_USER,
      to: options.to,
      subject: options.subject,
    };

    if (options.text) {
      mailOptions.text = options.text;
    }

    if (options.html) {
      mailOptions.html = options.html;
    }

    try {
      await this.transporter.sendMail(mailOptions);
      console.log(`Email sent to ${options.to}: ${options.subject}`);
    } catch (error) {
      console.error(`Error sending email to ${options.to}: ${error}`);
    }
  }
}

使用方法


//使用方法
 await EmailUtil.sendEmail({
     to: "recipient@example.com",
     subject: "Test Email",
     text: "This is a test email.",
});

import { CronJob } from 'cron';

class CronUtil {
  // jobMap记录了所有的CronJob实例
  private static jobMap: Map<string, CronJob> = new Map<string, CronJob>();

  /**
   * 添加一个定时任务
   * @param name 任务名称,用于唯一标识任务
   * @param cronTime cron表达式
   * @param onTick 定时任务回调函数
   * @param onComplete 任务完成回调函数
   */
  static addJob(name: string, cronTime: string, onTick: () => void, onComplete?: () => void): void {
    if (this.jobMap.has(name)) {
      throw new Error(`Cron job with name ${name} already exists`);
    }

    const job = new CronJob(cronTime, onTick, onComplete, true);
    this.jobMap.set(name, job);
  }

  /**
   * 启动指定名称的任务
   * @param name 任务名称
   */
  static startJob(name: string): void {
    const job = this.jobMap.get(name);
    if (!job) {
      throw new Error(`No cron job found with name ${name}`);
    }

    if (!job.running) {
      job.start();
    }
  }

  /**
   * 停止指定名称的任务
   * @param name 任务名称
   */
  static stopJob(name: string): void {
    const job = this.jobMap.get(name);
    if (!job) {
      throw new Error(`No cron job found with name ${name}`);
    }

    if (job.running) {
      job.stop();
    }
  }

  /**
   * 停止所有任务
   */
  static stopAllJobs(): void {
    for (const [_, job] of this.jobMap) {
      job.stop();
    }
  }
}

export default CronUtil;

使用方式

//任务调度器
CronUtil.addJob('myJob', '*/5 * * * * *', () => {
   console.log('Job executed!');
});

CronUtil.startJob('myJob');