feat: 优化代码生成及文案模板渲染

This commit is contained in:
王鹏
2026-07-29 16:17:49 +08:00
parent c631d058c1
commit e9d627df9c
15 changed files with 560 additions and 199 deletions

View File

@@ -103,26 +103,14 @@ public class TtCodeController extends BaseController
return toAjax(ttCodeService.deleteTtCodeByCodeIds(codeIds));
}
/**
* 转文章
*/
@PreAuthorize("@ss.hasPermi('office:code:edit')")
@Log(title = "源码管理", businessType = BusinessType.UPDATE)
@GetMapping("/transToArticle/{codeId}")
public AjaxResult transToArticle(@PathVariable Long codeId)
{
String msg = ttCodeService.transToArticle(codeId);
return success(msg);
}
/**
* 转文章
*/
@PreAuthorize("@ss.hasPermi('office:code:edit')")
@Log(title = "转文章(南音)", businessType = BusinessType.UPDATE)
@GetMapping("/transToArticle1/{codeId}")
public AjaxResult transToArticle1(@PathVariable Long codeId, @RequestParam String coverUrl) {
String msg = ttCodeService.transToArticle1(codeId, coverUrl);
public AjaxResult transToArticle1(@PathVariable Long codeId, @RequestParam Long templateId) {
String msg = ttCodeService.transToArticle1(codeId, templateId);
return success(msg);
}
}

View File

@@ -18,11 +18,9 @@ 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.TtFile;
import com.ruoyi.office.domain.TtCopyTemplate;
import com.ruoyi.office.service.CopyTemplateRenderer;
import com.ruoyi.office.service.ITtCodeService;
import com.ruoyi.office.service.ITtCopyTemplateService;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -44,6 +42,9 @@ public class TtCopyTemplateController extends BaseController {
@Autowired
private ITtCodeService ttCodeService;
@Autowired
private CopyTemplateRenderer copyTemplateRenderer;
/**
* 查询文案模板列表
*/
@@ -134,53 +135,6 @@ public class TtCopyTemplateController extends BaseController {
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 (TtFile 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("{frontendTechnology}", code.getFrontendTechnology() != null ? code.getFrontendTechnology() : "");
content = content.replace("{backendTechnology}", code.getBackendTechnology() != null ? code.getBackendTechnology() : "");
content = content.replace("{databaseTechnology}", code.getDatabaseTechnology() != null ? code.getDatabaseTechnology() : "");
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);
return success(copyTemplateRenderer.render(template, code));
}
}

View File

@@ -67,4 +67,6 @@ public interface TtProjectInfoMapper
int updateProjectQuarkUrl(@Param("id") Integer id, @Param("projectUrl") String projectUrl);
int updateProjectBaiduUrl(@Param("id") Integer id, @Param("projectBaiduUrl") String projectBaiduUrl);
int updateProjectNumIfBlank(@Param("id") Integer id, @Param("projectNum") String projectNum);
}

View File

