87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import * as crypto from 'crypto'
|
|
import { Injectable, Logger } from '@nestjs/common'
|
|
|
|
@Injectable()
|
|
export class ResumePdfService {
|
|
private readonly logger = new Logger(ResumePdfService.name)
|
|
|
|
async generatePdf(params: {
|
|
title: string
|
|
content: string
|
|
targetPosition?: string
|
|
userName?: string
|
|
}): Promise<Buffer> {
|
|
const { default: puppeteer } = await import('puppeteer')
|
|
const html = this.buildHtml(params)
|
|
const browser = await puppeteer.launch({
|
|
executablePath: '/root/.cache/puppeteer/chrome/linux-149.0.7827.22/chrome-linux64/chrome',
|
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
|
})
|
|
try {
|
|
const page = await browser.newPage()
|
|
await page.setContent(html, { waitUntil: 'load' })
|
|
const pdf = await page.pdf({
|
|
format: 'A4',
|
|
printBackground: true,
|
|
margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
|
|
})
|
|
return Buffer.from(pdf)
|
|
} finally {
|
|
await browser.close()
|
|
}
|
|
}
|
|
|
|
private escapeHtml(str: string): string {
|
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
.replace(/"/g, '"').replace(/'/g, ''')
|
|
}
|
|
|
|
private buildHtml(params: {
|
|
title: string
|
|
content: string
|
|
targetPosition?: string
|
|
userName?: string
|
|
}): string {
|
|
const contentHtml = this.escapeHtml(params.content)
|
|
.replace(/\n/g, '<br>')
|
|
.replace(/### (.+)/g, '<h3>$1</h3>')
|
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
|
|
return `<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
@page { size: A4; margin: 0; }
|
|
body {
|
|
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
font-size: 12pt; line-height: 1.6; color: #333; padding: 0; margin: 0;
|
|
}
|
|
.page { padding: 40px 30px; max-width: 210mm; margin: 0 auto; }
|
|
h1 { font-size: 22pt; color: #1a1a1a; margin-bottom: 4px; }
|
|
.subtitle { color: #666; font-size: 10pt; margin-bottom: 20px; }
|
|
h2 { font-size: 14pt; color: #2c6b9e; border-bottom: 2px solid #2c6b9e; padding-bottom: 4px; margin-top: 20px; }
|
|
h3 { font-size: 12pt; color: #333; margin-top: 12px; margin-bottom: 4px; }
|
|
strong { color: #1a1a1a; }
|
|
p { margin: 6px 0; }
|
|
ul { margin: 4px 0; padding-left: 20px; }
|
|
li { margin: 2px 0; }
|
|
.footer { margin-top: 30px; font-size: 9pt; color: #999; text-align: center; border-top: 1px solid #eee; padding-top: 10px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
<h1>${this.escapeHtml(params.title)}</h1>
|
|
<div class="subtitle">${params.targetPosition ? `目标岗位: ${this.escapeHtml(params.targetPosition)}` : ''}</div>
|
|
<div class="content">${contentHtml}</div>
|
|
<div class="footer">由 AI磁场·职引 生成</div>
|
|
</div>
|
|
</body>
|
|
</html>`
|
|
}
|
|
|
|
computeHash(content: string): string {
|
|
return crypto.createHash('md5').update(content).digest('hex')
|
|
}
|
|
}
|