forked from baskbuddy/zhangshun
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
74 lines
6.1 KiB
74 lines
6.1 KiB
package com.mydreamjob.onlineresume;
|
|
|
|
import org.springframework.stereotype.Component;
|
|
import java.io.*;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.*;
|
|
import java.util.zip.*;
|
|
|
|
/** 生成真正的 OOXML 文档;用户文本只作为文字写入,无外链、宏或文件读取。 */
|
|
@Component
|
|
public class OnlineResumeDocxWriter {
|
|
public byte[] write(OnlineResumeContent c) {
|
|
List<String> lines = new ArrayList<>();
|
|
var b = c.getBasicInfo();
|
|
lines.add(b.getRealName() == null || b.getRealName().isBlank() ? "个人简历" : b.getRealName() + "的简历");
|
|
lines.add(join(b.getPhone(), b.getEmail(), b.getLocation()));
|
|
lines.add(join(b.getEducation(), b.getSchool(), b.getGraduationYear()));
|
|
var j = c.getJobIntention();
|
|
lines.add("求职意向"); lines.add(join(j.getTargetPosition(), String.join("、", j.getPreferredCities())));
|
|
if (j.getExpectedSalaryMin() != null || j.getExpectedSalaryMax() != null)
|
|
lines.add("期望月薪(元):" + Objects.toString(j.getExpectedSalaryMin(), "不限") + "—" + Objects.toString(j.getExpectedSalaryMax(), "不限"));
|
|
lines.add(join(String.join("、", j.getEmploymentTypes()), String.join("、", j.getPreferredIndustries()), String.join("、", j.getPreferredJobTypes())));
|
|
if (j.getJobSeekingStatus() != null) lines.add("求职状态:" + switch (j.getJobSeekingStatus()) {
|
|
case LOOKING -> "正在求职"; case OPEN_TO_OPPORTUNITIES -> "考虑机会";
|
|
case NOT_LOOKING -> "暂不求职"; case STUDENT -> "在校学生";
|
|
});
|
|
if (j.getAvailableFrom() != null) lines.add("可到岗日期:" + j.getAvailableFrom());
|
|
if (j.getEmploymentStatus() != null) lines.add("当前状态:" + switch (j.getEmploymentStatus()) {
|
|
case EMPLOYED -> "在职"; case UNEMPLOYED -> "离职/未就业"; case STUDENT -> "在校";
|
|
});
|
|
if (j.isRemoteAllowed()) lines.add("接受远程工作");
|
|
lines.add("教育经历");
|
|
for (var e : c.getEducationExperiences()) { lines.add(join(e.getSchool(), e.getMajor(), e.getDegree(), dates(e))); lines.add(e.getDescription()); }
|
|
lines.add("工作经历");
|
|
for (var e : c.getWorkExperiences()) {
|
|
String type = "INTERNSHIP".equals(e.getExperienceType()) ? "实习"
|
|
: "FULL_TIME".equals(e.getExperienceType()) ? "正式工作" : "类型未区分";
|
|
lines.add(join(e.getCompany(), e.getPosition(), type, dates(e))); lines.add(e.getDescription());
|
|
}
|
|
lines.add("项目经历");
|
|
for (var e : c.getProjectExperiences()) { lines.add(join(e.getName(), e.getRole(), dates(e))); lines.add(e.getDescription()); }
|
|
lines.add("技能特长");
|
|
for (var e : c.getSkills()) {
|
|
String level = e.getLevel() == null ? null : switch (e.getLevel()) {
|
|
case BEGINNER -> "入门"; case FAMILIAR -> "熟悉"; case PROFICIENT -> "熟练"; case EXPERT -> "精通";
|
|
};
|
|
lines.add(join(e.getName(), level, e.getYears() == null ? null : e.getYears() + "年")); lines.add(e.getDescription());
|
|
}
|
|
lines.add("自我评价"); lines.add(c.getSelfEvaluation());
|
|
StringBuilder body = new StringBuilder();
|
|
for (String text : lines) {
|
|
if (text == null || text.isBlank()) continue;
|
|
for (String line : text.split("\\R", -1)) body.append("<w:p><w:pPr><w:spacing w:after=\"120\"/></w:pPr><w:r><w:rPr><w:rFonts w:ascii=\"Calibri\" w:eastAsia=\"Microsoft YaHei\"/><w:sz w:val=\"22\"/></w:rPr><w:t xml:space=\"preserve\">")
|
|
.append(xml(line)).append("</w:t></w:r></w:p>");
|
|
}
|
|
body.append("<w:sectPr><w:pgSz w:w=\"11906\" w:h=\"16838\"/><w:pgMar w:top=\"1134\" w:right=\"1134\" w:bottom=\"1134\" w:left=\"1134\"/></w:sectPr>");
|
|
try (var out = new ByteArrayOutputStream(); var zip = new ZipOutputStream(out, StandardCharsets.UTF_8)) {
|
|
add(zip, "[Content_Types].xml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/><Default Extension=\"xml\" ContentType=\"application/xml\"/><Override PartName=\"/word/document.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/></Types>");
|
|
add(zip, "_rels/.rels", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"word/document.xml\"/></Relationships>");
|
|
add(zip, "word/document.xml", "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><w:document xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"><w:body>" + body + "</w:body></w:document>");
|
|
zip.finish(); return out.toByteArray();
|
|
} catch (IOException e) { throw new IllegalStateException("cannot generate resume document", e); }
|
|
}
|
|
private String dates(OnlineResumeContent.DatedEntry e) { return Objects.toString(e.getStartDate(), "") + "—" + (e.isOngoing() ? "至今" : Objects.toString(e.getEndDate(), "")); }
|
|
private String join(Object... fields) { return Arrays.stream(fields).filter(Objects::nonNull).map(Object::toString).filter(s -> !s.isBlank()).collect(java.util.stream.Collectors.joining(" · ")); }
|
|
private String xml(String s) {
|
|
StringBuilder clean = new StringBuilder();
|
|
s.codePoints().filter(c -> c == 9 || c == 10 || c == 13 || c >= 32 && c <= 0xd7ff || c >= 0xe000 && c <= 0xfffd || c >= 0x10000 && c <= 0x10ffff).forEach(clean::appendCodePoint);
|
|
return clean.toString().replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """).replace("'", "'");
|
|
}
|
|
private void add(ZipOutputStream zip, String name, String text) throws IOException {
|
|
zip.putNextEntry(new ZipEntry(name)); zip.write(text.getBytes(StandardCharsets.UTF_8)); zip.closeEntry();
|
|
}
|
|
}
|
|
|