@@ -0,0 +1,99 @@
package com.ruoyi.office.service;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.stereotype.Component;
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.TtFile;
/**
* 文案模板占位符渲染器。
*/
@Component
public class CopyTemplateRenderer
{
private static final Pattern PROJECT_CODE_PATTERN = Pattern.compile("【(.+?)】");
private static final Pattern PROJECT_NAME_PATTERN = Pattern.compile("实现的(.+?)$");
public String render(TtCopyTemplate template, TtCode code)
{
String codeName = value(code.getCodeName());
String projectCode = extractProjectCode(codeName);
String projectName = extractProjectName(codeName);
String content = value(template.getTemplateBody());
content = content.replace("{codeName}", codeName);
content = content.replace("{projectCode}", projectCode);
content = content.replace("{projectName}", projectName);
content = content.replace("{codeDesc}", toPlainDescription(code.getCodeDesc()));
content = content.replace("{codeEnvironment}", value(code.getCodeEnvironment()));
content = content.replace("{frontendTechnology}", value(code.getFrontendTechnology()));
content = content.replace("{backendTechnology}", value(code.getBackendTechnology()));
content = content.replace("{databaseTechnology}", value(code.getDatabaseTechnology()));
content = content.replace("{codeTechnology}", value(code.getCodeTechnology()));
content = content.replace("{diskLink}", value(code.getDiskLink()));
content = content.replace("{screenshots}", buildScreenshots(code.getFileList()));
return content;
}
private String extractProjectCode(String codeName)
{
Matcher matcher = PROJECT_CODE_PATTERN.matcher(codeName);
return matcher.find() ? matcher.group(1) : "";
}
private String extractProjectName(String codeName)
{
String projectName = PROJECT_CODE_PATTERN.matcher(codeName).replaceFirst("").trim();
if (projectName.startsWith("基于"))
{
Matcher matcher = PROJECT_NAME_PATTERN.matcher(projectName);
if (matcher.find())
{
return matcher.group(1);
}
}
return projectName;
}
private String toPlainDescription(String codeDesc)
{
if (StringUtils.isEmpty(codeDesc))
{
return "";
}
return HtmlUtils.htmlToText(codeDesc.replaceAll("(?i)</p>", "</p>\n").trim());
}
private String buildScreenshots(List<TtFile> fileList)
{
if (fileList == null || fileList.isEmpty())
{
return "请前往微信小程序:南音源码库。查看项目详情!\n\n";
}
StringBuilder screenshots = new StringBuilder();
int index = 0;
for (TtFile file : fileList)
{
screenshots.append(++index)
.append(".")
.append(value(file.getFileName()))
.append("\n![](")
.append(value(file.getFileUrl()))
.append(")\n\n");
}
return screenshots.toString();
}
private String value(String value)
{
return value == null ? "" : value;
}
}

View File

@@ -67,16 +67,8 @@ public interface ITtCodeService {
*/
public int deleteTtCodeByCodeId(Long codeId);
/**
* 转文章
*
* @param codeId 源码管理主键
* @return 结果
*/
public String transToArticle(Long codeId);
/**
* 转文章(南音)
*/
public String transToArticle1(Long codeId, String coverUrl);
public String transToArticle1(Long codeId, Long templateId);
}

View File

