fix: code review — 7 issues resolved

(C1) Add dimensionScores/radarData/passed columns to AssessmentSession
(C2) Mock DataSource in service.spec.ts + app.e2e-spec.ts
(C3) Mock AuditLogService in controller.spec.ts
(C4) Rewrite deleteSession tests for dataSource.transaction
(I1) batchDeleteSessions uses transaction with certificate cleanup
(I2) extractDimensionScores reads from session property
(I3/I5) PDF generator supports multi-page + newline splitting
(I4) findOne inside transaction uses deleteCondition
This commit is contained in:
Developer
2026-05-19 10:06:30 +08:00
parent 7f8e7214b3
commit 82a9e75842
7 changed files with 164 additions and 63 deletions
@@ -96,7 +96,7 @@ export class ExportService {
}
private extractDimensionScores(session: AssessmentSession): any[][] {
const scores = session.templateJson?.dimensionScores || session.finalReport;
const scores = (session as any).dimensionScores;
if (!scores) return [['未找到维度分数']];
if (typeof scores === 'string') {
+83 -48
View File
@@ -47,69 +47,105 @@ function textToHex(str: string): string {
export async function generateAssessmentPdf(options: PdfReportOptions): Promise<Buffer> {
const doc = await PDFDocument.create();
const page = doc.addPage([595.28, 841.89]);
const ctx = doc.context;
const fontBytes = findFont();
if (fontBytes.length === 0) {
throw new Error('No CJK font found. Install Noto Sans SC or similar.');
}
const fontProgRef = ctx.nextRef();
ctx.assign(fontProgRef, ctx.flateStream(fontBytes));
const FONT_DESC_REF = { ref: null as any };
const FONT_REF = { ref: null as any };
const fontDescRef = ctx.nextRef();
ctx.assign(fontDescRef, ctx.obj({
Type: 'FontDescriptor',
FontName: 'CJKFont',
Flags: 4,
FontBBox: [0, -300, 1000, 1000],
ItalicAngle: 0,
Ascent: 900,
Descent: -200,
CapHeight: 800,
StemV: 80,
FontFile2: fontProgRef,
}));
function prepareFont(page: any) {
const fontProgRef = ctx.nextRef();
ctx.assign(fontProgRef, ctx.flateStream(fontBytes));
const cidFontRef = ctx.nextRef();
ctx.assign(cidFontRef, ctx.obj({
Type: 'Font',
Subtype: 'CIDFontType2',
BaseFont: 'CJKFont',
CIDSystemInfo: ctx.obj({
Registry: 'Adobe',
Ordering: 'Identity',
Supplement: 0,
}),
FontDescriptor: fontDescRef,
W: [0, [500]],
}));
const fontDescRef = ctx.nextRef();
ctx.assign(fontDescRef, ctx.obj({
Type: 'FontDescriptor',
FontName: 'CJKFont',
Flags: 4,
FontBBox: [0, -300, 1000, 1000],
ItalicAngle: 0,
Ascent: 900,
Descent: -200,
CapHeight: 800,
StemV: 80,
FontFile2: fontProgRef,
}));
FONT_DESC_REF.ref = fontDescRef;
const fontRef = ctx.nextRef();
ctx.assign(fontRef, ctx.obj({
Type: 'Font',
Subtype: 'Type0',
BaseFont: 'CJKFont',
Encoding: 'Identity-H',
DescendantFonts: [cidFontRef],
}));
const cidFontRef = ctx.nextRef();
ctx.assign(cidFontRef, ctx.obj({
Type: 'Font',
Subtype: 'CIDFontType2',
BaseFont: 'CJKFont',
CIDSystemInfo: ctx.obj({
Registry: 'Adobe',
Ordering: 'Identity',
Supplement: 0,
}),
FontDescriptor: fontDescRef,
W: [0, [500]],
}));
const fontKey = page.node.newFontDictionaryKey('F1');
page.node.setFontDictionary(fontKey, fontRef);
const fontRef = ctx.nextRef();
ctx.assign(fontRef, ctx.obj({
Type: 'Font',
Subtype: 'Type0',
BaseFont: 'CJKFont',
Encoding: 'Identity-H',
DescendantFonts: [cidFontRef],
}));
FONT_REF.ref = fontRef;
const fontKey = page.node.newFontDictionaryKey('F1');
page.node.setFontDictionary(fontKey, fontRef);
}
let contentOps = '';
let y = 800;
const margin = 50;
const pageWidth = 595.28;
const pageHeight = 841.89;
const ctx = doc.context as any;
let currentPage = doc.addPage([pageWidth, pageHeight]);
let contentOps = '';
let y = pageHeight - 50;
prepareFont(currentPage);
function addFontToPage(page: any) {
const fontKey = page.node.newFontDictionaryKey('F1');
page.node.setFontDictionary(fontKey, FONT_REF.ref);
}
function ensureSpace(needed: number) {
const bottomMargin = 60;
if (y - needed < bottomMargin) {
const contentObj = ctx.flateStream(contentOps);
const contentRef = ctx.nextRef();
ctx.assign(contentRef, contentObj);
currentPage.node.addContentStream(contentRef);
currentPage = doc.addPage([pageWidth, pageHeight]);
addFontToPage(currentPage);
contentOps = '';
y = pageHeight - 50;
}
}
function addLine(text: string, size: number, bold: boolean = false) {
const hex = textToHex(text);
contentOps += `BT\n/F1 ${size} Tf\n1 0 0 1 ${margin} ${y} Tm\n<${hex}> Tj\nET\n`;
y -= size * 1.5;
const lines = text.split('\n');
for (const line of lines) {
ensureSpace(size * 1.5);
const hex = textToHex(line);
contentOps += `BT\n/F1 ${size} Tf\n1 0 0 1 ${margin} ${y} Tm\n<${hex}> Tj\nET\n`;
y -= size * 1.5;
}
}
function addSeparator() {
ensureSpace(12);
let hex = '';
for (let i = 0; i < 55; i++) hex += '002D';
contentOps += `BT\n/F1 8 Tf\n1 0 0 1 ${margin} ${y} Tm\n<${hex}> Tj\nET\n`;
@@ -125,10 +161,9 @@ export async function generateAssessmentPdf(options: PdfReportOptions): Promise<
addSeparator();
for (const section of options.sections) {
if (y < 60) break;
ensureSpace(30);
addLine(section.title, 12);
for (const line of section.lines) {
if (y < 40) break;
addLine(line, 10);
}
y -= 6;
@@ -140,7 +175,7 @@ export async function generateAssessmentPdf(options: PdfReportOptions): Promise<
const contentObj = ctx.flateStream(contentOps);
const contentRef = ctx.nextRef();
ctx.assign(contentRef, contentObj);
page.node.addContentStream(contentRef);
currentPage.node.addContentStream(contentRef);
return Buffer.from(await doc.save());
}