Compare commits

3 Commits

Author SHA1 Message Date
王鹏
a1a806f266 feat: support article demo videos and category counts 2026-08-17 15:50:17 +08:00
王鹏
2bf0a6db59 支持自定义积分充值来源 2026-07-30 16:39:08 +08:00
王鹏
4bda30f9c2 feat: 支持S/K精品源码付费规格转换 2026-07-30 14:25:22 +08:00
12 changed files with 332 additions and 48 deletions

View File

@@ -2,6 +2,7 @@ package com.ruoyi.web.controller.app;
import com.ruoyi.app.domain.AppBlogArticle; import com.ruoyi.app.domain.AppBlogArticle;
import com.ruoyi.app.mapper.AppBlogArticleMapper; import com.ruoyi.app.mapper.AppBlogArticleMapper;
import com.ruoyi.app.mapper.AppResourceMapper;
import com.ruoyi.app.service.ImageUrlService; import com.ruoyi.app.service.ImageUrlService;
import com.ruoyi.app.service.IAppBlogArticleService; import com.ruoyi.app.service.IAppBlogArticleService;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
@@ -19,7 +20,9 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 文章Controller * 文章Controller
@@ -39,6 +42,9 @@ public class AppBlogArticleController extends BaseController {
@Resource @Resource
private AppBlogArticleMapper appBlogArticleMapper; private AppBlogArticleMapper appBlogArticleMapper;
@Resource
private AppResourceMapper appResourceMapper;
@Resource @Resource
private ITtFileService fileService; private ITtFileService fileService;
@@ -87,6 +93,19 @@ public class AppBlogArticleController extends BaseController {
return success(appBlogArticle); return success(appBlogArticle);
} }
/**
* 获取首页四个分类的可见内容数量
*/
@GetMapping(value = "/app/homeCategoryCounts")
public AjaxResult homeCategoryCounts() {
Map<String, Long> counts = new LinkedHashMap<>();
counts.put("highQuality", appBlogArticleMapper.countByArticleType(7L, 1L));
counts.put("resource", appResourceMapper.countByIsShow(1L));
counts.put("integral", appBlogArticleMapper.countByArticleType(4L, 1L));
counts.put("free", appBlogArticleMapper.countByArticleType(8L, 1L));
return success(counts);
}
/** /**
* 新增文章 * 新增文章
*/ */

View File

