feat: 新增开放接口,支持通过模板名称和项目名称生成文案

- 新增 HtmlUtils 工具类,支持 HTML 转纯文本
- 新增 selectTtCodeByCodeName 方法,支持按名称查询源码
- 新增 /office/copyTemplate/open/generate 开放接口
  无需登录,参数:templateName(模板名称)、codeName(项目名称)
  返回:填充占位符后的完整文案内容

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
王鹏
2026-04-10 19:28:15 +08:00
parent 6940541216
commit eb6339c9a7
6 changed files with 175 additions and 1 deletions

View File

@@ -0,0 +1,60 @@
package com.ruoyi.common.utils;
import java.util.regex.Pattern;
/**
* HTML工具类
*
* @author ruoyi
*/
public class HtmlUtils {
/**
* 将HTML内容转换为纯文本
* 移除所有HTML标签将&nbsp;等转义字符转换为普通空格,合并多余空白符
*/
public static String htmlToText(String html) {
if (StringUtils.isEmpty(html)) {
return "";
}
String text = html;
// 移除script和style标签及其内容
text = Pattern.compile("<script[^>]*?>.*?</script>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(text).replaceAll("");
text = Pattern.compile("<style[^>]*?>.*?</style>", 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("&nbsp;", " ");
text = text.replaceAll("&amp;", "&");
text = text.replaceAll("&lt;", "<");
text = text.replaceAll("&gt;", ">");
text = text.replaceAll("&quot;", "\"");
text = text.replaceAll("&apos;", "'");
text = text.replaceAll("&#39;", "'");
text = text.replaceAll("&mdash;", "");
text = text.replaceAll("&ndash;", "");
text = text.replaceAll("&hellip;", "");
text = text.replaceAll("&ldquo;", """);
text = text.replaceAll("&rdquo;", """);
text = text.replaceAll("&lsquo;", "'");
text = text.replaceAll("&rsquo;", "'");
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();
}
}

View File

@@ -11,12 +11,18 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType; 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.domain.TtCopyTemplate;
import com.ruoyi.office.service.ITtCodeService;
import com.ruoyi.office.service.ITtCopyTemplateService; import com.ruoyi.office.service.ITtCopyTemplateService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
@@ -34,6 +40,9 @@ public class TtCopyTemplateController extends BaseController {
@Autowired @Autowired
private ITtCopyTemplateService ttCopyTemplateService; private ITtCopyTemplateService ttCopyTemplateService;
@Autowired
private ITtCodeService ttCodeService;
/** /**
* 查询文案模板列表 * 查询文案模板列表
*/ */
@@ -103,4 +112,71 @@ public class TtCopyTemplateController extends BaseController {
public AjaxResult remove(@PathVariable Long[] templateIds) { public AjaxResult remove(@PathVariable Long[] templateIds) {
return toAjax(ttCopyTemplateService.deleteTtCopyTemplateByTemplateIds(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("</p>", "</p>\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![](" + item.getFileUrl() + ")\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);
}
} }

View File

@@ -13,12 +13,20 @@ public interface TtCodeMapper
{ {
/** /**
* 查询源码管理 * 查询源码管理
* *
* @param codeId 源码管理主键 * @param codeId 源码管理主键
* @return 源码管理 * @return 源码管理
*/ */
public TtCode selectTtCodeByCodeId(Long codeId); public TtCode selectTtCodeByCodeId(Long codeId);
/**
* 根据项目名称查询源码管理
*
* @param codeName 项目名称
* @return 源码管理
*/
public TtCode selectTtCodeByCodeName(String codeName);
/** /**
* 查询源码管理列表 * 查询源码管理列表
* *

View File

@@ -19,6 +19,14 @@ public interface ITtCodeService {
*/ */
public TtCode selectTtCodeByCodeId(Long codeId); public TtCode selectTtCodeByCodeId(Long codeId);
/**
* 根据项目名称查询源码管理
*
* @param codeName 项目名称
* @return 源码管理
*/
public TtCode selectTtCodeByCodeName(String codeName);
/** /**
* 查询源码管理列表 * 查询源码管理列表
* *

View File

@@ -58,6 +58,23 @@ public class TtCodeServiceImpl implements ITtCodeService {
return ttCode; 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;
}
/** /**
* 查询源码管理列表 * 查询源码管理列表
* *

View File

@@ -43,6 +43,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectTtCodeVo"/> <include refid="selectTtCodeVo"/>
where code_id = #{codeId} where code_id = #{codeId}
</select> </select>
<select id="selectTtCodeByCodeName" parameterType="String" resultMap="TtCodeResult">
<include refid="selectTtCodeVo"/>
where code_name = #{codeName} limit 1
</select>
<insert id="insertTtCode" parameterType="TtCode"> <insert id="insertTtCode" parameterType="TtCode">
insert into tt_code insert into tt_code