当前位置:首页 > 技术分析 > 正文内容

eggjs如何实现批量替换word合同模板中的变量?

ruisui882周前 (04-11)技术分析13

场景:我司供应商有上万家,每家都需要签定合同,公司一般会保存word合同模板,有新供应商需要签约时,希望可以自动替换word合同的乙方名称并生成一份合同PDF,请问通过eggjs该如何实现?

在 Egg.js 中实现 Word 合同模板的自动替换并生成PDF,可以通过以下方案高效完成:


技术方案

使用 docxtemplater 库(专为模板替换设计,支持复杂格式保留) + 文件流处理,具体步骤如下:


实现步骤

1. 创建 Word 模板

在 Word 模板中定义占位符(如 {{supplierName}}):

合同甲方:XX公司
合同乙方:{{supplierName}}
合同条款:...

2. 安装依赖

npm install docxtemplater pizzip  # 核心依赖

3. 编写核心替换逻辑

// app/service/docService.js
const Service = require('egg').Service;
const Docxtemplater = require('docxtemplater');
const PizZip = require('pizzip');
const fs = require('fs').promises;
const path = require('path');

class DocService extends Service {
  async generateContract(supplierName, templateName = 'default.docx') {
    // 读取模板文件
    const templatePath = path.join(this.config.baseDir, 'app/templates', templateName);
    const fileBuffer = await fs.readFile(templatePath);

    // 初始化文档处理器
    const zip = new PizZip(fileBuffer);
    const doc = new Docxtemplater(zip, {
      paragraphLoop: true,
      linebreaks: true,
    });

    // 注入数据并渲染
    doc.setData({ supplierName });
    try {
      doc.render();
    } catch (error) {
      this.ctx.logger.error('模板渲染失败:', error);
      throw new Error('合同生成失败,请检查模板格式');
    }

    // 生成最终文件
    return doc.getZip().generate({ type: 'nodebuffer' });
  }
}

module.exports = DocService;

4. 控制器处理请求

// app/controller/contract.js
const Controller = require('egg').Controller;

class ContractController extends Controller {
  async generate() {
    const { ctx } = this;
    const { supplierName, templateName } = ctx.request.body;

    // 校验参数
    if (!supplierName) {
      ctx.status = 400;
      return ctx.body = { error: '供应商名称不能为空' };
    }

    try {
      // 调用服务生成文档
      const buffer = await ctx.service.docService.generateContract(supplierName, templateName);

      // 返回文件流
      ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
      ctx.set('Content-Disposition', `attachment; filename="${encodeURIComponent(supplierName)}_合同.docx"`);
      ctx.body = buffer;
    } catch (error) {
      ctx.status = 500;
      ctx.body = { error: error.message || '合同生成失败' };
    }
  }
}

module.exports = ContractController;

5. 路由配置

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.post('/api/generate-contract', controller.contract.generate);
};

6. 配置文件存储

  • 将模板文件存放在 app/templates 目录
  • 配置安全限制(防止非法模板访问):
// config/config.default.js
config.static = {
  dir: [
    { prefix: '/templates', dir: path.join(appInfo.baseDir, 'app/templates') },
  ],
  // 禁止直接访问模板文件
  dynamic: false,
};

高级优化方案

1. 模板管理功能

// 添加模板上传接口
router.post('/api/upload-template', controller.contract.uploadTemplate);

// 控制器代码
async uploadTemplate() {
  const { ctx } = this;
  const file = ctx.request.files[0];
  const { name } = ctx.request.body;

  // 移动文件到模板目录
  await fs.move(file.filepath, path.join(this.config.baseDir, 'app/templates', name));
  ctx.body = { success: true };
}

2. 结合 PDF 转换

复用之前的 LibreOffice 方案:

