init local code

This commit is contained in:
chenxing
2026-09-07 09:09:39 +08:00
commit 99cd26b3d2
259 changed files with 20554 additions and 0 deletions
@@ -0,0 +1,164 @@
package com.yunhai.cms.support;
import com.yunhai.cms.entity.CmsArticle;
import com.yunhai.cms.entity.CmsBanner;
import com.yunhai.cms.entity.CmsCategory;
import com.yunhai.cms.entity.CmsLink;
import com.yunhai.cms.entity.CmsSite;
import com.yunhai.cms.enums.ArticleStatusEnum;
import com.yunhai.cms.enums.CategoryTypeEnum;
import com.yunhai.cms.repository.CmsArticleRepository;
import com.yunhai.cms.repository.CmsAttachmentRepository;
import com.yunhai.cms.repository.CmsBannerRepository;
import com.yunhai.cms.repository.CmsCategoryRepository;
import com.yunhai.cms.repository.CmsLinkRepository;
import com.yunhai.cms.repository.CmsSiteRepository;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.CacheManager;
import java.time.LocalDateTime;
/**
* 集成测试基类:真实 Spring 上下文 + 本地 MySQL(bsch_cms_test) + Caffeine 缓存。
*
* <p>所有子类共享同一上下文配置(本类注解即完整配置),Spring TestContext 只启动一次,
* schema-cms-test.sql 的 DROP/CREATE 也只执行一次;跨测试的库与缓存清洁由
* {@link #clearCaches()} / {@link #purgeTables()} 负责:
* <ul>
* <li>@Transactional 用例:回滚即隔离,仅需清缓存(缓存是进程级全局的,不随事务回滚);</li>
* <li>非事务用例(事件 AFTER_COMMIT / MockMvc / 定时任务):@BeforeEach purge + @AfterEach purge。</li>
* </ul>
*/
@SpringBootTest(properties = "cms.schedule.auto-enabled=false")
@AutoConfigureMockMvc
public abstract class CmsIntegrationTestBase {
@Autowired
protected CacheManager cacheManager;
@Autowired
protected CmsSiteRepository siteRepository;
@Autowired
protected CmsCategoryRepository categoryRepository;
@Autowired
protected CmsArticleRepository articleRepository;
@Autowired
protected CmsBannerRepository bannerRepository;
@Autowired
protected CmsLinkRepository linkRepository;
@Autowired
protected CmsAttachmentRepository attachmentRepository;
@BeforeEach
void clearCachesBeforeEachTest() {
clearCaches();
}
/** 清空全部 CMS 缓存(cms:site / cms:article / cms:list),防止用例间缓存串扰 */
protected void clearCaches() {
for (String name : cacheManager.getCacheNames()) {
cacheManager.getCache(name).clear();
}
}
/** 清空 6 张业务表(无外键约束,顺序无碍),非事务用例的隔离手段 */
protected void purgeTables() {
attachmentRepository.deleteAllInBatch();
articleRepository.deleteAllInBatch();
categoryRepository.deleteAllInBatch();
bannerRepository.deleteAllInBatch();
linkRepository.deleteAllInBatch();
siteRepository.deleteAllInBatch();
}
// ===== 落库种子(返回带自增 id 的实体)=====
protected CmsSite persistSite(String code, Integer schId, String name) {
CmsSite site = new CmsSite();
site.setCode(code);
site.setSchId(schId);
site.setSchName("学校-" + schId);
site.setName(name);
site.setTheme("default");
site.setStatus(1);
return siteRepository.saveAndFlush(site);
}
/** 带上级站点的落库(院系子站,《院系子站方案》):parentSiteId 传 null 视为顶层 0 */
protected CmsSite persistSite(String code, Integer schId, String name, Integer parentSiteId) {
CmsSite site = persistSite(code, schId, name);
site.setParentSiteId(parentSiteId == null ? 0 : parentSiteId);
return siteRepository.saveAndFlush(site);
}
protected CmsCategory persistCategory(Integer siteId, Integer parentId, String name,
CategoryTypeEnum type) {
CmsCategory c = new CmsCategory();
c.setSiteId(siteId);
c.setParentId(parentId == null ? 0 : parentId);
c.setName(name);
c.setType(type.getCode());
c.setStatus(1);
return categoryRepository.saveAndFlush(c);
}
protected CmsArticle persistArticle(Integer siteId, Integer categoryId, String title,
ArticleStatusEnum status, LocalDateTime publishTime) {
CmsArticle a = new CmsArticle();
a.setSiteId(siteId);
a.setCategoryId(categoryId);
a.setTitle(title);
a.setContent("<p>" + title + " 正文</p>");
a.setSummary(title + " 摘要");
a.setStatus(status.getCode());
a.setPublishTime(publishTime);
return articleRepository.saveAndFlush(a);
}
protected CmsBanner persistBanner(Integer siteId, String position, String title,
LocalDateTime start, LocalDateTime end) {
CmsBanner b = new CmsBanner();
b.setSiteId(siteId);
b.setPosition(position);
b.setTitle(title);
b.setImageUrl("/upload/cms/banner.png");
b.setLinkType(3);
b.setLinkValue("https://www.example.com");
b.setStatus(1);
b.setStartTime(start);
b.setEndTime(end);
return bannerRepository.saveAndFlush(b);
}
protected CmsLink persistLink(Integer siteId, String groupName, String name) {
CmsLink l = new CmsLink();
l.setSiteId(siteId);
l.setGroupName(groupName);
l.setName(name);
l.setLinkUrl("https://www.example.com");
l.setStatus(1);
return linkRepository.saveAndFlush(l);
}
/** 简易轮询等待(无 awaitility 依赖):最长 waitMillis 内每 50ms 检查一次期望 */
protected static void awaitTrue(long waitMillis, java.util.function.BooleanSupplier condition)
throws InterruptedException {
long deadline = System.currentTimeMillis() + waitMillis;
while (System.currentTimeMillis() < deadline) {
if (condition.getAsBoolean()) {
return;
}
Thread.sleep(50);
}
}
protected static LocalDateTime nowPlusSeconds(long seconds) {
return LocalDateTime.now().plusSeconds(seconds);
}
protected static LocalDateTime nowMinusSeconds(long seconds) {
return LocalDateTime.now().minusSeconds(seconds);
}
}
@@ -0,0 +1,55 @@
package com.yunhai.cms.support;
import com.yunhai.platform.utils.model.BoaRoleInfo;
import com.yunhai.platform.utils.model.BoaUser;
import java.util.Collections;
/**
* 单测用户夹具:超管判定为 {@code BoaUser.type == 1}(平台约定,见 CmsAuthProperties)。
* <ul>
* <li>superAdmin —— type=1,可管理所有站点(方案 §7.2 规则①)</li>
* <li>schoolUser —— 校级角色,仅可管理 currentRole.schId 匹配的站点(方案 §7.2 规则②)</li>
* <li>plainUser —— 无角色无 currentRole,不能管理任何站点</li>
* </ul>
*/
public final class TestUsers {
private TestUsers() {
}
public static BoaUser superAdmin() {
BoaUser user = new BoaUser();
user.setId(1);
user.setAccount("admin");
user.setName("平台管理员");
user.setType(1);
return user;
}
public static BoaUser schoolUser(Integer schId) {
return schoolUser(schId, "school_editor", "校级编辑");
}
public static BoaUser schoolUser(Integer schId, String account, String name) {
BoaUser user = new BoaUser();
user.setId(2);
user.setAccount(account);
user.setName(name);
user.setRoleIds(Collections.singletonList(2));
BoaRoleInfo role = new BoaRoleInfo();
role.setRoleId(2);
role.setSchId(schId);
user.setCurrentRole(role);
return user;
}
/** 无角色、无 currentRole:listFor 应返回空,canManage 恒 false */
public static BoaUser plainUser() {
BoaUser user = new BoaUser();
user.setId(3);
user.setAccount("nobody");
user.setName("无角色用户");
return user;
}
}