From eb6339c9a74563d8c1e6ebd76635a16f97eabd82 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?=
Date: Fri, 10 Apr 2026 19:28:15 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=BC=80=E6=94=BE?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87?=
=?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=90=8D=E7=A7=B0=E5=92=8C=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E5=90=8D=E7=A7=B0=E7=94=9F=E6=88=90=E6=96=87=E6=A1=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 HtmlUtils 工具类,支持 HTML 转纯文本
- 新增 selectTtCodeByCodeName 方法,支持按名称查询源码
- 新增 /office/copyTemplate/open/generate 开放接口
无需登录,参数:templateName(模板名称)、codeName(项目名称)
返回:填充占位符后的完整文案内容
Co-Authored-By: Claude Opus 4.6
---
.../com/ruoyi/common/utils/HtmlUtils.java | 60 +++++++++++++++
.../controller/TtCopyTemplateController.java | 76 +++++++++++++++++++
.../com/ruoyi/office/mapper/TtCodeMapper.java | 10 ++-
.../ruoyi/office/service/ITtCodeService.java | 8 ++
.../service/impl/TtCodeServiceImpl.java | 17 +++++
.../resources/mapper/office/TtCodeMapper.xml | 5 ++
6 files changed, 175 insertions(+), 1 deletion(-)
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlUtils.java
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlUtils.java
new file mode 100644
index 0000000..88fc1bd
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlUtils.java
@@ -0,0 +1,60 @@
+package com.ruoyi.common.utils;
+
+import java.util.regex.Pattern;
+
+/**
+ * HTML工具类
+ *
+ * @author ruoyi
+ */
+public class HtmlUtils {
+
+ /**
+ * 将HTML内容转换为纯文本
+ * 移除所有HTML标签,将 等转义字符转换为普通空格,合并多余空白符
+ */
+ public static String htmlToText(String html) {
+ if (StringUtils.isEmpty(html)) {
+ return "";
+ }
+ String text = html;
+ // 移除script和style标签及其内容
+ text = Pattern.compile("", Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(text).replaceAll("");
+ text = Pattern.compile("", Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(text).replaceAll("");
+ // 移除HTML注释
+ text = Pattern.compile("", Pattern.DOTALL).matcher(text).replaceAll("");
+ // 移除所有HTML标签
+ text = Pattern.compile("<[^>]+>").matcher(text).replaceAll("");
+ // 转义字符转换
+ text = text.replaceAll(" ", " ");
+ text = text.replaceAll("&", "&");
+ text = text.replaceAll("<", "<");
+ text = text.replaceAll(">", ">");
+ text = text.replaceAll(""", "\"");
+ text = text.replaceAll("'", "'");
+ text = text.replaceAll("'", "'");
+ text = text.replaceAll("—", "—");
+ text = text.replaceAll("–", "–");
+ text = text.replaceAll("…", "…");
+ text = text.replaceAll("“", """);
+ text = text.replaceAll("”", """);
+ text = text.replaceAll("‘", "'");
+ text = text.replaceAll("’", "'");
+ text = text.replaceAll("\\r\\n", "\n");
+ text = text.replaceAll("\\r", "\n");
+ text = text.replaceAll("\\n\\n+", "\n\n");
+ // 合并每行首尾空白并移除全空行
+ String[] lines = text.split("\n");
+ StringBuilder sb = new StringBuilder();
+ for (String line : lines) {
+ String trimmed = line.trim();
+ if (trimmed.length() > 0) {
+ if (sb.length() > 0) {
+ sb.append("\n");
+ }
+ sb.append(trimmed);
+ }
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-office/src/main/java/com/ruoyi/office/controller/TtCopyTemplateController.java b/ruoyi-office/src/main/java/com/ruoyi/office/controller/TtCopyTemplateController.java
index 7b1001a..7bbab5e 100644
--- a/ruoyi-office/src/main/java/com/ruoyi/office/controller/TtCopyTemplateController.java
+++ b/ruoyi-office/src/main/java/com/ruoyi/office/controller/TtCopyTemplateController.java
@@ -11,12 +11,18 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.HtmlUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.office.domain.TtCode;
import com.ruoyi.office.domain.TtCopyTemplate;
+import com.ruoyi.office.service.ITtCodeService;
import com.ruoyi.office.service.ITtCopyTemplateService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
@@ -34,6 +40,9 @@ public class TtCopyTemplateController extends BaseController {
@Autowired
private ITtCopyTemplateService ttCopyTemplateService;
+ @Autowired
+ private ITtCodeService ttCodeService;
+
/**
* 查询文案模板列表
*/
@@ -103,4 +112,71 @@ public class TtCopyTemplateController extends BaseController {
public AjaxResult remove(@PathVariable Long[] templateIds) {
return toAjax(ttCopyTemplateService.deleteTtCopyTemplateByTemplateIds(templateIds));
}
+
+ /**
+ * 开放接口:通过文案模板名称和项目名称生成文案
+ * 无需登录,浏览器可直接访问
+ */
+ @Anonymous
+ @GetMapping("/open/generate")
+ public AjaxResult generateByOpen(@RequestParam String templateName, @RequestParam String codeName) {
+ // 1. 查询模板
+ TtCopyTemplate template = ttCopyTemplateService.selectEnabledTemplates().stream()
+ .filter(t -> templateName.equals(t.getTemplateName()))
+ .findFirst()
+ .orElse(null);
+ if (template == null) {
+ return error("未找到名称为【" + templateName + "】的启用模板");
+ }
+ // 2. 查询源码
+ TtCode code = ttCodeService.selectTtCodeByCodeName(codeName);
+ if (code == null) {
+ return error("未找到名称为【" + codeName + "】的源码项目");
+ }
+ // 3. 提取变量
+ String fullCodeName = code.getCodeName() != null ? code.getCodeName() : "";
+ String projectCode = "";
+ String projectName = fullCodeName;
+ if (fullCodeName.length() > 0) {
+ java.util.regex.Matcher m = java.util.regex.Pattern.compile("【(.+?)】").matcher(fullCodeName);
+ if (m.find()) {
+ projectCode = m.group(1);
+ }
+ projectName = fullCodeName.replaceAll("【.+?】", "").trim();
+ if (projectName.startsWith("基于")) {
+ java.util.regex.Matcher nameMatcher = java.util.regex.Pattern.compile("实现的(.+?)$").matcher(projectName);
+ if (nameMatcher.find()) {
+ projectName = nameMatcher.group(1);
+ }
+ }
+ }
+ // 4. 生成纯文本描述
+ String plainDesc = "";
+ if (StringUtils.isNotEmpty(code.getCodeDesc())) {
+ String codeDesc = code.getCodeDesc().replaceAll("
", "\n");
+ plainDesc = HtmlUtils.htmlToText(codeDesc.trim());
+ }
+ // 5. 生成截图列表
+ String screenshots = "";
+ int idx = 0;
+ if (code.getFileList() != null && !code.getFileList().isEmpty()) {
+ for (var item : code.getFileList()) {
+ screenshots += (++idx) + "." + item.getFileName() + "\n + ")\n\n";
+ }
+ } else {
+ screenshots = "请前往微信小程序:南音源码库。查看项目详情!\n\n";
+ }
+ // 6. 替换占位符
+ String content = template.getTemplateBody();
+ content = content.replace("{codeName}", fullCodeName);
+ content = content.replace("{projectCode}", projectCode);
+ content = content.replace("{projectName}", projectName);
+ content = content.replace("{codeDesc}", plainDesc);
+ content = content.replace("{codeEnvironment}", code.getCodeEnvironment() != null ? code.getCodeEnvironment() : "");
+ content = content.replace("{codeTechnology}", code.getCodeTechnology() != null ? code.getCodeTechnology() : "");
+ content = content.replace("{diskLink}", code.getDiskLink() != null ? code.getDiskLink() : "");
+ content = content.replace("{screenshots}", screenshots);
+
+ return success(content);
+ }
}
\ No newline at end of file
diff --git a/ruoyi-office/src/main/java/com/ruoyi/office/mapper/TtCodeMapper.java b/ruoyi-office/src/main/java/com/ruoyi/office/mapper/TtCodeMapper.java
index 91f59e8..153affb 100644
--- a/ruoyi-office/src/main/java/com/ruoyi/office/mapper/TtCodeMapper.java
+++ b/ruoyi-office/src/main/java/com/ruoyi/office/mapper/TtCodeMapper.java
@@ -13,12 +13,20 @@ public interface TtCodeMapper
{
/**
* 查询源码管理
- *
+ *
* @param codeId 源码管理主键
* @return 源码管理
*/
public TtCode selectTtCodeByCodeId(Long codeId);
+ /**
+ * 根据项目名称查询源码管理
+ *
+ * @param codeName 项目名称
+ * @return 源码管理
+ */
+ public TtCode selectTtCodeByCodeName(String codeName);
+
/**
* 查询源码管理列表
*
diff --git a/ruoyi-office/src/main/java/com/ruoyi/office/service/ITtCodeService.java b/ruoyi-office/src/main/java/com/ruoyi/office/service/ITtCodeService.java
index 76116a0..134f643 100644
--- a/ruoyi-office/src/main/java/com/ruoyi/office/service/ITtCodeService.java
+++ b/ruoyi-office/src/main/java/com/ruoyi/office/service/ITtCodeService.java
@@ -19,6 +19,14 @@ public interface ITtCodeService {
*/
public TtCode selectTtCodeByCodeId(Long codeId);
+ /**
+ * 根据项目名称查询源码管理
+ *
+ * @param codeName 项目名称
+ * @return 源码管理
+ */
+ public TtCode selectTtCodeByCodeName(String codeName);
+
/**
* 查询源码管理列表
*
diff --git a/ruoyi-office/src/main/java/com/ruoyi/office/service/impl/TtCodeServiceImpl.java b/ruoyi-office/src/main/java/com/ruoyi/office/service/impl/TtCodeServiceImpl.java
index dcc494a..e603e17 100644
--- a/ruoyi-office/src/main/java/com/ruoyi/office/service/impl/TtCodeServiceImpl.java
+++ b/ruoyi-office/src/main/java/com/ruoyi/office/service/impl/TtCodeServiceImpl.java
@@ -58,6 +58,23 @@ public class TtCodeServiceImpl implements ITtCodeService {
return ttCode;
}
+ /**
+ * 根据项目名称查询源码管理
+ *
+ * @param codeName 项目名称
+ * @return 源码管理
+ */
+ @Override
+ public TtCode selectTtCodeByCodeName(String codeName) {
+ TtCode ttCode = ttCodeMapper.selectTtCodeByCodeName(codeName);
+ if (ttCode != null) {
+ TtFile file = new TtFile();
+ file.setCodeName(ttCode.getCodeName());
+ ttCode.setFileList(fileMapper.selectTtFileList(file));
+ }
+ return ttCode;
+ }
+
/**
* 查询源码管理列表
*
diff --git a/ruoyi-office/src/main/resources/mapper/office/TtCodeMapper.xml b/ruoyi-office/src/main/resources/mapper/office/TtCodeMapper.xml
index cf33b0e..a47af60 100644
--- a/ruoyi-office/src/main/resources/mapper/office/TtCodeMapper.xml
+++ b/ruoyi-office/src/main/resources/mapper/office/TtCodeMapper.xml
@@ -43,6 +43,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where code_id = #{codeId}
+
+
insert into tt_code