@@ -3,12 +3,16 @@ package com.ruoyi.office.service.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ruoyi.app.domain.AppBlogArticle; import com.ruoyi.app.domain.AppBlogArticle;
import com.ruoyi.app.domain.AppResource; import com.ruoyi.app.domain.AppResource;
import com.ruoyi.app.domain.AppResourceList; import com.ruoyi.app.domain.AppResourceList;
import com.ruoyi.app.mapper.AppBlogArticleMapper; import com.ruoyi.app.mapper.AppBlogArticleMapper;
import com.ruoyi.app.mapper.AppResourceMapper; import com.ruoyi.app.mapper.AppResourceMapper;
import com.ruoyi.app.service.IAppResourceService;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.office.domain.TtCopyTemplate; import com.ruoyi.office.domain.TtCopyTemplate;
@@ -33,6 +37,18 @@ import com.ruoyi.office.service.ITtCodeService;
*/ */
@Service @Service
public class TtCodeServiceImpl implements ITtCodeService { public class TtCodeServiceImpl implements ITtCodeService {
private static final Pattern PREMIUM_PROJECT_CODE_PATTERN =
Pattern.compile("(?i)(?:^|【)\\s*([SK])\\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 BASIC_SPEC_NAME = "源码 + 数据库 + 论文 + 答辩PPT";
private static final String DEPLOYMENT_SPEC_NAME = "源码 + 数据库 + 论文 + 答辩PPT + 项目部署";
private static final String DEPLOYMENT_CONTACT = "调试部署加微信forfeastcoding";
@Autowired @Autowired
private TtCodeMapper ttCodeMapper; private TtCodeMapper ttCodeMapper;
@Autowired @Autowired
@@ -42,6 +58,8 @@ public class TtCodeServiceImpl implements ITtCodeService {
@Autowired @Autowired
private AppResourceMapper appResourceMapper; private AppResourceMapper appResourceMapper;
@Autowired @Autowired
private IAppResourceService appResourceService;
@Autowired
private TtProjectInfoMapper ttProjectInfoMapper; private TtProjectInfoMapper ttProjectInfoMapper;
@Autowired @Autowired
private TtCopyTemplateMapper ttCopyTemplateMapper; private TtCopyTemplateMapper ttCopyTemplateMapper;
@@ -150,7 +168,13 @@ public class TtCodeServiceImpl implements ITtCodeService {
if (ttProjectInfo == null) { if (ttProjectInfo == null) {
throw new ServiceException("未找到对应的项目清单信息"); throw new ServiceException("未找到对应的项目清单信息");
} }
ttCode.setDiskLink(ttProjectInfo.getProjectBaiduUrl()); String projectSeries = resolveProjectSeries(ttCode, ttProjectInfo);
boolean premiumProject = isPremiumProject(projectSeries);
if (premiumProject && StringUtils.isEmpty(ttProjectInfo.getProjectUrl())) {
throw new ServiceException("该项目未配置夸克网盘链接,无法生成付费规格");
}
ttCode.setDiskLink(premiumProject
? ttProjectInfo.getProjectUrl() : ttProjectInfo.getProjectBaiduUrl());
TtFile file = new TtFile(); TtFile file = new TtFile();
file.setCodeName(ttCode.getCodeName()); file.setCodeName(ttCode.getCodeName());
List<TtFile> fileList = fileMapper.selectTtFileList(file); List<TtFile> fileList = fileMapper.selectTtFileList(file);
@@ -160,7 +184,8 @@ public class TtCodeServiceImpl implements ITtCodeService {
AppBlogArticle existingArticle = appBlogArticleMapper.selectAppBlogArticleByTitle(ttCode.getCodeName()); AppBlogArticle existingArticle = appBlogArticleMapper.selectAppBlogArticleByTitle(ttCode.getCodeName());
if (existingArticle != null) { if (existingArticle != null) {
if (repairIncompleteConversion(existingArticle, ttProjectInfo, content, firstImageUrl)) { if (repairIncompleteConversion(existingArticle, ttProjectInfo, content,
firstImageUrl, projectSeries)) {
markCodePublished(ttCode); markCodePublished(ttCode);
return "源码转文章成功,已补全资源链接!"; return "源码转文章成功,已补全资源链接!";
} }
@@ -174,17 +199,16 @@ public class TtCodeServiceImpl implements ITtCodeService {
if (!StringUtils.isEmpty(firstImageUrl)) { if (!StringUtils.isEmpty(firstImageUrl)) {
resource.setShowImg(firstImageUrl); resource.setShowImg(firstImageUrl);
} }
resource.setResourceType(5L); configureResource(resource, projectSeries);
resource.setIsShow(0L); resource.setIsShow(0L);
resource.setIsAd(2L); List<AppResourceList> resourceList = buildResourceList(ttProjectInfo, null, projectSeries);
resource.setAdNumber(100L); resource.setAppResourceListList(resourceList);
resource.setPriceFen(0); appResourceService.insertAppResource(resource);
resource.setCreateTime(new Date());
appResourceMapper.insertAppResource(resource);
//新增文章 //新增文章
AppBlogArticle article = new AppBlogArticle(); AppBlogArticle article = new AppBlogArticle();
article.setTitle(ttCode.getCodeName()); article.setTitle(ttCode.getCodeName());
article.setArticleType(4L); article.setArticleType(premiumProject
? PREMIUM_SOURCE_ARTICLE_TYPE : SOURCE_SHARE_ARTICLE_TYPE);
article.setIsRecommendation(1L); article.setIsRecommendation(1L);
article.setIsShow(1L); article.setIsShow(1L);
article.setIsAd(1L); article.setIsAd(1L);
@@ -195,12 +219,6 @@ public class TtCodeServiceImpl implements ITtCodeService {
article.setAppResourceId(resource.getId()); article.setAppResourceId(resource.getId());
article.setCreateTime(new Date()); article.setCreateTime(new Date());
appBlogArticleMapper.insertAppBlogArticle(article); appBlogArticleMapper.insertAppBlogArticle(article);
//新增资源网盘链接
List<AppResourceList> resourceList = buildResourceList(ttProjectInfo, resource.getId());
if (resourceList.size() > 0)
{
appResourceMapper.batchAppResourceList(resourceList);
}
markCodePublished(ttCode); markCodePublished(ttCode);
return "源码转文章成功!"; return "源码转文章成功!";
} }
@@ -209,7 +227,8 @@ public class TtCodeServiceImpl implements ITtCodeService {
* 修复旧转换流程异常后已经生成文章、但尚未写入下载链接的半成品数据。 * 修复旧转换流程异常后已经生成文章、但尚未写入下载链接的半成品数据。
*/ */
private boolean repairIncompleteConversion(AppBlogArticle article, TtProjectInfo projectInfo, private boolean repairIncompleteConversion(AppBlogArticle article, TtProjectInfo projectInfo,
String content, String firstImageUrl) String content, String firstImageUrl,
String projectSeries)
{ {
if (article.getAppResourceId() == null) if (article.getAppResourceId() == null)
{ {
@@ -222,7 +241,8 @@ public class TtCodeServiceImpl implements ITtCodeService {
return false; return false;
} }
List<AppResourceList> resourceList = buildResourceList(projectInfo, resource.getId()); List<AppResourceList> resourceList = buildResourceList(
projectInfo, resource.getId(), projectSeries);
if (resourceList.isEmpty()) if (resourceList.isEmpty())
{ {
return false; return false;
@@ -233,16 +253,20 @@ public class TtCodeServiceImpl implements ITtCodeService {
{ {
resource.setShowImg(firstImageUrl); resource.setShowImg(firstImageUrl);
} }
resource.setPriceFen(0); configureResource(resource, projectSeries);
appResourceMapper.updateAppResource(resource); resource.setAppResourceListList(resourceList);
appResourceService.updateAppResource(resource);
article.setContentInfo(content); article.setContentInfo(content);
if (!StringUtils.isEmpty(firstImageUrl)) if (!StringUtils.isEmpty(firstImageUrl))
{ {
article.setShowImg(firstImageUrl); article.setShowImg(firstImageUrl);
} }
if (isPremiumProject(projectSeries))
{
article.setArticleType(PREMIUM_SOURCE_ARTICLE_TYPE);
}
appBlogArticleMapper.updateAppBlogArticle(article); appBlogArticleMapper.updateAppBlogArticle(article);
appResourceMapper.batchAppResourceList(resourceList);
return true; return true;
} }
@@ -255,9 +279,26 @@ public class TtCodeServiceImpl implements ITtCodeService {
return fileList.get(0).getFileUrl(); return fileList.get(0).getFileUrl();
} }
private List<AppResourceList> buildResourceList(TtProjectInfo projectInfo, Long resourceId) private List<AppResourceList> buildResourceList(TtProjectInfo projectInfo, Long resourceId,
String projectSeries)
{ {
List<AppResourceList> resourceList = new ArrayList<AppResourceList>(); List<AppResourceList> resourceList = new ArrayList<AppResourceList>();
if ("K".equals(projectSeries))
{
addResourceList(resourceList, resourceId, BASIC_SPEC_NAME,
projectInfo.getProjectUrl(), 1900, 1);
addResourceList(resourceList, resourceId, DEPLOYMENT_SPEC_NAME,
DEPLOYMENT_CONTACT, 6600, 2);
return resourceList;
}
if ("S".equals(projectSeries))
{
addResourceList(resourceList, resourceId, BASIC_SPEC_NAME,
projectInfo.getProjectUrl(), 5900, 1);
addResourceList(resourceList, resourceId, DEPLOYMENT_SPEC_NAME,
DEPLOYMENT_CONTACT, 9900, 2);
return resourceList;
}
addResourceList(resourceList, resourceId, "百度网盘", projectInfo.getProjectBaiduUrl()); addResourceList(resourceList, resourceId, "百度网盘", projectInfo.getProjectBaiduUrl());
addResourceList(resourceList, resourceId, "夸克网盘", projectInfo.getProjectUrl()); addResourceList(resourceList, resourceId, "夸克网盘", projectInfo.getProjectUrl());
return resourceList; return resourceList;
@@ -281,6 +322,57 @@ public class TtCodeServiceImpl implements ITtCodeService {
resourceList.add(item); resourceList.add(item);
} }
private void addResourceList(List<AppResourceList> resourceList, Long resourceId,
String name, String url, int priceFen, int sortOrder)
{
AppResourceList item = new AppResourceList();
item.setAppResourceId(resourceId);
item.setListName(name);
item.setListUrl(url);
item.setPassword("");
item.setPriceFen(priceFen);
item.setStatus(1);
item.setSortOrder(sortOrder);
resourceList.add(item);
}
private void configureResource(AppResource resource, String projectSeries)
{
if (isPremiumProject(projectSeries))
{
resource.setResourceType(PREMIUM_PROJECT_RESOURCE_TYPE);
resource.setIsAd(PAID_ACCESS_TYPE);
resource.setAdNumber(0L);
resource.setPriceFen("K".equals(projectSeries) ? 1900 : 5900);
return;
}
resource.setResourceType(SOURCE_SHARE_RESOURCE_TYPE);
resource.setIsAd(POINTS_ACCESS_TYPE);
resource.setAdNumber(100L);
resource.setPriceFen(0);
}
private boolean isPremiumProject(String projectSeries)
{
return "S".equals(projectSeries) || "K".equals(projectSeries);
}
private String resolveProjectSeries(TtCode code, TtProjectInfo projectInfo)
{
String projectSeries = extractProjectSeries(projectInfo.getProjectNum());
return projectSeries == null ? extractProjectSeries(code.getCodeName()) : projectSeries;
}
private String extractProjectSeries(String value)
{
if (StringUtils.isEmpty(value))
{
return null;
}
Matcher matcher = PREMIUM_PROJECT_CODE_PATTERN.matcher(value);
return matcher.find() ? matcher.group(1).toUpperCase(Locale.ROOT) : null;
}
private void markCodePublished(TtCode ttCode) private void markCodePublished(TtCode ttCode)
{ {
ttCode.setPublishFlag("Y"); ttCode.setPublishFlag("Y");

View File

@@ -9,6 +9,8 @@ import com.ruoyi.app.domain.AppResource;
import com.ruoyi.app.domain.AppResourceList; import com.ruoyi.app.domain.AppResourceList;
import com.ruoyi.app.mapper.AppBlogArticleMapper; import com.ruoyi.app.mapper.AppBlogArticleMapper;
import com.ruoyi.app.mapper.AppResourceMapper; import com.ruoyi.app.mapper.AppResourceMapper;
import com.ruoyi.app.service.IAppResourceService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.office.domain.TtCode; import com.ruoyi.office.domain.TtCode;
import com.ruoyi.office.domain.TtCopyTemplate; import com.ruoyi.office.domain.TtCopyTemplate;
import com.ruoyi.office.domain.TtFile; import com.ruoyi.office.domain.TtFile;
@@ -47,6 +49,8 @@ public class TtCodeServiceImplTest
@Mock @Mock
private AppResourceMapper resourceMapper; private AppResourceMapper resourceMapper;
@Mock @Mock
private IAppResourceService resourceService;
@Mock
private TtProjectInfoMapper projectInfoMapper; private TtProjectInfoMapper projectInfoMapper;
@Mock @Mock
private TtCopyTemplateMapper copyTemplateMapper; private TtCopyTemplateMapper copyTemplateMapper;
@@ -68,6 +72,7 @@ public class TtCodeServiceImplTest
code.setCodeName("【S009】自习室预约选座系统"); code.setCodeName("【S009】自习室预约选座系统");
projectInfo = new TtProjectInfo(); projectInfo = new TtProjectInfo();
projectInfo.setProjectNum("S009");
projectInfo.setProjectBaiduUrl("https://pan.baidu.com/s/example"); projectInfo.setProjectBaiduUrl("https://pan.baidu.com/s/example");
projectInfo.setProjectUrl("https://pan.quark.cn/s/example"); projectInfo.setProjectUrl("https://pan.quark.cn/s/example");
@@ -80,16 +85,17 @@ public class TtCodeServiceImplTest
when(copyTemplateMapper.selectTtCopyTemplateByTemplateId(1L)).thenReturn(template); when(copyTemplateMapper.selectTtCopyTemplateByTemplateId(1L)).thenReturn(template);
when(codeMapper.selectTtCodeByCodeId(9L)).thenReturn(code); when(codeMapper.selectTtCodeByCodeId(9L)).thenReturn(code);
when(projectInfoMapper.selectTtProjectInfoByName(code.getCodeName())).thenReturn(projectInfo); when(projectInfoMapper.selectTtProjectInfoByName(any())).thenReturn(projectInfo);
when(fileMapper.selectTtFileList(any())).thenReturn(Arrays.asList(firstImage, secondImage)); when(fileMapper.selectTtFileList(any())).thenReturn(Arrays.asList(firstImage, secondImage));
when(templateRenderer.render(template, code)).thenReturn("模板正文"); when(templateRenderer.render(template, code)).thenReturn("模板正文");
} }
@Test @Test
public void fillsRequiredResourceListFields() public void createsSPremiumProjectWithPaidSpecs()
{ {
projectInfo.setProjectNum(null);
when(articleMapper.selectAppBlogArticleByTitle(code.getCodeName())).thenReturn(null); when(articleMapper.selectAppBlogArticleByTitle(code.getCodeName())).thenReturn(null);
when(resourceMapper.insertAppResource(any())).thenAnswer(invocation -> { when(resourceService.insertAppResource(any())).thenAnswer(invocation -> {
AppResource resource = invocation.getArgument(0); AppResource resource = invocation.getArgument(0);
resource.setId(100L); resource.setId(100L);
return 1; return 1;
@@ -98,20 +104,45 @@ public class TtCodeServiceImplTest
service.transToArticle1(9L, 1L); service.transToArticle1(9L, 1L);
ArgumentCaptor<AppResource> resourceCaptor = ArgumentCaptor.forClass(AppResource.class); ArgumentCaptor<AppResource> resourceCaptor = ArgumentCaptor.forClass(AppResource.class);
verify(resourceMapper).insertAppResource(resourceCaptor.capture()); verify(resourceService).insertAppResource(resourceCaptor.capture());
Assert.assertEquals(Integer.valueOf(0), resourceCaptor.getValue().getPriceFen()); AppResource resource = resourceCaptor.getValue();
Assert.assertEquals("https://img.example.com/first.png", resourceCaptor.getValue().getShowImg()); Assert.assertEquals(Long.valueOf(6L), resource.getResourceType());
Assert.assertEquals(Long.valueOf(3L), resource.getIsAd());
Assert.assertEquals(Integer.valueOf(5900), resource.getPriceFen());
Assert.assertEquals("https://img.example.com/first.png", resource.getShowImg());
Assert.assertEquals("https://pan.quark.cn/s/example", code.getDiskLink());
assertPremiumSpecs(resource.getAppResourceListList(), 5900, 9900);
ArgumentCaptor<AppBlogArticle> articleCaptor = ArgumentCaptor.forClass(AppBlogArticle.class); ArgumentCaptor<AppBlogArticle> articleCaptor = ArgumentCaptor.forClass(AppBlogArticle.class);
verify(articleMapper).insertAppBlogArticle(articleCaptor.capture()); verify(articleMapper).insertAppBlogArticle(articleCaptor.capture());
Assert.assertEquals("https://img.example.com/first.png", articleCaptor.getValue().getShowImg()); Assert.assertEquals("https://img.example.com/first.png", articleCaptor.getValue().getShowImg());
Assert.assertEquals(Long.valueOf(7L), articleCaptor.getValue().getArticleType());
}
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class); @Test
verify(resourceMapper).batchAppResourceList(listCaptor.capture()); public void createsKPremiumProjectWithPaidSpecs()
List<AppResourceList> items = listCaptor.getValue(); {
Assert.assertEquals(2, items.size()); code.setCodeName("【K001】药店进销存系统");
assertResourceListDefaults(items.get(0), 0); projectInfo.setProjectNum(" k001 ");
assertResourceListDefaults(items.get(1), 1); when(resourceService.insertAppResource(any())).thenAnswer(invocation -> {
AppResource resource = invocation.getArgument(0);
resource.setId(101L);
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(1900), resource.getPriceFen());
assertPremiumSpecs(resource.getAppResourceListList(), 1900, 6600);
ArgumentCaptor<AppBlogArticle> articleCaptor = ArgumentCaptor.forClass(AppBlogArticle.class);
verify(articleMapper).insertAppBlogArticle(articleCaptor.capture());
Assert.assertEquals(Long.valueOf(7L), articleCaptor.getValue().getArticleType());
} }
@Test @Test
@@ -134,19 +165,80 @@ public class TtCodeServiceImplTest
Assert.assertEquals("源码转文章成功,已补全资源链接!", message); Assert.assertEquals("源码转文章成功,已补全资源链接!", message);
Assert.assertEquals("https://img.example.com/first.png", existingResource.getShowImg()); Assert.assertEquals("https://img.example.com/first.png", existingResource.getShowImg());
Assert.assertEquals("https://img.example.com/first.png", existingArticle.getShowImg()); Assert.assertEquals("https://img.example.com/first.png", existingArticle.getShowImg());
verify(resourceMapper).updateAppResource(existingResource); Assert.assertEquals(Long.valueOf(6L), existingResource.getResourceType());
Assert.assertEquals(Long.valueOf(3L), existingResource.getIsAd());
Assert.assertEquals(Long.valueOf(7L), existingArticle.getArticleType());
assertPremiumSpecs(existingResource.getAppResourceListList(), 5900, 9900);
verify(resourceService).updateAppResource(existingResource);
verify(articleMapper).updateAppBlogArticle(existingArticle); verify(articleMapper).updateAppBlogArticle(existingArticle);
verify(resourceMapper).batchAppResourceList(any()); verify(resourceService, never()).insertAppResource(any());
verify(resourceMapper, never()).insertAppResource(any());
verify(articleMapper, never()).insertAppBlogArticle(any()); verify(articleMapper, never()).insertAppBlogArticle(any());
} }
private void assertResourceListDefaults(AppResourceList item, int sortOrder) @Test
public void keepsLegacyRulesForOtherProjectSeries()
{ {
Assert.assertEquals(Integer.valueOf(0), item.getPriceFen()); code.setCodeName("【A001】普通源码项目");
projectInfo.setProjectNum("A001");
when(resourceService.insertAppResource(any())).thenAnswer(invocation -> {
AppResource resource = invocation.getArgument(0);
resource.setId(102L);
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(5L), resource.getResourceType());
Assert.assertEquals(Long.valueOf(2L), resource.getIsAd());
Assert.assertEquals(Integer.valueOf(0), resource.getPriceFen());
Assert.assertEquals("https://pan.baidu.com/s/example", code.getDiskLink());
Assert.assertEquals("百度网盘", resource.getAppResourceListList().get(0).getListName());
Assert.assertEquals("夸克网盘", resource.getAppResourceListList().get(1).getListName());
ArgumentCaptor<AppBlogArticle> articleCaptor = ArgumentCaptor.forClass(AppBlogArticle.class);
verify(articleMapper).insertAppBlogArticle(articleCaptor.capture());
Assert.assertEquals(Long.valueOf(4L), articleCaptor.getValue().getArticleType());
}
@Test
public void rejectsPremiumProjectWithoutQuarkLink()
{
projectInfo.setProjectUrl(null);
try
{
service.transToArticle1(9L, 1L);
Assert.fail("应拒绝未配置夸克链接的精品项目");
}
catch (ServiceException exception)
{
Assert.assertEquals("该项目未配置夸克网盘链接,无法生成付费规格", exception.getMessage());
}
verify(resourceService, never()).insertAppResource(any());
verify(articleMapper, never()).insertAppBlogArticle(any());
}
private void assertPremiumSpecs(List<AppResourceList> items, int firstPrice, int secondPrice)
{
Assert.assertEquals(2, items.size());
assertPremiumSpec(items.get(0), "源码 + 数据库 + 论文 + 答辩PPT",
"https://pan.quark.cn/s/example", firstPrice, 1);
assertPremiumSpec(items.get(1), "源码 + 数据库 + 论文 + 答辩PPT + 项目部署",
"调试部署加微信forfeastcoding", secondPrice, 2);
}
private void assertPremiumSpec(AppResourceList item, String name, String url,
int priceFen, int sortOrder)
{
Assert.assertEquals(name, item.getListName());
Assert.assertEquals(url, item.getListUrl());
Assert.assertEquals(Integer.valueOf(priceFen), item.getPriceFen());
Assert.assertEquals(Integer.valueOf(1), item.getStatus()); Assert.assertEquals(Integer.valueOf(1), item.getStatus());
Assert.assertEquals(Integer.valueOf(sortOrder), item.getSortOrder()); Assert.assertEquals(Integer.valueOf(sortOrder), item.getSortOrder());
Assert.assertEquals(Long.valueOf(100L), item.getAppResourceId());
Assert.assertEquals("", item.getPassword()); Assert.assertEquals("", item.getPassword());
} }
} }

View File

@@ -43,6 +43,14 @@ public class AppBlogArticle extends BaseEntity
/** List-card thumbnail URL (not persisted). */ /** List-card thumbnail URL (not persisted). */
private String showImgThumb; private String showImgThumb;
/** 视频号演示视频feedId */
@Excel(name = "视频号演示视频ID")
private String videoFeedId;
/** 第二个视频号演示视频feedId */
@Excel(name = "视频号演示视频ID 2")
private String videoFeedId2;
/** 关联已有资源关联app_resource id */ /** 关联已有资源关联app_resource id */
@Excel(name = "关联已有资源", readConverterExp = "关=联app_resource,i=d") @Excel(name = "关联已有资源", readConverterExp = "关=联app_resource,i=d")
private Long appResourceId; private Long appResourceId;
@@ -171,6 +179,24 @@ public class AppBlogArticle extends BaseEntity
{ {
return showImgThumb; return showImgThumb;
} }
public void setVideoFeedId(String videoFeedId)
{
this.videoFeedId = videoFeedId;
}
public String getVideoFeedId()
{
return videoFeedId;
}
public void setVideoFeedId2(String videoFeedId2)
{
this.videoFeedId2 = videoFeedId2;
}
public String getVideoFeedId2()
{
return videoFeedId2;
}
public void setAppResourceId(Long appResourceId) public void setAppResourceId(Long appResourceId)
{ {
this.appResourceId = appResourceId; this.appResourceId = appResourceId;
@@ -260,6 +286,8 @@ public class AppBlogArticle extends BaseEntity
.append("contentInfo", getContentInfo()) .append("contentInfo", getContentInfo())
.append("articleType", getArticleType()) .append("articleType", getArticleType())
.append("showImg", getShowImg()) .append("showImg", getShowImg())
.append("videoFeedId", getVideoFeedId())
.append("videoFeedId2", getVideoFeedId2())
.append("appResourceId", getAppResourceId()) .append("appResourceId", getAppResourceId())
.append("lookNumber", getLookNumber()) .append("lookNumber", getLookNumber())
.append("loveNumber", getLoveNumber()) .append("loveNumber", getLoveNumber())

View File

@@ -3,6 +3,8 @@ package com.ruoyi.app.mapper;
import java.util.List; import java.util.List;
import com.ruoyi.app.domain.AppBlogArticle; import com.ruoyi.app.domain.AppBlogArticle;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
/** /**
@@ -37,6 +39,10 @@ public interface AppBlogArticleMapper
*/ */
public List<AppBlogArticle> selectAppBlogArticleList(AppBlogArticle appBlogArticle); public List<AppBlogArticle> selectAppBlogArticleList(AppBlogArticle appBlogArticle);
/** 查询指定类型的可见文章数量 */
@Select("select count(*) from app_blog_article where article_type = #{articleType} and is_show = #{isShow}")
public Long countByArticleType(@Param("articleType") Long articleType, @Param("isShow") Long isShow);
/** /**
* 新增文章 * 新增文章
* *

View File

@@ -4,6 +4,7 @@ import java.util.List;
import com.ruoyi.app.domain.AppResource; import com.ruoyi.app.domain.AppResource;
import com.ruoyi.app.domain.AppResourceList; import com.ruoyi.app.domain.AppResourceList;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Update;
/** /**
@@ -44,6 +45,10 @@ public interface AppResourceMapper
*/ */
public List<AppResource> selectAppResourceList(AppResource appResource); public List<AppResource> selectAppResourceList(AppResource appResource);
/** 查询可见资源数量 */
@Select("select count(*) from app_resource where is_show = #{isShow}")
public Long countByIsShow(@Param("isShow") Long isShow);
/** /**
* 新增资源 * 新增资源
* *

View File

@@ -4,6 +4,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -99,7 +100,9 @@ public class AppIntegralRecordServiceImpl implements IAppIntegralRecordService
@Override @Override
public int czAppIntegra(AppIntegralRecord appIntegralRecord) { public int czAppIntegra(AppIntegralRecord appIntegralRecord) {
appIntegralRecord.setSource("充值积分"); if (StringUtils.isBlank(appIntegralRecord.getSource())) {
appIntegralRecord.setSource("充值积分");
}
appIntegralRecord.setIsAdd(0L); appIntegralRecord.setIsAdd(0L);
appIntegralRecord.setIntegralTime(new Date()); appIntegralRecord.setIntegralTime(new Date());
int count = appIntegralRecordMapper.insertAppIntegralRecord(appIntegralRecord); int count = appIntegralRecordMapper.insertAppIntegralRecord(appIntegralRecord);

View File

@@ -11,6 +11,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="articleType" column="article_type" /> <result property="articleType" column="article_type" />
<result property="keyword" column="keyword" /> <result property="keyword" column="keyword" />
<result property="showImg" column="show_img" /> <result property="showImg" column="show_img" />
<result property="videoFeedId" column="video_feed_id" />
<result property="videoFeedId2" column="video_feed_id_2" />
<result property="appResourceId" column="app_resource_id" /> <result property="appResourceId" column="app_resource_id" />
<result property="lookNumber" column="look_number" /> <result property="lookNumber" column="look_number" />
<result property="loveNumber" column="love_number" /> <result property="loveNumber" column="love_number" />
@@ -46,12 +48,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectAppBlogArticleVo"> <sql id="selectAppBlogArticleVo">
select id, title, content_info, article_type, keyword, show_img, app_resource_id, look_number, weight, love_number, is_recommendation, is_show, is_ad, ad_number, del_flag, create_by, create_time, remark from app_blog_article select id, title, content_info, article_type, keyword, show_img, video_feed_id, video_feed_id_2, app_resource_id, look_number, weight, love_number, is_recommendation, is_show, is_ad, ad_number, del_flag, create_by, create_time, remark from app_blog_article
</sql> </sql>
<select id="selectAppBlogArticleList" parameterType="AppBlogArticle" resultMap="AppBlogArticleWithResourceResult"> <select id="selectAppBlogArticleList" parameterType="AppBlogArticle" resultMap="AppBlogArticleWithResourceResult">
select select
a.id, a.title, a.content_info, a.article_type, a.keyword, a.show_img, a.app_resource_id, a.id, a.title, a.content_info, a.article_type, a.keyword, a.show_img, a.video_feed_id, a.video_feed_id_2, a.app_resource_id,
a.look_number, a.weight, a.love_number, a.is_recommendation, a.is_show, a.is_ad, a.look_number, a.weight, a.love_number, a.is_recommendation, a.is_show, a.is_ad,
a.ad_number, a.del_flag, a.create_by, a.create_time, a.remark, a.ad_number, a.del_flag, a.create_by, a.create_time, a.remark,
r.id as resource_id, r.id as resource_id,
@@ -83,6 +85,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where> </where>
<choose> <choose>
<when test="orderType == 'look_number'"> ORDER BY a.look_number desc, a.create_time desc</when > <when test="orderType == 'look_number'"> ORDER BY a.look_number desc, a.create_time desc</when >
<when test="orderType == 'create_time'"> ORDER BY a.create_time desc, a.id desc</when>
<otherwise> ORDER BY a.weight desc, a.create_time desc</otherwise > <otherwise> ORDER BY a.weight desc, a.create_time desc</otherwise >
</choose> </choose>
</select> </select>
@@ -106,6 +109,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="articleType != null">article_type,</if> <if test="articleType != null">article_type,</if>
<if test="keyword != null and keyword != ''">keyword,</if> <if test="keyword != null and keyword != ''">keyword,</if>
<if test="showImg != null">show_img,</if> <if test="showImg != null">show_img,</if>
<if test="videoFeedId != null">video_feed_id,</if>
<if test="videoFeedId2 != null">video_feed_id_2,</if>
<if test="appResourceId != null">app_resource_id,</if> <if test="appResourceId != null">app_resource_id,</if>
<if test="lookNumber != null">look_number,</if> <if test="lookNumber != null">look_number,</if>
<if test="loveNumber != null">love_number,</if> <if test="loveNumber != null">love_number,</if>
@@ -125,6 +130,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="articleType != null">#{articleType},</if> <if test="articleType != null">#{articleType},</if>
<if test="keyword != null and keyword != ''">#{keyword},</if> <if test="keyword != null and keyword != ''">#{keyword},</if>
<if test="showImg != null">#{showImg},</if> <if test="showImg != null">#{showImg},</if>
<if test="videoFeedId != null">#{videoFeedId},</if>
<if test="videoFeedId2 != null">#{videoFeedId2},</if>
<if test="appResourceId != null">#{appResourceId},</if> <if test="appResourceId != null">#{appResourceId},</if>
<if test="lookNumber != null">#{lookNumber},</if> <if test="lookNumber != null">#{lookNumber},</if>
<if test="loveNumber != null">#{loveNumber},</if> <if test="loveNumber != null">#{loveNumber},</if>
@@ -148,6 +155,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="articleType != null">article_type = #{articleType},</if> <if test="articleType != null">article_type = #{articleType},</if>
<if test="keyword != null and keyword != ''">keyword = #{keyword},</if> <if test="keyword != null and keyword != ''">keyword = #{keyword},</if>
<if test="showImg != null">show_img = #{showImg},</if> <if test="showImg != null">show_img = #{showImg},</if>
<if test="videoFeedId != null">video_feed_id = #{videoFeedId},</if>
<if test="videoFeedId2 != null">video_feed_id_2 = #{videoFeedId2},</if>
<if test="appResourceId != null">app_resource_id = #{appResourceId},</if> <if test="appResourceId != null">app_resource_id = #{appResourceId},</if>
<if test="lookNumber != null">look_number = #{lookNumber},</if> <if test="lookNumber != null">look_number = #{lookNumber},</if>
<if test="loveNumber != null">love_number = #{loveNumber},</if> <if test="loveNumber != null">love_number = #{loveNumber},</if>

View File

@@ -187,6 +187,9 @@
<!-- 充值积分对话框 --> <!-- 充值积分对话框 -->
<el-dialog :title="title" :visible.sync="openCzjf" width="500px" append-to-body> <el-dialog :title="title" :visible.sync="openCzjf" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="充值来源" prop="source">
<el-input v-model="form.source" placeholder="请输入充值来源" />
</el-form-item>
<el-form-item label="充值分数" prop="integralNumber"> <el-form-item label="充值分数" prop="integralNumber">
<el-input v-model="form.integralNumber" placeholder="请输入充值分数" /> <el-input v-model="form.integralNumber" placeholder="请输入充值分数" />
</el-form-item> </el-form-item>
@@ -302,6 +305,7 @@ export default {
/** 充值积分按钮操作 */ /** 充值积分按钮操作 */
handleCzjf() { handleCzjf() {
this.reset(); this.reset();
this.form.source = "充值积分";
this.openCzjf = true; this.openCzjf = true;
this.title = "充值积分"; this.title = "充值积分";
}, },

View File

@@ -9,9 +9,6 @@
<el-option v-for="dict in dict.type.article_type" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-option v-for="dict in dict.type.article_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="关键字" prop="keyword">
<el-input v-model="queryParams.keyword" placeholder="请输入搜素关键字" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="推荐文章" prop="isRecommendation"> <el-form-item label="推荐文章" prop="isRecommendation">
<el-select v-model="queryParams.isRecommendation" placeholder="请选择是否为推荐文章" clearable> <el-select v-model="queryParams.isRecommendation" placeholder="请选择是否为推荐文章" clearable>
<el-option v-for="dict in dict.type.sys_yes_no2" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-option v-for="dict in dict.type.sys_yes_no2" :key="dict.value" :label="dict.label" :value="dict.value" />
@@ -58,8 +55,6 @@
<dict-tag :options="dict.type.article_type" :value="scope.row.articleType" /> <dict-tag :options="dict.type.article_type" :value="scope.row.articleType" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="搜素关键字" align="center" prop="keyword" />
<el-table-column label="游览次数" align="center" prop="lookNumber" /> <el-table-column label="游览次数" align="center" prop="lookNumber" />
<el-table-column label="点赞次数" align="center" prop="loveNumber" /> <el-table-column label="点赞次数" align="center" prop="loveNumber" />
<el-table-column label="权重" align="center" prop="weight" /> <el-table-column label="权重" align="center" prop="weight" />
@@ -80,8 +75,16 @@
</el-table-column> </el-table-column>
<!-- <el-table-column label="需要观看几次广告解锁" align="center" prop="adNumber" /> --> <!-- <el-table-column label="需要观看几次广告解锁" align="center" prop="adNumber" /> -->
<el-table-column label="关联已有资源" align="center" prop="appResourceId" /> <el-table-column label="关联已有资源" align="center" prop="appResourceId" />
<el-table-column label="演示视频" align="center" prop="videoFeedId" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.videoFeedId || scope.row.videoFeedId2 ? 'success' : 'info'" size="mini">
{{ scope.row.videoFeedId && scope.row.videoFeedId2
? '2个视频'
: (scope.row.videoFeedId || scope.row.videoFeedId2 ? '1个视频' : '未配置') }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="160px" /> <el-table-column label="创建时间" align="center" prop="createTime" width="160px" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
@@ -119,6 +122,20 @@
<el-input v-model="form.showImg" placeholder="请输入封面图url" /> <el-input v-model="form.showImg" placeholder="请输入封面图url" />
<image-upload style="margin-top: 10px;" v-model="form.showImg"/> <image-upload style="margin-top: 10px;" v-model="form.showImg"/>
</el-form-item> </el-form-item>
<el-form-item label="演示视频ID" prop="videoFeedId">
<el-input
v-model.trim="form.videoFeedId"
placeholder="请输入 export/ 开头的视频 feedId留空表示暂无演示视频"
clearable
/>
</el-form-item>
<el-form-item label="演示视频ID 2" prop="videoFeedId2">
<el-input
v-model.trim="form.videoFeedId2"
placeholder="请输入第二个视频的 feedId留空表示只有一个演示视频"
clearable
/>
</el-form-item>
<el-form-item label="权重" prop="weight"> <el-form-item label="权重" prop="weight">
<el-input v-model="form.weight" placeholder="请输入权重 999 表示为置顶" /> <el-input v-model="form.weight" placeholder="请输入权重 999 表示为置顶" />
</el-form-item> </el-form-item>
@@ -283,6 +300,8 @@
contentInfo: null, contentInfo: null,
articleType: null, articleType: null,
showImg: null, showImg: null,
videoFeedId: null,
videoFeedId2: null,
appResourceId: null, appResourceId: null,
lookNumber: null, lookNumber: null,
loveNumber: null, loveNumber: null,

View File

@@ -0,0 +1,4 @@
-- 为每篇文章单独配置微信视频号演示视频。
-- 视频号 finderUserName 由小程序统一配置,此处只保存视频 feedId。
ALTER TABLE `app_blog_article`
ADD COLUMN `video_feed_id` VARCHAR(255) NULL COMMENT '视频号演示视频feedId' AFTER `show_img`;

View File

@@ -0,0 +1,3 @@
-- 每篇文章最多维护两个微信视频号演示视频。
ALTER TABLE `app_blog_article`
ADD COLUMN `video_feed_id_2` VARCHAR(255) NULL COMMENT '第二个视频号演示视频feedId' AFTER `video_feed_id`;