async generatePdf() {
  // 生成 Word 文件
  const wordBuffer = await this.generateContract(...);

  // 保存临时文件
  const tempPath = '/tmp/contract.docx';
  await fs.writeFile(tempPath, wordBuffer);

  // 调用 LibreOffice 转换
  const cmd = `libreoffice --headless --convert-to pdf --outdir /tmp ${tempPath}`;
  await execPromise(cmd);

  // 读取并返回 PDF
  return fs.readFile('/tmp/contract.pdf');
}

3. 异步队列处理

使用 egg-bull 插件处理高并发:

// 创建任务队列
app.bull.define('generate_contract', async job => {
  const { supplierName } = job.data;
  return ctx.service.docService.generateContract(supplierName);
});

// 控制器中提交任务
async generateAsync() {
  const job = await this.app.bull.add('generate_contract', {
    supplierName: ctx.request.body.supplierName,
  });
  ctx.body = { jobId: job.id };
}

安全注意事项

  1. 输入校验

if (!/^[\u4e00-\u9fa5a-zA-Z0-9]{2,20}$/.test(supplierName)) { throw new Error('供应商名称包含非法字符'); }

  1. 模板沙箱:限制模板目录访问权限
  2. 防注入攻击:禁止使用 {{ }} 外的动态语法

部署建议

  • 使用 Docker 部署保证 LibreOffice 环境一致性
  • 模板文件存储到云存储(如 OSS)实现分布式访问
  • 添加 API 限流(egg-ratelimiter)

通过以上方案,可实现每小时处理数千份合同的生成需求,且能保证文档格式的严格一致性。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/3442.html

标签: js文档
分享给朋友:

“eggjs如何实现批量替换word合同模板中的变量?” 的相关文章

「图解」父子组件通过 props 进行数据交互的方法

1.组件化开发,经常有这样的一个场景,就是父组件通过 Ajax 获取数据,传递给子组件,如何通过 props 进行数据交互来实现,便是本图解的重点。2.代码的结构3.具体代码 ①在父组件 data 中存放数据 ms。 ②将父组件 data 中的数据 ms 绑定到子组件中的属性 ms。 ③子组件在 p...

快速上手React

web前端三大主流框架1、Angular大家眼里比较牛的框架,甚至有人说三大框架中只有它能称得上一个完整的框架,因为它包含的东西比较完善,包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能。对于刚开始学习使用框架的小伙伴们,可以推荐这个框架,学会之后简直能颠覆之前你对前端开发的认...

学会使用Vue JSX,一车老干妈都是你的

作者:子君转发链接:https://mp.weixin.qq.com/s/eAOivpHeowLShfwPfW8-BA?君自前端来,应知前端事。需求时时变,bug改不完。?连续几篇文章,每篇都有女神,被老铁给吐槽了,今天不提了女神了,反正女神都是别人的(扎心了)。这两天小编看了腾讯与老干妈的事情,晚...

Git 分支管理策略汇总

最近,团队新入职了一些小伙伴,在开发过程中,他们问我 Git 分支是如何管理的,以及应该怎么提交代码?我大概说了一些规则,但仔细想来,好像也并没有形成一个清晰规范的流程。所以查了一些资料,总结出下面这篇文章,一共包含四种常见的分支管理策略,分享给大家。Git flow在这种模式下,主要维护了两类分支...

Java教程:gitlab-使用入门

1 导读本教程主要讲解了GitLab在项目的环境搭建和基本的使用,可以帮助大家在企业中能够自主搭建GitLab服务,并且可以GitLab中的组、权限、项目自主操作GitLab简介GitLab环境搭建GitLab基本使用(组、权限、用户、项目)2 GitLab简介GitLab是整个DevOps生命周期...

前后端分离自动化运维平台开发

运维平台采用前后端分离:前端vue,框架vue-element-admin;后端python,框架django-rest-framework.目前运维平台模块如下:1、 CMDB管理应用管理、环境管理、开发语言管理、产品项目管理、资产管理2、 构建发布持续构建、持续部署、Jar工程依赖构建3、 容器...