Expand publishing and virtual product workflows
This commit is contained in:
@@ -20,6 +20,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.office.domain.TtCode;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.TtCopyTemplateGroupStat;
|
||||
import com.ruoyi.office.service.CopyTemplateRenderer;
|
||||
import com.ruoyi.office.service.ITtCodeService;
|
||||
import com.ruoyi.office.service.ITtCopyTemplateService;
|
||||
@@ -56,6 +57,16 @@ public class TtCopyTemplateController extends BaseController {
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询模板分组及各分组模板数量
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('office:copyTemplate:list')")
|
||||
@GetMapping("/groups")
|
||||
public AjaxResult groups() {
|
||||
List<TtCopyTemplateGroupStat> groups = ttCopyTemplateService.selectCopyTemplateGroupStats();
|
||||
return success(groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按场景查询所有启用的文案模板(无需分页权限)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.office.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 文案模板分组统计。
|
||||
*/
|
||||
public class TtCopyTemplateGroupStat implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 分组名称;__UNGROUPED__ 表示未分组。 */
|
||||
private String templateGroup;
|
||||
|
||||
/** 分组下的模板数量。 */
|
||||
private Long templateCount;
|
||||
|
||||
public String getTemplateGroup()
|
||||
{
|
||||
return templateGroup;
|
||||
}
|
||||
|
||||
public void setTemplateGroup(String templateGroup)
|
||||
{
|
||||
this.templateGroup = templateGroup;
|
||||
}
|
||||
|
||||
public Long getTemplateCount()
|
||||
{
|
||||
return templateCount;
|
||||
}
|
||||
|
||||
public void setTemplateCount(Long templateCount)
|
||||
{
|
||||
this.templateCount = templateCount;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.office.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.TtCopyTemplateGroupStat;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
@@ -22,6 +23,11 @@ public interface TtCopyTemplateMapper {
|
||||
*/
|
||||
public List<TtCopyTemplate> selectTtCopyTemplateList(TtCopyTemplate ttCopyTemplate);
|
||||
|
||||
/**
|
||||
* 查询模板分组及模板数量
|
||||
*/
|
||||
public List<TtCopyTemplateGroupStat> selectCopyTemplateGroupStats();
|
||||
|
||||
/**
|
||||
* 查询所有启用的文案模板(按排序号升序)
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.office.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.TtCopyTemplateGroupStat;
|
||||
|
||||
/**
|
||||
* 文案模板Service接口
|
||||
@@ -21,6 +22,11 @@ public interface ITtCopyTemplateService {
|
||||
*/
|
||||
public List<TtCopyTemplate> selectTtCopyTemplateList(TtCopyTemplate ttCopyTemplate);
|
||||
|
||||
/**
|
||||
* 查询模板分组及模板数量
|
||||
*/
|
||||
public List<TtCopyTemplateGroupStat> selectCopyTemplateGroupStats();
|
||||
|
||||
/**
|
||||
* 查询所有启用的文案模板(供源码明细页按钮使用)
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,16 @@ public class WebsiteArticleDocumentParser
|
||||
private static final Pattern MARKDOWN_IMAGE = Pattern.compile("!\\[[^\\]]*\\]\\(([^)]+)\\)");
|
||||
|
||||
public WebsiteArticleDocument parse(String renderedContent)
|
||||
{
|
||||
return parse(renderedContent, true);
|
||||
}
|
||||
|
||||
public WebsiteArticleDocument parseForWechat(String renderedContent)
|
||||
{
|
||||
return parse(renderedContent, false);
|
||||
}
|
||||
|
||||
private WebsiteArticleDocument parse(String renderedContent, boolean enforceWebsiteTitleLimit)
|
||||
{
|
||||
if (StringUtils.isBlank(renderedContent))
|
||||
{
|
||||
@@ -44,7 +54,7 @@ public class WebsiteArticleDocumentParser
|
||||
{
|
||||
throw new ServiceException("复制网站文案第一行标题为空");
|
||||
}
|
||||
if (title.length() > 50)
|
||||
if (enforceWebsiteTitleLimit && title.length() > 50)
|
||||
{
|
||||
throw new ServiceException("网站文章标题不能超过50个字符:" + title);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class WechatDraftContentService
|
||||
if (code == null) throw new ServiceException("源码项目不存在");
|
||||
if (template == null || !"0".equals(template.getStatus())) throw new ServiceException("文案模板不存在或已停用");
|
||||
|
||||
WebsiteArticleDocument generated = documentParser.parse(templateRenderer.render(template, code));
|
||||
WebsiteArticleDocument generated = documentParser.parseForWechat(templateRenderer.render(template, code));
|
||||
String markdown = StringUtils.isNotBlank(request.getContentMarkdown())
|
||||
? request.getContentMarkdown() : generated.getContentMarkdown();
|
||||
String title = StringUtils.isNotBlank(request.getTitle()) ? request.getTitle() : generated.getTitle();
|
||||
@@ -59,7 +59,7 @@ public class WechatDraftContentService
|
||||
WechatDraftPreview preview = new WechatDraftPreview();
|
||||
preview.setRequestNo(StringUtils.isNotBlank(request.getRequestNo())
|
||||
? request.getRequestNo() : UUID.randomUUID().toString().replace("-", ""));
|
||||
preview.setTitle(limit(title, 32));
|
||||
preview.setTitle(limit(title, 60));
|
||||
preview.setAuthor(limit(firstNonBlank(request.getAuthor(), account == null ? null : account.getDefaultAuthor()), 16));
|
||||
String html = articleRenderer.render(markdown, request.getThemeColor(), request.getThemeName(), request.getFontSize());
|
||||
// 摘要默认留空,交由微信使用正文内容生成展示摘要;手动摘要遵循微信 120 字限制。
|
||||
|
||||
@@ -38,13 +38,14 @@ import com.ruoyi.office.service.ITtCodeService;
|
||||
@Service
|
||||
public class TtCodeServiceImpl implements ITtCodeService {
|
||||
private static final Pattern PREMIUM_PROJECT_CODE_PATTERN =
|
||||
Pattern.compile("(?i)(?:^|【)\\s*([SK])\\s*\\d+");
|
||||
Pattern.compile("(?i)(?:^|【)\\s*([SKF])\\s*\\d+");
|
||||
private static final long SOURCE_SHARE_RESOURCE_TYPE = 5L;
|
||||
private static final long PREMIUM_PROJECT_RESOURCE_TYPE = 6L;
|
||||
private static final long SOURCE_SHARE_ARTICLE_TYPE = 4L;
|
||||
private static final long PREMIUM_SOURCE_ARTICLE_TYPE = 7L;
|
||||
private static final long POINTS_ACCESS_TYPE = 2L;
|
||||
private static final long PAID_ACCESS_TYPE = 3L;
|
||||
private static final String F_BASIC_SPEC_NAME = "源码 + 数据库 + 万字文档 + PPT";
|
||||
private static final String BASIC_SPEC_NAME = "源码 + 数据库 + 论文 + 答辩PPT";
|
||||
private static final String DEPLOYMENT_SPEC_NAME = "源码 + 数据库 + 论文 + 答辩PPT + 项目部署";
|
||||
private static final String DEPLOYMENT_CONTACT = "调试部署加微信:forfeastcoding";
|
||||
@@ -286,6 +287,12 @@ public class TtCodeServiceImpl implements ITtCodeService {
|
||||
String projectSeries)
|
||||
{
|
||||
List<AppResourceList> resourceList = new ArrayList<AppResourceList>();
|
||||
if ("F".equals(projectSeries))
|
||||
{
|
||||
addResourceList(resourceList, resourceId, F_BASIC_SPEC_NAME,
|
||||
projectInfo.getProjectUrl(), 900, 1);
|
||||
return resourceList;
|
||||
}
|
||||
if ("K".equals(projectSeries))
|
||||
{
|
||||
addResourceList(resourceList, resourceId, BASIC_SPEC_NAME,
|
||||
@@ -346,7 +353,7 @@ public class TtCodeServiceImpl implements ITtCodeService {
|
||||
resource.setResourceType(PREMIUM_PROJECT_RESOURCE_TYPE);
|
||||
resource.setIsAd(PAID_ACCESS_TYPE);
|
||||
resource.setAdNumber(0L);
|
||||
resource.setPriceFen("K".equals(projectSeries) ? 1900 : 5900);
|
||||
resource.setPriceFen(resolvePremiumBasePrice(projectSeries));
|
||||
return;
|
||||
}
|
||||
resource.setResourceType(SOURCE_SHARE_RESOURCE_TYPE);
|
||||
@@ -357,7 +364,17 @@ public class TtCodeServiceImpl implements ITtCodeService {
|
||||
|
||||
private boolean isPremiumProject(String projectSeries)
|
||||
{
|
||||
return "S".equals(projectSeries) || "K".equals(projectSeries);
|
||||
return "S".equals(projectSeries) || "K".equals(projectSeries)
|
||||
|| "F".equals(projectSeries);
|
||||
}
|
||||
|
||||
private int resolvePremiumBasePrice(String projectSeries)
|
||||
{
|
||||
if ("F".equals(projectSeries))
|
||||
{
|
||||
return 900;
|
||||
}
|
||||
return "K".equals(projectSeries) ? 1900 : 5900;
|
||||
}
|
||||
|
||||
private String resolveProjectSeries(TtCode code, TtProjectInfo projectInfo)
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.TtCopyTemplateGroupStat;
|
||||
import com.ruoyi.office.mapper.TtCopyTemplateMapper;
|
||||
import com.ruoyi.office.service.ITtCopyTemplateService;
|
||||
|
||||
@@ -30,6 +31,11 @@ public class TtCopyTemplateServiceImpl implements ITtCopyTemplateService {
|
||||
return ttCopyTemplateMapper.selectTtCopyTemplateList(ttCopyTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtCopyTemplateGroupStat> selectCopyTemplateGroupStats() {
|
||||
return ttCopyTemplateMapper.selectCopyTemplateGroupStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtCopyTemplate> selectEnabledTemplates() {
|
||||
return selectEnabledTemplates(null);
|
||||
|
||||
@@ -21,7 +21,7 @@ public class TtFileServiceImpl implements ITtFileService {
|
||||
@Autowired
|
||||
private TtFileMapper ttFileMapper;
|
||||
|
||||
private final String SITE_URL = "http://img.yidaima.cn/";
|
||||
private final String SITE_URL = "https://img.yidaima.cn/";
|
||||
|
||||
/**
|
||||
* 查询文件管理
|
||||
@@ -58,7 +58,7 @@ public class TtFileServiceImpl implements ITtFileService {
|
||||
String[] pictures = ttFile.getPictureFile().split(",");
|
||||
TreeMap<Integer, String> treeMap = new TreeMap();
|
||||
for (String url : pictures) {
|
||||
url = url.replaceAll(SITE_URL, "");
|
||||
url = url.replace(SITE_URL, "");
|
||||
String[] urls = url.split("-");
|
||||
String picName = urls[0];
|
||||
if(picName.contains("_")){
|
||||
|
||||
@@ -3,11 +3,9 @@ package com.ruoyi.office.service.impl;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -171,7 +169,8 @@ public class TtProjectInfoServiceImpl implements ITtProjectInfoService
|
||||
|
||||
List<ValidatedImportRow> validRows = validateRows(sourceRows, normalizedDiskType, result);
|
||||
Set<String> duplicateNames = findDuplicateNames(validRows);
|
||||
Map<String, List<TtProjectInfo>> projectsByName = loadProjectsByNormalizedName();
|
||||
List<TtProjectInfo> projects = new ArrayList<>(
|
||||
ttProjectInfoMapper.selectTtProjectInfoList(new TtProjectInfo()));
|
||||
|
||||
for (ValidatedImportRow row : validRows)
|
||||
{
|
||||
@@ -181,23 +180,22 @@ public class TtProjectInfoServiceImpl implements ITtProjectInfoService
|
||||
continue;
|
||||
}
|
||||
|
||||
List<TtProjectInfo> matches = projectsByName.get(row.normalizedName);
|
||||
if (matches != null && matches.size() > 1)
|
||||
List<TtProjectInfo> matches = findMatchingProjects(row, projects);
|
||||
if (matches.size() > 1)
|
||||
{
|
||||
result.addFailed(row.rowNumber, row.projectName, "数据库中存在多个同名项目,无法确定更新目标");
|
||||
result.addFailed(row.rowNumber, row.projectName,
|
||||
"项目编号、项目名称或源码名称匹配到多条数据,无法确定更新目标");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matches == null || matches.isEmpty())
|
||||
if (matches.isEmpty())
|
||||
{
|
||||
TtProjectInfo project = createProject(row, normalizedDiskType);
|
||||
if (ttProjectInfoMapper.insertTtProjectInfo(project) != 1)
|
||||
{
|
||||
throw new ServiceException("新增项目失败:" + row.projectName);
|
||||
}
|
||||
List<TtProjectInfo> inserted = new ArrayList<>();
|
||||
inserted.add(project);
|
||||
projectsByName.put(row.normalizedName, inserted);
|
||||
projects.add(project);
|
||||
result.incrementAddedCount();
|
||||
continue;
|
||||
}
|
||||
@@ -344,20 +342,22 @@ public class TtProjectInfoServiceImpl implements ITtProjectInfoService
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<TtProjectInfo>> loadProjectsByNormalizedName()
|
||||
private List<TtProjectInfo> findMatchingProjects(ValidatedImportRow row, List<TtProjectInfo> projects)
|
||||
{
|
||||
Map<String, List<TtProjectInfo>> projectsByName = new HashMap<>();
|
||||
List<TtProjectInfo> projects = ttProjectInfoMapper.selectTtProjectInfoList(new TtProjectInfo());
|
||||
List<TtProjectInfo> matches = new ArrayList<>();
|
||||
String projectNum = normalizeProjectNum(extractProjectNum(row.projectName));
|
||||
for (TtProjectInfo project : projects)
|
||||
{
|
||||
String normalizedName = normalizeProjectName(project.getProjectName());
|
||||
if (StringUtils.isBlank(normalizedName))
|
||||
boolean projectNumMatches = StringUtils.isNotBlank(projectNum)
|
||||
&& projectNum.equals(normalizeProjectNum(project.getProjectNum()));
|
||||
boolean projectNameMatches = row.normalizedName.equals(normalizeProjectName(project.getProjectName()));
|
||||
boolean sourceNameMatches = row.normalizedName.equals(normalizeProjectName(project.getProjectName1()));
|
||||
if (projectNumMatches || projectNameMatches || sourceNameMatches)
|
||||
{
|
||||
continue;
|
||||
matches.add(project);
|
||||
}
|
||||
projectsByName.computeIfAbsent(normalizedName, key -> new ArrayList<>()).add(project);
|
||||
}
|
||||
return projectsByName;
|
||||
return matches;
|
||||
}
|
||||
|
||||
private Set<String> findDuplicateNames(List<ValidatedImportRow> rows)
|
||||
@@ -419,6 +419,11 @@ public class TtProjectInfoServiceImpl implements ITtProjectInfoService
|
||||
.trim();
|
||||
}
|
||||
|
||||
private String normalizeProjectNum(String projectNum)
|
||||
{
|
||||
return normalizeProjectName(projectNum).toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizeDiskType(String diskType)
|
||||
{
|
||||
String value = StringUtils.trimToEmpty(diskType).toUpperCase(Locale.ROOT);
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
@@ -38,16 +40,21 @@ import com.ruoyi.office.domain.PublishProjectRow;
|
||||
import com.ruoyi.office.domain.PublishStatusUpdate;
|
||||
import com.ruoyi.office.domain.TtCode;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.TtFile;
|
||||
import com.ruoyi.office.domain.TtPublishTask;
|
||||
import com.ruoyi.office.mapper.TtPublishTaskMapper;
|
||||
import com.ruoyi.office.service.CopyTemplateRenderer;
|
||||
import com.ruoyi.office.service.ITtCodeService;
|
||||
import com.ruoyi.office.service.ITtCopyTemplateService;
|
||||
import com.ruoyi.office.service.ITtFileService;
|
||||
import com.ruoyi.office.service.ITtPublishTaskService;
|
||||
|
||||
@Service
|
||||
public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
{
|
||||
private static final int MAX_PUBLISH_IMAGES = 9;
|
||||
private static final int MAX_ASSET_REDIRECTS = 5;
|
||||
|
||||
private static final Set<String> PLATFORMS = Collections.unmodifiableSet(
|
||||
new LinkedHashSet<>(Arrays.asList("xiaohongshu", "douyin", "wechat_channels", "bilibili", "xianyu")));
|
||||
|
||||
@@ -66,6 +73,9 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
@Autowired
|
||||
private ITtCopyTemplateService copyTemplateService;
|
||||
|
||||
@Autowired
|
||||
private ITtFileService fileService;
|
||||
|
||||
@Autowired
|
||||
private CopyTemplateRenderer copyTemplateRenderer;
|
||||
|
||||
@@ -87,6 +97,7 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
material.setCodeId(code.getCodeId());
|
||||
material.setCodeName(code.getCodeName());
|
||||
material.setTitle(code.getCodeName());
|
||||
material.setImageUrls(selectProjectImageUrls(code.getCodeName()));
|
||||
TtCopyTemplate selectedTemplate = selectPublishTemplate(
|
||||
copyTemplateService.selectEnabledTemplates("CODE"), templateGroup);
|
||||
if (selectedTemplate != null)
|
||||
@@ -99,6 +110,29 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接复用文件管理中与项目关联的图片,避免发布时再次上传同一批素材。
|
||||
*/
|
||||
private List<String> selectProjectImageUrls(String codeName)
|
||||
{
|
||||
List<TtFile> files = fileService.selectTtFileByCodeName(codeName);
|
||||
List<String> urls = new ArrayList<>();
|
||||
if (files != null)
|
||||
{
|
||||
for (TtFile file : files)
|
||||
{
|
||||
if (file != null)
|
||||
{
|
||||
urls.add(file.getFileUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> cleaned = cleanUrls(urls);
|
||||
return cleaned.size() <= MAX_PUBLISH_IMAGES
|
||||
? cleaned
|
||||
: new ArrayList<>(cleaned.subList(0, MAX_PUBLISH_IMAGES));
|
||||
}
|
||||
|
||||
private TtCopyTemplate selectPublishTemplate(List<TtCopyTemplate> templates, String targetGroup)
|
||||
{
|
||||
if (templates == null || templates.isEmpty())
|
||||
@@ -295,6 +329,7 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
}
|
||||
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition, Content-Type, Content-Length");
|
||||
response.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
||||
response.setHeader("Cache-Control", "private, no-store");
|
||||
|
||||
@@ -374,22 +409,8 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
|
||||
private void writeRemoteAsset(String source, HttpServletResponse response) throws IOException
|
||||
{
|
||||
URL url = new URL(source);
|
||||
if (!"http".equalsIgnoreCase(url.getProtocol()) && !"https".equalsIgnoreCase(url.getProtocol()))
|
||||
{
|
||||
throw new ServiceException("不支持的发布素材地址");
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(10000);
|
||||
connection.setReadTimeout(120000);
|
||||
connection.setInstanceFollowRedirects(true);
|
||||
connection.setRequestProperty("User-Agent", "RuoYi-Publish-Bridge/1.0");
|
||||
int status = connection.getResponseCode();
|
||||
if (status < 200 || status >= 300)
|
||||
{
|
||||
connection.disconnect();
|
||||
throw new ServiceException("读取远程发布素材失败,HTTP " + status);
|
||||
}
|
||||
HttpURLConnection connection = openRemoteAssetConnection(source);
|
||||
URL finalUrl = connection.getURL();
|
||||
String contentType = connection.getContentType();
|
||||
response.setContentType(contentType == null ? "application/octet-stream" : contentType);
|
||||
long length = connection.getContentLengthLong();
|
||||
@@ -397,7 +418,7 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
{
|
||||
response.setContentLengthLong(length);
|
||||
}
|
||||
String fileName = new File(url.getPath()).getName();
|
||||
String fileName = new File(finalUrl.getPath()).getName();
|
||||
response.setHeader("Content-Disposition", "inline; filename*=UTF-8''" + encode(fileName));
|
||||
try (InputStream input = connection.getInputStream())
|
||||
{
|
||||
@@ -409,6 +430,74 @@ public class TtPublishTaskServiceImpl implements ITtPublishTaskService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpURLConnection 不会自动跟随 HTTP 到 HTTPS 的跨协议重定向,图床地址需要在这里显式处理。
|
||||
*/
|
||||
private HttpURLConnection openRemoteAssetConnection(String source) throws IOException
|
||||
{
|
||||
URL currentUrl = requireHttpAssetUrl(source);
|
||||
for (int redirectCount = 0; redirectCount <= MAX_ASSET_REDIRECTS; redirectCount++)
|
||||
{
|
||||
HttpURLConnection connection = (HttpURLConnection) currentUrl.openConnection();
|
||||
connection.setConnectTimeout(10000);
|
||||
connection.setReadTimeout(120000);
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
connection.setRequestProperty("User-Agent", "RuoYi-Publish-Bridge/1.0");
|
||||
int status = connection.getResponseCode();
|
||||
if (status >= 200 && status < 300)
|
||||
{
|
||||
return connection;
|
||||
}
|
||||
if (!isRedirectStatus(status))
|
||||
{
|
||||
connection.disconnect();
|
||||
throw new ServiceException("读取远程发布素材失败,HTTP " + status);
|
||||
}
|
||||
String location = connection.getHeaderField("Location");
|
||||
connection.disconnect();
|
||||
if (StringUtils.isEmpty(location))
|
||||
{
|
||||
throw new ServiceException("远程发布素材重定向地址为空");
|
||||
}
|
||||
if (redirectCount == MAX_ASSET_REDIRECTS)
|
||||
{
|
||||
throw new ServiceException("远程发布素材重定向次数过多");
|
||||
}
|
||||
currentUrl = requireHttpAssetUrl(new URL(currentUrl, location).toString());
|
||||
}
|
||||
throw new ServiceException("读取远程发布素材失败");
|
||||
}
|
||||
|
||||
private URL requireHttpAssetUrl(String source) throws IOException
|
||||
{
|
||||
final URI uri;
|
||||
try
|
||||
{
|
||||
uri = new URI(source);
|
||||
}
|
||||
catch (URISyntaxException ex)
|
||||
{
|
||||
throw new IOException("发布素材地址格式错误", ex);
|
||||
}
|
||||
String scheme = uri.getScheme();
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))
|
||||
{
|
||||
throw new ServiceException("不支持的发布素材地址");
|
||||
}
|
||||
// URL/HttpURLConnection 不会自动把中文路径转换为 UTF-8 百分号编码。
|
||||
// 图床会原样重定向乱码路径,最终导致本来存在的图片返回 404。
|
||||
return new URL(uri.toASCIIString());
|
||||
}
|
||||
|
||||
private boolean isRedirectStatus(int status)
|
||||
{
|
||||
return status == HttpURLConnection.HTTP_MOVED_PERM
|
||||
|| status == HttpURLConnection.HTTP_MOVED_TEMP
|
||||
|| status == HttpURLConnection.HTTP_SEE_OTHER
|
||||
|| status == 307
|
||||
|| status == 308;
|
||||
}
|
||||
|
||||
private void copy(InputStream input, OutputStream output) throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
@@ -16,6 +16,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="TtCopyTemplateGroupStat" id="TtCopyTemplateGroupStatResult">
|
||||
<result property="templateGroup" column="template_group" />
|
||||
<result property="templateCount" column="template_count" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTtCopyTemplateVo">
|
||||
select template_id, template_name, template_group, template_scene, template_body, sort_num, status, create_time, update_time
|
||||
from tt_copy_template
|
||||
@@ -25,13 +30,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<include refid="selectTtCopyTemplateVo"/>
|
||||
<where>
|
||||
<if test="templateName != null and templateName != ''"> and template_name like concat('%', #{templateName}, '%')</if>
|
||||
<if test="templateGroup != null and templateGroup != ''"> and template_group like concat('%', #{templateGroup}, '%')</if>
|
||||
<if test="templateGroup != null and templateGroup != ''">
|
||||
<choose>
|
||||
<when test="templateGroup == '__UNGROUPED__'">
|
||||
and (template_group is null or trim(template_group) = '')
|
||||
</when>
|
||||
<otherwise>
|
||||
and template_group = #{templateGroup}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
<if test="templateScene != null and templateScene != ''"> and template_scene = #{templateScene}</if>
|
||||
<if test="status != null and status != ''"> and status = #{status}</if>
|
||||
</where>
|
||||
order by sort_num asc, create_time asc
|
||||
</select>
|
||||
|
||||
<select id="selectCopyTemplateGroupStats" resultMap="TtCopyTemplateGroupStatResult">
|
||||
select
|
||||
case
|
||||
when template_group is null or trim(template_group) = '' then '__UNGROUPED__'
|
||||
else template_group
|
||||
end as template_group,
|
||||
count(1) as template_count
|
||||
from tt_copy_template
|
||||
group by
|
||||
case
|
||||
when template_group is null or trim(template_group) = '' then '__UNGROUPED__'
|
||||
else template_group
|
||||
end
|
||||
order by min(sort_num) asc, template_group asc
|
||||
</select>
|
||||
|
||||
<select id="selectEnabledTemplates" resultMap="TtCopyTemplateResult">
|
||||
<include refid="selectTtCopyTemplateVo"/>
|
||||
where status = '0'
|
||||
|
||||
@@ -30,4 +30,17 @@ public class WebsiteArticleDocumentParserTest
|
||||
{
|
||||
parser.parse("只有标题");
|
||||
}
|
||||
|
||||
@Test(expected = ServiceException.class)
|
||||
public void keepsWebsiteTitleLimitAtFiftyCharacters()
|
||||
{
|
||||
parser.parse(repeat("题", 51) + "\n正文");
|
||||
}
|
||||
|
||||
private String repeat(String value, int count)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) result.append(value);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.office.service;
|
||||
|
||||
import com.ruoyi.office.domain.TtCode;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.WechatDraftPreview;
|
||||
import com.ruoyi.office.domain.WechatDraftPushRequest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class WechatDraftContentServiceTest
|
||||
{
|
||||
@Test
|
||||
public void acceptsSixtyCharacterTitleAndLimitsGeneratedTitle()
|
||||
{
|
||||
ITtCodeService codeService = mock(ITtCodeService.class);
|
||||
ITtCopyTemplateService templateService = mock(ITtCopyTemplateService.class);
|
||||
ITtWechatAccountService accountService = mock(ITtWechatAccountService.class);
|
||||
CopyTemplateRenderer templateRenderer = mock(CopyTemplateRenderer.class);
|
||||
WechatArticleRenderer articleRenderer = mock(WechatArticleRenderer.class);
|
||||
WechatDraftContentService service = new WechatDraftContentService(codeService, templateService,
|
||||
accountService, templateRenderer, new WebsiteArticleDocumentParser(), articleRenderer);
|
||||
|
||||
TtCode code = new TtCode();
|
||||
TtCopyTemplate template = new TtCopyTemplate();
|
||||
template.setStatus("0");
|
||||
when(codeService.selectTtCodeByCodeId(1L)).thenReturn(code);
|
||||
when(templateService.selectTtCopyTemplateByTemplateId(2L)).thenReturn(template);
|
||||
when(templateRenderer.render(template, code)).thenReturn(repeat("题", 61) + "\n正文");
|
||||
|
||||
WechatDraftPushRequest request = new WechatDraftPushRequest();
|
||||
request.setCodeId(1L);
|
||||
request.setTemplateId(2L);
|
||||
request.setTitle(repeat("字", 60));
|
||||
WechatDraftPreview preview = service.preview(request);
|
||||
Assert.assertEquals(repeat("字", 60), preview.getTitle());
|
||||
|
||||
request.setTitle("");
|
||||
preview = service.preview(request);
|
||||
Assert.assertEquals(repeat("题", 60), preview.getTitle());
|
||||
}
|
||||
|
||||
private String repeat(String value, int count)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) result.append(value);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,36 @@ public class TtCodeServiceImplTest
|
||||
Assert.assertEquals(Long.valueOf(7L), articleCaptor.getValue().getArticleType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsFPremiumProjectWithSingleNineYuanSpec()
|
||||
{
|
||||
code.setCodeName("【F001】毕业设计项目");
|
||||
projectInfo.setProjectNum(" f001 ");
|
||||
when(resourceService.insertAppResource(any())).thenAnswer(invocation -> {
|
||||
AppResource resource = invocation.getArgument(0);
|
||||
resource.setId(103L);
|
||||
return 1;
|
||||
});
|
||||
|
||||
service.transToArticle1(9L, 1L);
|
||||
|
||||
ArgumentCaptor<AppResource> resourceCaptor = ArgumentCaptor.forClass(AppResource.class);
|
||||
verify(resourceService).insertAppResource(resourceCaptor.capture());
|
||||
AppResource resource = resourceCaptor.getValue();
|
||||
Assert.assertEquals(Long.valueOf(6L), resource.getResourceType());
|
||||
Assert.assertEquals(Long.valueOf(3L), resource.getIsAd());
|
||||
Assert.assertEquals(Integer.valueOf(900), resource.getPriceFen());
|
||||
Assert.assertEquals(1, resource.getAppResourceListList().size());
|
||||
assertPremiumSpec(resource.getAppResourceListList().get(0),
|
||||
"源码 + 数据库 + 万字文档 + PPT",
|
||||
"https://pan.quark.cn/s/example", 900, 1);
|
||||
|
||||
ArgumentCaptor<AppBlogArticle> articleCaptor = ArgumentCaptor.forClass(AppBlogArticle.class);
|
||||
verify(articleMapper).insertAppBlogArticle(articleCaptor.capture());
|
||||
Assert.assertEquals(Long.valueOf(7L), articleCaptor.getValue().getArticleType());
|
||||
Assert.assertEquals("https://pan.quark.cn/s/example", code.getDiskLink());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repairsArticleLeftByPreviousFailedConversion()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ruoyi.office.service.impl;
|
||||
|
||||
import com.ruoyi.office.domain.TtFile;
|
||||
import com.ruoyi.office.mapper.TtFileMapper;
|
||||
import org.junit.Assert;
|
||||
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 java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TtFileServiceImplTest {
|
||||
@InjectMocks
|
||||
private TtFileServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private TtFileMapper mapper;
|
||||
|
||||
@Test
|
||||
public void insertOrdersNumberedHttpsImagesAndKeepsNames() {
|
||||
TtFile request = new TtFile();
|
||||
request.setCodeName("测试项目");
|
||||
request.setPictureFile("https://img.yidaima.cn/2_管理端数据仪表盘-bbbb,"
|
||||
+ "https://img.yidaima.cn/1_统一登录入口-aaaa");
|
||||
|
||||
Assert.assertEquals(2, service.insertTtFile(request));
|
||||
|
||||
ArgumentCaptor<TtFile> saved = ArgumentCaptor.forClass(TtFile.class);
|
||||
verify(mapper, times(2)).insertTtFile(saved.capture());
|
||||
List<TtFile> files = saved.getAllValues();
|
||||
Assert.assertEquals("统一登录入口", files.get(0).getFileName());
|
||||
Assert.assertEquals("https://img.yidaima.cn/1_统一登录入口-aaaa", files.get(0).getFileUrl());
|
||||
Assert.assertEquals("管理端数据仪表盘", files.get(1).getFileName());
|
||||
Assert.assertEquals("https://img.yidaima.cn/2_管理端数据仪表盘-bbbb", files.get(1).getFileUrl());
|
||||
Assert.assertEquals("测试项目", files.get(0).getCodeName());
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,55 @@ public class TtProjectInfoServiceImplTest
|
||||
Assert.assertEquals(1, result.getUnchangedCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatesExistingProjectWhenProjectNumberMatches()
|
||||
{
|
||||
TtProjectInfo existing = new TtProjectInfo();
|
||||
existing.setId(31);
|
||||
existing.setProjectNum("s031");
|
||||
existing.setProjectName("旧项目名称");
|
||||
existing.setProjectName1("旧源码名称");
|
||||
existing.setProjectUrl("https://pan.quark.cn/s/old");
|
||||
|
||||
when(parser.parse(file, "QUARK")).thenReturn(Collections.singletonList(quarkRow));
|
||||
when(projectInfoMapper.selectTtProjectInfoList(any(TtProjectInfo.class)))
|
||||
.thenReturn(Collections.singletonList(existing));
|
||||
when(projectInfoMapper.updateProjectQuarkUrl(31,
|
||||
"https://pan.quark.cn/s/741bf63df1bb")).thenReturn(1);
|
||||
|
||||
ProjectLinkImportResult result = service.importProjectLinks(file, "QUARK");
|
||||
|
||||
verify(projectInfoMapper).updateProjectQuarkUrl(31,
|
||||
"https://pan.quark.cn/s/741bf63df1bb");
|
||||
verify(projectInfoMapper, never()).insertTtProjectInfo(any(TtProjectInfo.class));
|
||||
Assert.assertEquals(1, result.getUpdatedCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatesExistingProjectWhenSourceNameMatches()
|
||||
{
|
||||
quarkRow.setProjectName("婚纱摄影管理系统");
|
||||
TtProjectInfo existing = new TtProjectInfo();
|
||||
existing.setId(31);
|
||||
existing.setProjectNum("S999");
|
||||
existing.setProjectName("另一个项目名称");
|
||||
existing.setProjectName1("婚纱摄影管理系统");
|
||||
existing.setProjectUrl("https://pan.quark.cn/s/old");
|
||||
|
||||
when(parser.parse(file, "QUARK")).thenReturn(Collections.singletonList(quarkRow));
|
||||
when(projectInfoMapper.selectTtProjectInfoList(any(TtProjectInfo.class)))
|
||||
.thenReturn(Collections.singletonList(existing));
|
||||
when(projectInfoMapper.updateProjectQuarkUrl(31,
|
||||
"https://pan.quark.cn/s/741bf63df1bb")).thenReturn(1);
|
||||
|
||||
ProjectLinkImportResult result = service.importProjectLinks(file, "QUARK");
|
||||
|
||||
verify(projectInfoMapper).updateProjectQuarkUrl(31,
|
||||
"https://pan.quark.cn/s/741bf63df1bb");
|
||||
verify(projectInfoMapper, never()).insertTtProjectInfo(any(TtProjectInfo.class));
|
||||
Assert.assertEquals(1, result.getUpdatedCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatesOnlyBaiduLinkForExistingProjectAndAddsPasscode()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.ruoyi.office.service.impl;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
@@ -9,11 +12,13 @@ import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.office.domain.PublishMaterial;
|
||||
import com.ruoyi.office.domain.TtCode;
|
||||
import com.ruoyi.office.domain.TtCopyTemplate;
|
||||
import com.ruoyi.office.domain.TtFile;
|
||||
import com.ruoyi.office.domain.TtPublishTask;
|
||||
import com.ruoyi.office.mapper.TtPublishTaskMapper;
|
||||
import com.ruoyi.office.service.CopyTemplateRenderer;
|
||||
import com.ruoyi.office.service.ITtCodeService;
|
||||
import com.ruoyi.office.service.ITtCopyTemplateService;
|
||||
import com.ruoyi.office.service.ITtFileService;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
@@ -26,6 +31,7 @@ import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -47,6 +53,9 @@ public class TtPublishTaskServiceImplTest
|
||||
@Mock
|
||||
private ITtCopyTemplateService copyTemplateService;
|
||||
|
||||
@Mock
|
||||
private ITtFileService fileService;
|
||||
|
||||
@Mock
|
||||
private CopyTemplateRenderer copyTemplateRenderer;
|
||||
|
||||
@@ -65,6 +74,7 @@ public class TtPublishTaskServiceImplTest
|
||||
code.setVideoFile("/profile/demo.mp4");
|
||||
when(codeService.selectTtCodeByCodeId(40L)).thenReturn(code);
|
||||
when(copyTemplateService.selectEnabledTemplates("CODE")).thenReturn(Collections.emptyList());
|
||||
when(fileService.selectTtFileByCodeName(code.getCodeName())).thenReturn(Collections.emptyList());
|
||||
|
||||
SysUser user = new SysUser();
|
||||
user.setUserName("publisher");
|
||||
@@ -80,13 +90,21 @@ public class TtPublishTaskServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void leavesMediaEmptyForManualSelection()
|
||||
public void loadsProjectImagesFromFileManagement()
|
||||
{
|
||||
TtFile first = projectFile(1L, "http://img.yidaima.cn/1-home.png");
|
||||
TtFile duplicate = projectFile(2L, "http://img.yidaima.cn/1-home.png");
|
||||
TtFile second = projectFile(3L, "http://img.yidaima.cn/2-detail.jpg");
|
||||
when(fileService.selectTtFileByCodeName(code.getCodeName()))
|
||||
.thenReturn(Arrays.asList(first, duplicate, second));
|
||||
|
||||
PublishMaterial material = service.selectProjectMaterial(40L, "xiaohongshu");
|
||||
|
||||
Assert.assertEquals(code.getCodeName(), material.getTitle());
|
||||
Assert.assertNull(material.getVideoUrl());
|
||||
Assert.assertTrue(material.getImageUrls().isEmpty());
|
||||
Assert.assertEquals(Arrays.asList(
|
||||
"http://img.yidaima.cn/1-home.png",
|
||||
"http://img.yidaima.cn/2-detail.jpg"), material.getImageUrls());
|
||||
Assert.assertNull(material.getCoverUrl());
|
||||
}
|
||||
|
||||
@@ -350,6 +368,85 @@ public class TtPublishTaskServiceImplTest
|
||||
? new java.util.Date() : created.getCreateTime()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void followsRedirectWhenReadingRemotePublishImage() throws Exception
|
||||
{
|
||||
byte[] image = "test-png-content".getBytes(StandardCharsets.UTF_8);
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
int port = server.getAddress().getPort();
|
||||
server.createContext("/redirect", exchange -> {
|
||||
exchange.getResponseHeaders().set("Location", "http://127.0.0.1:" + port + "/image");
|
||||
exchange.sendResponseHeaders(301, -1);
|
||||
exchange.close();
|
||||
});
|
||||
server.createContext("/image", exchange -> {
|
||||
exchange.getResponseHeaders().set("Content-Type", "image/png");
|
||||
exchange.sendResponseHeaders(200, image.length);
|
||||
exchange.getResponseBody().write(image);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
|
||||
try
|
||||
{
|
||||
TtPublishTask task = new TtPublishTask();
|
||||
task.setTaskId("redirect-task");
|
||||
task.setImageUrls("[\"http://127.0.0.1:" + port + "/redirect\"]");
|
||||
when(publishTaskMapper.selectPublishTaskByBridge("redirect-task", "bridge-token"))
|
||||
.thenReturn(task);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
service.writeBridgeAsset("redirect-task", "bridge-token", "image", 0, response);
|
||||
|
||||
Assert.assertEquals("image/png", response.getContentType());
|
||||
Assert.assertArrayEquals(image, response.getContentAsByteArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodesUnicodePathWhenReadingRemotePublishImage() throws Exception
|
||||
{
|
||||
byte[] image = "unicode-path-image".getBytes(StandardCharsets.UTF_8);
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
int port = server.getAddress().getPort();
|
||||
server.createContext("/", exchange -> {
|
||||
if (!"/%E5%9B%BE%E7%89%87.png".equals(exchange.getRequestURI().getRawPath()))
|
||||
{
|
||||
exchange.sendResponseHeaders(404, -1);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
exchange.getResponseHeaders().set("Content-Type", "image/png");
|
||||
exchange.sendResponseHeaders(200, image.length);
|
||||
exchange.getResponseBody().write(image);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
|
||||
try
|
||||
{
|
||||
TtPublishTask task = new TtPublishTask();
|
||||
task.setTaskId("unicode-path-task");
|
||||
task.setImageUrls("[\"http://127.0.0.1:" + port + "/图片.png\"]");
|
||||
when(publishTaskMapper.selectPublishTaskByBridge("unicode-path-task", "bridge-token"))
|
||||
.thenReturn(task);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
service.writeBridgeAsset("unicode-path-task", "bridge-token", "image", 0, response);
|
||||
|
||||
Assert.assertEquals("image/png", response.getContentType());
|
||||
Assert.assertArrayEquals(image, response.getContentAsByteArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private TtCopyTemplate template(Long id, String name, String group)
|
||||
{
|
||||
TtCopyTemplate template = new TtCopyTemplate();
|
||||
@@ -360,4 +457,13 @@ public class TtPublishTaskServiceImplTest
|
||||
template.setStatus("0");
|
||||
return template;
|
||||
}
|
||||
|
||||
private TtFile projectFile(Long id, String url)
|
||||
{
|
||||
TtFile file = new TtFile();
|
||||
file.setFileId(id);
|
||||
file.setCodeName(code.getCodeName());
|
||||
file.setFileUrl(url);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user