@@ -9,15 +9,18 @@ import com.ruoyi.app.domain.AppResource;
import com.ruoyi.app.domain.AppResourceList;
import com.ruoyi.app.mapper.AppBlogArticleMapper;
import com.ruoyi.app.mapper.AppResourceMapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.office.domain.TtArticles;
import com.ruoyi.office.domain.TtCopyTemplate;
import com.ruoyi.office.domain.TtFile;
import com.ruoyi.office.domain.TtProjectInfo;
import com.ruoyi.office.mapper.TtArticlesMapper;
import com.ruoyi.office.mapper.TtCopyTemplateMapper;
import com.ruoyi.office.mapper.TtFileMapper;
import com.ruoyi.office.mapper.TtProjectInfoMapper;
import com.ruoyi.office.service.CopyTemplateRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.office.mapper.TtCodeMapper;
import com.ruoyi.office.domain.TtCode;
import com.ruoyi.office.service.ITtCodeService;
@@ -33,8 +36,6 @@ public class TtCodeServiceImpl implements ITtCodeService {
@Autowired
private TtCodeMapper ttCodeMapper;
@Autowired
private TtArticlesMapper ttArticlesMapper;
@Autowired
private TtFileMapper fileMapper;
@Autowired
private AppBlogArticleMapper appBlogArticleMapper;
@@ -42,6 +43,10 @@ public class TtCodeServiceImpl implements ITtCodeService {
private AppResourceMapper appResourceMapper;
@Autowired
private TtProjectInfoMapper ttProjectInfoMapper;
@Autowired
private TtCopyTemplateMapper ttCopyTemplateMapper;
@Autowired
private CopyTemplateRenderer copyTemplateRenderer;
/**
* 查询源码管理
@@ -131,105 +136,154 @@ public class TtCodeServiceImpl implements ITtCodeService {
}
@Override
public String transToArticle(Long codeId) {
TtCode ttCode = ttCodeMapper.selectTtCodeByCodeId(codeId);
TtArticles ttArticles = new TtArticles();
ttArticles.setTitle(ttCode.getCodeName());
List<TtArticles> list = ttArticlesMapper.selectTtArticlesList(ttArticles);
if (!list.isEmpty()) {
return "不能重复转文章!";
@Transactional(rollbackFor = Exception.class)
public String transToArticle1(Long codeId, Long templateId) {
TtCopyTemplate template = ttCopyTemplateMapper.selectTtCopyTemplateByTemplateId(templateId);
if (template == null || !"0".equals(template.getStatus())) {
throw new ServiceException("所选文案模板不存在或已停用");
}
ttArticles.setAuthor("Feast");
ttArticles.setCategory(ttCode.getPaymentType());
if (ttCode.getPaymentType().equals("2")) {
ttArticles.setAttachmentUrl(ttCode.getDiskLink());
}
StringBuffer content = new StringBuffer();
content.append("<p><strong>### 项目描述</strong></p>");
content.append(ttCode.getCodeDesc());
content.append("<p><strong>### 运行环境</strong></p>");
content.append(ttCode.getCodeEnvironment());
content.append("<p><strong>### 其他技术</strong></p>");
content.append(ttCode.getCodeTechnology());
ttArticles.setContent(content.toString());
ttArticlesMapper.insertTtArticles(ttArticles);
return "源码转文章成功!";
}
@Override
public String transToArticle1(Long codeId, String coverUrl) {
TtCode ttCode = ttCodeMapper.selectTtCodeByCodeId(codeId);
if (ttCode == null) {
throw new ServiceException("源码项目不存在");
}
TtProjectInfo ttProjectInfo = ttProjectInfoMapper.selectTtProjectInfoByName(ttCode.getCodeName());
if (ttProjectInfo == null) {
throw new ServiceException("未找到对应的项目清单信息");
}
ttCode.setDiskLink(ttProjectInfo.getProjectBaiduUrl());
ttCode.setPublishFlag("Y");
ttCodeMapper.updateTtCode(ttCode);
StringBuffer content = new StringBuffer();
content.append("<p><strong>### 项目描述</strong></p>");
content.append(ttCode.getCodeDesc());
content.append("<p><strong>### 运行环境</strong></p>");
content.append(ttCode.getCodeEnvironment());
content.append("<p><strong>### 其他技术</strong></p>");
content.append(ttCode.getCodeTechnology());
content.append("<p><strong>### 演示视频</strong></p>");
content.append("请移步首页-<strong>视频资源</strong>,搜索<strong>项目编号</strong>查看");
TtFile file = new TtFile();
file.setCodeName(ttCode.getCodeName());
List<TtFile> fileList = fileMapper.selectTtFileList(file);
AppBlogArticle article = new AppBlogArticle();
article.setTitle(ttCode.getCodeName());
List<AppBlogArticle> list = appBlogArticleMapper.selectAppBlogArticleList(article);
if (!list.isEmpty()) {
ttCode.setFileList(fileList);
String content = copyTemplateRenderer.render(template, ttCode);
String firstImageUrl = getFirstImageUrl(fileList);
AppBlogArticle existingArticle = appBlogArticleMapper.selectAppBlogArticleByTitle(ttCode.getCodeName());
if (existingArticle != null) {
if (repairIncompleteConversion(existingArticle, ttProjectInfo, content, firstImageUrl)) {
markCodePublished(ttCode);
return "源码转文章成功,已补全资源链接!";
}
return "不能重复转文章!";
}
//新增资源
AppResource resource = new AppResource();
resource.setExplain(content.toString());
resource.setExplain(content);
resource.setResourceTitle(ttCode.getCodeName());
if (!fileList.isEmpty()) {
resource.setShowImg(fileList.get(0).getFileUrl());
if (!StringUtils.isEmpty(firstImageUrl)) {
resource.setShowImg(firstImageUrl);
}
resource.setResourceType(5L);
resource.setIsShow(0L);
resource.setIsAd(2L);
resource.setAdNumber(100L);
resource.setPriceFen(0);
resource.setCreateTime(new Date());
resource.setShowImg(coverUrl);
appResourceMapper.insertAppResource(resource);
//新增文章
AppBlogArticle article = new AppBlogArticle();
article.setTitle(ttCode.getCodeName());
article.setArticleType(4L);
article.setIsRecommendation(1L);
article.setIsShow(1L);
article.setIsAd(1L);
if (!fileList.isEmpty()) {
article.setShowImg(fileList.get(0).getFileUrl());
if (!StringUtils.isEmpty(firstImageUrl)) {
article.setShowImg(firstImageUrl);
}
article.setContentInfo(content.toString());
article.setContentInfo(content);
article.setAppResourceId(resource.getId());
article.setCreateTime(new Date());
article.setShowImg(coverUrl);
appBlogArticleMapper.insertAppBlogArticle(article);
//新增资源网盘链接
List<AppResourceList> resourceList = new ArrayList<AppResourceList>();
//百度网盘
if(!StringUtils.isEmpty(ttProjectInfo.getProjectBaiduUrl())){
AppResourceList appResourceList = new AppResourceList();
appResourceList.setAppResourceId(resource.getId());
appResourceList.setListName("百度网盘");
appResourceList.setListUrl(ttProjectInfo.getProjectBaiduUrl());
resourceList.add(appResourceList);
}
//夸克网盘
if(!StringUtils.isEmpty(ttProjectInfo.getProjectUrl())){
AppResourceList appResourceList1 = new AppResourceList();
appResourceList1.setAppResourceId(resource.getId());
appResourceList1.setListName("夸克网盘");
appResourceList1.setListUrl(ttProjectInfo.getProjectUrl());
resourceList.add(appResourceList1);
}
List<AppResourceList> resourceList = buildResourceList(ttProjectInfo, resource.getId());
if (resourceList.size() > 0)
{
appResourceMapper.batchAppResourceList(resourceList);
}
markCodePublished(ttCode);
return "源码转文章成功!";
}
/**
* 修复旧转换流程异常后已经生成文章、但尚未写入下载链接的半成品数据。
*/
private boolean repairIncompleteConversion(AppBlogArticle article, TtProjectInfo projectInfo,
String content, String firstImageUrl)
{
if (article.getAppResourceId() == null)
{
return false;
}
AppResource resource = appResourceMapper.selectAppResourceById(article.getAppResourceId());
if (resource == null || (resource.getAppResourceListList() != null
&& !resource.getAppResourceListList().isEmpty()))
{
return false;
}
List<AppResourceList> resourceList = buildResourceList(projectInfo, resource.getId());
if (resourceList.isEmpty())
{
return false;
}
resource.setExplain(content);
if (!StringUtils.isEmpty(firstImageUrl))
{
resource.setShowImg(firstImageUrl);
}
resource.setPriceFen(0);
appResourceMapper.updateAppResource(resource);
article.setContentInfo(content);
if (!StringUtils.isEmpty(firstImageUrl))
{
article.setShowImg(firstImageUrl);
}
appBlogArticleMapper.updateAppBlogArticle(article);
appResourceMapper.batchAppResourceList(resourceList);
return true;
}
private String getFirstImageUrl(List<TtFile> fileList)
{
if (fileList == null || fileList.isEmpty())
{
return null;
}
return fileList.get(0).getFileUrl();
}
private List<AppResourceList> buildResourceList(TtProjectInfo projectInfo, Long resourceId)
{
List<AppResourceList> resourceList = new ArrayList<AppResourceList>();
addResourceList(resourceList, resourceId, "百度网盘", projectInfo.getProjectBaiduUrl());
addResourceList(resourceList, resourceId, "夸克网盘", projectInfo.getProjectUrl());
return resourceList;
}
private void addResourceList(List<AppResourceList> resourceList, Long resourceId,
String name, String url)
{
if (StringUtils.isEmpty(url))
{
return;
}
AppResourceList item = new AppResourceList();
item.setAppResourceId(resourceId);
item.setListName(name);
item.setListUrl(url);
item.setPassword("");
item.setPriceFen(0);
item.setStatus(1);
item.setSortOrder(resourceList.size());
resourceList.add(item);
}
private void markCodePublished(TtCode ttCode)
{
ttCode.setPublishFlag("Y");
ttCodeMapper.updateTtCode(ttCode);
}
}

View File

@@ -39,7 +39,7 @@ import com.ruoyi.office.service.ITtProjectInfoService;
public class TtProjectInfoServiceImpl implements ITtProjectInfoService
{
private static final Pattern PROJECT_NUM_PATTERN =
Pattern.compile("^[\\[]\\s*(S\\d+)\\s*[\\]]", Pattern.CASE_INSENSITIVE);
Pattern.compile("\\s*([^】]+?)\\s*】");
private static final Pattern QUARK_URL_PATTERN =
Pattern.compile("https://pan\\.quark\\.cn/s/[A-Za-z0-9_-]+(?:\\?[^\\s\"'<>,。;]*)?");
@@ -203,6 +203,7 @@ public class TtProjectInfoServiceImpl implements ITtProjectInfoService
}
TtProjectInfo project = matches.get(0);
backfillProjectNumIfBlank(project, row.projectName);
String oldLink = NetDiskConstants.DISK_QUARK.equals(normalizedDiskType)
? project.getProjectUrl() : project.getProjectBaiduUrl();
if (StringUtils.equals(StringUtils.trimToEmpty(oldLink), row.linkUrl))
@@ -390,6 +391,20 @@ public class TtProjectInfoServiceImpl implements ITtProjectInfoService
return project;
}
private void backfillProjectNumIfBlank(TtProjectInfo project, String projectName)
{
if (StringUtils.isNotBlank(project.getProjectNum()))
{
return;
}
String projectNum = extractProjectNum(projectName);
if (StringUtils.isNotBlank(projectNum)
&& ttProjectInfoMapper.updateProjectNumIfBlank(project.getId(), projectNum) == 1)
{
project.setProjectNum(projectNum);
}
}
private String extractProjectNum(String projectName)
{
Matcher matcher = PROJECT_NUM_PATTERN.matcher(projectName);

View File

@@ -125,6 +125,13 @@
where id = #{id}
</update>
<update id="updateProjectNumIfBlank">
update tt_project_info
set project_num = #{projectNum}
where id = #{id}
and (project_num is null or trim(project_num) = '')
</update>
<delete id="deleteTtProjectInfoById" parameterType="Integer">
delete from tt_project_info where id = #{id}
</delete>

View File

@@ -0,0 +1,56 @@
package com.ruoyi.office.service;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import com.ruoyi.office.domain.TtCode;
import com.ruoyi.office.domain.TtCopyTemplate;
import com.ruoyi.office.domain.TtFile;
public class CopyTemplateRendererTest
{
private final CopyTemplateRenderer renderer = new CopyTemplateRenderer();
@Test
public void rendersCodeFieldsAndScreenshots()
{
TtCopyTemplate template = new TtCopyTemplate();
template.setTemplateBody("{codeName}|{projectCode}|{projectName}|{codeDesc}|"
+ "{codeEnvironment}|{frontendTechnology}|{backendTechnology}|"
+ "{databaseTechnology}|{codeTechnology}|{diskLink}|{screenshots}");
TtCode code = new TtCode();
code.setCodeName("【S008】基于SpringBoot实现的爱心众筹系统");
code.setCodeDesc("<p>第一段</p><p>第二段</p>");
code.setCodeEnvironment("JDK 17");
code.setFrontendTechnology("Vue3");
code.setBackendTechnology("SpringBoot");
code.setDatabaseTechnology("MySQL");
code.setCodeTechnology("ECharts");
code.setDiskLink("https://example.com/resource");
TtFile screenshot = new TtFile();
screenshot.setFileName("首页");
screenshot.setFileUrl("https://example.com/home.png");
code.setFileList(Arrays.asList(screenshot));
String content = renderer.render(template, code);
Assert.assertEquals("【S008】基于SpringBoot实现的爱心众筹系统|S008|爱心众筹系统|"
+ "第一段\n第二段|JDK 17|Vue3|SpringBoot|MySQL|ECharts|"
+ "https://example.com/resource|1.首页\n![](https://example.com/home.png)\n\n", content);
}
@Test
public void usesFallbackWhenThereAreNoScreenshots()
{
TtCopyTemplate template = new TtCopyTemplate();
template.setTemplateBody("{screenshots}");
String content = renderer.render(template, new TtCode());
Assert.assertEquals("请前往微信小程序:南音源码库。查看项目详情!\n\n", content);
}
}

View File

@@ -0,0 +1,152 @@
package com.ruoyi.office.service.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.ruoyi.app.domain.AppBlogArticle;
import com.ruoyi.app.domain.AppResource;
import com.ruoyi.app.domain.AppResourceList;
import com.ruoyi.app.mapper.AppBlogArticleMapper;
import com.ruoyi.app.mapper.AppResourceMapper;
import com.ruoyi.office.domain.TtCode;
import com.ruoyi.office.domain.TtCopyTemplate;
import com.ruoyi.office.domain.TtFile;
import com.ruoyi.office.domain.TtProjectInfo;
import com.ruoyi.office.mapper.TtCodeMapper;
import com.ruoyi.office.mapper.TtCopyTemplateMapper;
import com.ruoyi.office.mapper.TtFileMapper;
import com.ruoyi.office.mapper.TtProjectInfoMapper;
import com.ruoyi.office.service.CopyTemplateRenderer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TtCodeServiceImplTest
{
@InjectMocks
private TtCodeServiceImpl service;
@Mock
private TtCodeMapper codeMapper;
@Mock
private TtFileMapper fileMapper;
@Mock
private AppBlogArticleMapper articleMapper;
@Mock
private AppResourceMapper resourceMapper;
@Mock
private TtProjectInfoMapper projectInfoMapper;
@Mock
private TtCopyTemplateMapper copyTemplateMapper;
@Mock
private CopyTemplateRenderer templateRenderer;
private TtCode code;
private TtProjectInfo projectInfo;
@Before
public void setUp()
{
TtCopyTemplate template = new TtCopyTemplate();
template.setTemplateId(1L);
template.setStatus("0");
code = new TtCode();
code.setCodeId(9L);
code.setCodeName("【S009】自习室预约选座系统");
projectInfo = new TtProjectInfo();
projectInfo.setProjectBaiduUrl("https://pan.baidu.com/s/example");
projectInfo.setProjectUrl("https://pan.quark.cn/s/example");
TtFile firstImage = new TtFile();
firstImage.setFileName("第一张");
firstImage.setFileUrl("https://img.example.com/first.png");
TtFile secondImage = new TtFile();
secondImage.setFileName("第二张");
secondImage.setFileUrl("https://img.example.com/second.png");
when(copyTemplateMapper.selectTtCopyTemplateByTemplateId(1L)).thenReturn(template);
when(codeMapper.selectTtCodeByCodeId(9L)).thenReturn(code);
when(projectInfoMapper.selectTtProjectInfoByName(code.getCodeName())).thenReturn(projectInfo);
when(fileMapper.selectTtFileList(any())).thenReturn(Arrays.asList(firstImage, secondImage));
when(templateRenderer.render(template, code)).thenReturn("模板正文");
}
@Test
public void fillsRequiredResourceListFields()
{
when(articleMapper.selectAppBlogArticleByTitle(code.getCodeName())).thenReturn(null);
when(resourceMapper.insertAppResource(any())).thenAnswer(invocation -> {
AppResource resource = invocation.getArgument(0);
resource.setId(100L);
return 1;
});
service.transToArticle1(9L, 1L);
ArgumentCaptor<AppResource> resourceCaptor = ArgumentCaptor.forClass(AppResource.class);
verify(resourceMapper).insertAppResource(resourceCaptor.capture());
Assert.assertEquals(Integer.valueOf(0), resourceCaptor.getValue().getPriceFen());
Assert.assertEquals("https://img.example.com/first.png", resourceCaptor.getValue().getShowImg());
ArgumentCaptor<AppBlogArticle> articleCaptor = ArgumentCaptor.forClass(AppBlogArticle.class);
verify(articleMapper).insertAppBlogArticle(articleCaptor.capture());
Assert.assertEquals("https://img.example.com/first.png", articleCaptor.getValue().getShowImg());
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class);
verify(resourceMapper).batchAppResourceList(listCaptor.capture());
List<AppResourceList> items = listCaptor.getValue();
Assert.assertEquals(2, items.size());
assertResourceListDefaults(items.get(0), 0);
assertResourceListDefaults(items.get(1), 1);
}
@Test
public void repairsArticleLeftByPreviousFailedConversion()
{
AppBlogArticle existingArticle = new AppBlogArticle();
existingArticle.setId(200L);
existingArticle.setTitle(code.getCodeName());
existingArticle.setAppResourceId(100L);
AppResource existingResource = new AppResource();
existingResource.setId(100L);
existingResource.setAppResourceListList(new ArrayList<AppResourceList>());
when(articleMapper.selectAppBlogArticleByTitle(code.getCodeName())).thenReturn(existingArticle);
when(resourceMapper.selectAppResourceById(100L)).thenReturn(existingResource);
String message = service.transToArticle1(9L, 1L);
Assert.assertEquals("源码转文章成功,已补全资源链接!", message);
Assert.assertEquals("https://img.example.com/first.png", existingResource.getShowImg());
Assert.assertEquals("https://img.example.com/first.png", existingArticle.getShowImg());
verify(resourceMapper).updateAppResource(existingResource);
verify(articleMapper).updateAppBlogArticle(existingArticle);
verify(resourceMapper).batchAppResourceList(any());
verify(resourceMapper, never()).insertAppResource(any());
verify(articleMapper, never()).insertAppBlogArticle(any());
}
private void assertResourceListDefaults(AppResourceList item, int sortOrder)
{
Assert.assertEquals(Integer.valueOf(0), item.getPriceFen());
Assert.assertEquals(Integer.valueOf(1), item.getStatus());
Assert.assertEquals(Integer.valueOf(sortOrder), item.getSortOrder());
Assert.assertEquals(Long.valueOf(100L), item.getAppResourceId());
Assert.assertEquals("", item.getPassword());
}
}

View File

@@ -77,6 +77,43 @@ public class TtProjectInfoServiceImplTest
Assert.assertEquals(1, result.getAddedCount());
}
@Test
public void extractsAnyTextInsideFullWidthBracketsAsProjectNumber()
{
quarkRow.setProjectName("课程项目【 K001 】药店进销存系统");
when(parser.parse(file, "QUARK")).thenReturn(Collections.singletonList(quarkRow));
when(projectInfoMapper.selectTtProjectInfoList(any(TtProjectInfo.class)))
.thenReturn(Collections.emptyList());
when(projectInfoMapper.insertTtProjectInfo(any(TtProjectInfo.class))).thenReturn(1);
service.importProjectLinks(file, "QUARK");
ArgumentCaptor<TtProjectInfo> captor = ArgumentCaptor.forClass(TtProjectInfo.class);
verify(projectInfoMapper).insertTtProjectInfo(captor.capture());
Assert.assertEquals("K001", captor.getValue().getProjectNum());
}
@Test
public void backfillsProjectNumberForExistingProjectWhenBlank()
{
quarkRow.setProjectName("【K001】药店进销存系统");
TtProjectInfo existing = new TtProjectInfo();
existing.setId(1);
existing.setProjectName("【K001】药店进销存系统");
existing.setProjectUrl("https://pan.quark.cn/s/741bf63df1bb");
when(parser.parse(file, "QUARK")).thenReturn(Collections.singletonList(quarkRow));
when(projectInfoMapper.selectTtProjectInfoList(any(TtProjectInfo.class)))
.thenReturn(Collections.singletonList(existing));
when(projectInfoMapper.updateProjectNumIfBlank(1, "K001")).thenReturn(1);
ProjectLinkImportResult result = service.importProjectLinks(file, "QUARK");
verify(projectInfoMapper).updateProjectNumIfBlank(1, "K001");
Assert.assertEquals("K001", existing.getProjectNum());
Assert.assertEquals(1, result.getUnchangedCount());
}
@Test
public void updatesOnlyBaiduLinkForExistingProjectAndAddsPasscode()
{
@@ -89,6 +126,7 @@ public class TtProjectInfoServiceImplTest
TtProjectInfo existing = new TtProjectInfo();
existing.setId(31);
existing.setProjectNum("S031");
existing.setProjectName("【S031】婚纱摄影管理系统");
existing.setProjectName1("原源码名称");
existing.setProjectUrl("https://pan.quark.cn/s/old");