Compare commits

5 Commits

Author SHA1 Message Date
gxwebsoft 09a8300064 feat(cms): 增加克隆来源租户UID字段与逻辑支持
- CmsWebsite实体新增cloneTenantId字段,作为克隆来源租户UID记录
- 修改version字段描述,增加体验版版本说明
- CmsWebsiteMapper新增cloneTenantId的XML映射支持
- CmsWebsiteServiceImpl改用cloneTenantId作为内容克隆依据,区分templateId用途
- 添加幂等SQL脚本,动态添加clone_tenant_id列以兼容不同MySQL版本
- 保证cloneTenantId记录建站时sys_user.template_id,便于后端按模板分类和内容复制
2026-08-13 18:19:32 +08:00
gxwebsoft e1a93c1496 feat(shop): 添加会员价字段
- 在ShopGoodsSku实体中新增buyingPrice字段
- 使用@Schema注解描述会员价含义
- 扩展商品SKU数据结构以支持会员价功能
2026-08-11 11:02:15 +08:00
gxwebsoft bc5b5bf8ff fix(cms): 修复空更新时生成非法SQL的问题
- 引入BeanUtil判断实体是否有除id和deleted外的非空字段
- 避免MyBatis-Plus在无可更新字段时生成缺SET子句的SQL
- 防止多租户插件jsqlparser因SQL异常而失败
- 空更新场景返回幂等成功结果
- 保持原有代码逻辑,确保业务修改正确执行
2026-08-11 07:42:32 +08:00
gxwebsoft babd3b5aa0 feat(cms): 支持注册时选择初始化示例数据功能
- 在 CmsWebsite 实体添加 initDemoData 字段,支持标记是否初始化示例数据
- 根据 initDemoData 字段区分三种站点初始化流程:生成示例内容、空白骨架、克隆模板内容
- 新增 initDemoData 选中时的标准示例数据填充逻辑,包含导航栏目、关于我们单页、产品、案例和新闻资讯
- 保留历史默认行为,未传 initDemoData 时按模板克隆栏目与文章
- 删除历史复制栏目与文章业务,改用条件分支实现灵活初始化
- 详细实现示例数据的栏目构建、内容填充及关联逻辑,提升新站点用户体验
2026-08-11 00:21:35 +08:00
gxwebsoft 975ec6758b feat(cms): 增加内部接口以激活站点状态
- 新增 /internal/activate POST接口,通过X-Internal-Key进行鉴权
- 实现根据请求体更新站点状态和运行标志
- 更新成功后清除对应站点缓存,保证状态及时同步
- 添加配置项 internal.key 用于内部接口密钥校验
- 处理鉴权失败和参数缺失的异常响应
- 采用日志记录缓存清理异常信息
2026-08-10 19:47:43 +08:00
7 changed files with 347 additions and 58 deletions
+17
View File
@@ -0,0 +1,17 @@
# 2026-08-11 工作记录
## 日志诊断:cms-api (websoft-modules) 报错
来源文件:/Users/gxwebsoft/Downloads/websoft-modules (1).log
### 主报错(反复出现,需修复)
- 现象:`CmsWebsiteController.update()` → `updateById(cmsWebsite)` 生成非法 SQL
`UPDATE cms_website WHERE website_id=? AND deleted=0`(SET 子句为空)
- 抛错链:MyBatis-Plus `TenantLineInnerInterceptor` 用 jsqlparser 解析该空 SET SQL → `ParseException: Encountered unexpected token: "WHERE"` → `MybatisPlusException: Failed to process`
- 根因:`application.yml` 未设置全局 `field-strategy`,MP 3.4.x 默认 `NOT_NULL`。当 PUT /cms/website 的 body 只有 websiteId(websiteCode 被置 null,其余字段全 null)时,`updateById` 没有任何非空字段可写 → SET 为空 → SQL 非法。
- 实体佐证:`CmsWebsite` 的 `@TableLogic deleted` 进 WHERE、`@TableId websiteId` 进 WHERE、`updateTime` 无 `@TableField(fill)` 自动填充 → 无字段兜底。
- 已修复(2026-08-11):在 `CmsWebsiteController.update()` 调用 `updateById` 前,用 `BeanUtil.beanToMap` 判断是否存在非 id/deleted 的非空字段;为空则直接 `success` 跳过 DB 更新。已新增 import `cn.hutool.core.bean.BeanUtil`。
### 次要报错(非紧急)
- 01:44:17 MQTT:client `hjm_car_1786362857263` keepAlive=20s 超时,`MQTT连接丢失`(GPS 回调)。Paho 通常会自动重连,疑为 broker 网络/keepalive 问题。
- 07:35:20 WARN Druid:`removeAbandoned is true, not use in production.` 连接池 abandoned 清理开启,Druid 不建议生产环境开启,属配置告警。
@@ -2,6 +2,7 @@ package com.gxwebsoft.cms.controller;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -22,6 +23,7 @@ import com.gxwebsoft.common.system.entity.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@@ -52,6 +54,9 @@ public class CmsWebsiteController extends BaseController {
@Resource
private CmsWebsiteSettingService cmsWebsiteSettingService;
@Value("${internal.key:websopy-internal-2025}")
private String internalKey;
private static final String SITE_INFO_KEY_PREFIX = "SiteInfo:";
private static final String MP_INFO_KEY_PREFIX = "MpInfo:";
private static final String SELECT_PAYMENT_KEY_PREFIX = "SelectPayment:";
@@ -147,6 +152,15 @@ public class CmsWebsiteController extends BaseController {
// 不允许通过此接口清空或修改 websiteCode(设为 null 则 MP 不更新该字段)
cmsWebsite.setWebsiteCode(null);
}
// 修复:当实体没有任何非空(可更新)字段时,MyBatis-Plus 默认 NOT_NULL 策略会生成
// 缺 SET 子句的非法 SQL(如 "UPDATE cms_website WHERE ..."),触发多租户插件 jsqlparser
// 解析异常。先判断是否存在 id / deleted 之外的非空字段,空更新视为幂等成功。
boolean hasUpdatable = BeanUtil.beanToMap(cmsWebsite, false, false).entrySet().stream()
.anyMatch(e -> !"websiteId".equals(e.getKey()) && !"deleted".equals(e.getKey())
&& e.getValue() != null);
if (!hasUpdatable) {
return success("修改成功");
}
if (cmsWebsiteService.updateById(cmsWebsite)) {
// 站点信息(含 domain / templateId 等)有 1 天 Redis 缓存,改完必须立刻失效,
// 否则公开站与后台链接拼接最长 1 天读到旧值(与 CmsNavigationController 写操作保持一致)。
@@ -679,5 +693,47 @@ public class CmsWebsiteController extends BaseController {
return success(cmsWebsiteService.pageReviews(param));
}
/**
* 内部接口:由 websopy 支付回调在服务端调用,将站点置为「运行中(1)」。
* 通过 X-Internal-Key 头做简单鉴权(仅限内网/可信服务调用,密钥与 websopy 端保持一致)。
*/
@PostMapping("/internal/activate")
public ApiResult<?> activateInternal(
@RequestHeader(value = "X-Internal-Key", required = false) String key,
@RequestBody Map<String, Object> body) {
if (key == null || !key.equals(internalKey)) {
return fail("非法请求");
}
Integer websiteId = body.get("websiteId") instanceof Number
? ((Number) body.get("websiteId")).intValue() : null;
if (websiteId == null) {
return fail("websiteId 不能为空");
}
Integer status = body.get("status") instanceof Number
? ((Number) body.get("status")).intValue() : 1;
Integer running = body.get("running") instanceof Number
? ((Number) body.get("running")).intValue() : 0;
CmsWebsite site = new CmsWebsite();
site.setWebsiteId(websiteId);
site.setStatus(status);
site.setRunning(running);
if (cmsWebsiteService.updateById(site)) {
// 失效站点缓存,保证后台/公开站尽快读到新状态
try {
CmsWebsite exist = cmsWebsiteService.getOne(new LambdaQueryWrapper<CmsWebsite>()
.eq(CmsWebsite::getWebsiteId, websiteId).last("limit 1"));
if (exist != null && ObjectUtil.isNotEmpty(exist.getTenantId())) {
redisUtil.delete(SITE_INFO_KEY_PREFIX.concat(exist.getTenantId().toString()));
redisUtil.delete(MP_INFO_KEY_PREFIX.concat(exist.getTenantId().toString()));
}
} catch (Exception e) {
log.warn("清除站点缓存失败: {}", e.getMessage());
}
return success("站点已激活");
}
return fail("激活失败");
}
}
@@ -89,7 +89,7 @@ public class CmsWebsite implements Serializable {
@Schema(description = "自定义API接口")
private String apiUrl;
@Schema(description = "应用版本 10免费版 20授权版 30永久授权")
@Schema(description = "应用版本 0体验版 10基础版 20授权版 30永久授权")
private Integer version;
@Schema(description = "服务到期时间")
@@ -117,6 +117,13 @@ public class CmsWebsite implements Serializable {
@TableField(exist = false)
private String templateName;
@Schema(description = "克隆来源租户UID(建站时据此复制参考租户内容;值为 sys_user.template_id,后端据此按模板分类)")
private Integer cloneTenantId;
@Schema(description = "是否初始化示例数据(仅 create 入参使用,不落库;true=生成标准示例栏目与内容,false=空白骨架,null=沿用模板克隆历史行为)")
@TableField(exist = false)
private Boolean initDemoData;
@Schema(description = "行业类型(父级)")
private String industryParent;
@@ -210,6 +210,9 @@
<if test="param.templateId != null">
template_id = #{param.templateId},
</if>
<if test="param.cloneTenantId != null">
clone_tenant_id = #{param.cloneTenantId},
</if>
<if test="param.companyId != null">
company_id = #{param.companyId},
</if>
@@ -19,7 +19,9 @@ import com.gxwebsoft.shop.vo.ShopVo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -69,6 +71,12 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
@Resource
private CmsArticleContentService cmsArticleContentService;
@Resource
private CmsProductService cmsProductService;
@Resource
private CmsCaseService cmsCaseService;
@Resource
private CmsPageService cmsPageService;
@Resource
private CmsWebsiteMapper cmsWebsiteMapper;
@Resource
private ProjectService projectService;
@@ -191,7 +199,9 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
website.setUserId(loginUser.getUserId());
website.setDeveloper(loginUser.getNickname());
website.setTenantId(loginUser.getTenantId());
website.setTemplateId(loginUser.getTemplateId());
// 克隆来源租户UID = sys_user.template_id(注册时选定的模板租户),写入独立字段 cloneTenantId,
// 供后端按模板分类;同时作为内容克隆来源。template_id 留给 /templates 模板选择器写入。
website.setCloneTenantId(loginUser.getTemplateId());
website.setCompanyId(loginUser.getCompanyId());
// 默认运行中:status(后台枚举)与 running(getSiteInfo 状态展示用)保持一致,
@@ -227,7 +237,8 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
// cmsWebsiteSettingService.save(setting);
// 将网站创建者的userId做为查询条件 10257(4716),10324(6978),10398(26564)
Integer websiteUserId = website.getTemplateId();
// 克隆来源 = cloneTenantId(=sys_user.template_id),据此复制参考租户的内容
Integer websiteUserId = website.getCloneTenantId();
// 只有当templateId存在时才执行复制操作
if (websiteUserId != null && websiteUserId > 0) {
@@ -279,61 +290,17 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
});
cmsLinkService.saveBatch(links);
// TODO 复制栏目和文章、文章内容
CmsNavigationParam cmsNavigationParam = new CmsNavigationParam();
cmsNavigationParam.setWebsiteUserId(websiteUserId);
cmsNavigationParam.setParentId(0);
final List<CmsNavigation> parents = cmsNavigationMapper.selectListAllRel(cmsNavigationParam);
parents.forEach(d -> {
Integer navigationId = d.getNavigationId();
// 复制顶级栏目
d.setTenantId(loginUser.getTenantId());
d.setUserId(loginUser.getUserId());
if (cmsNavigationService.save(d)) {
cmsNavigationService.saveAsync(d);
// 复制栏目文章
CmsArticleParam cmsArticleParam = new CmsArticleParam();
cmsArticleParam.setWebsiteUserId(websiteUserId);
cmsArticleParam.setCategoryId(navigationId);
final List<CmsArticle> articles = cmsArticleMapper.selectListAllRel(cmsArticleParam);
articles.forEach(a -> {
a.setCategoryId(d.getNavigationId());
a.setUserId(loginUser.getUserId());
a.setTenantId(loginUser.getTenantId());
if (cmsArticleService.save(a)) {
final CmsArticleContent content = new CmsArticleContent();
content.setArticleId(a.getArticleId());
content.setContent(a.getContent());
cmsArticleContentService.save(content);
}
});
// 复制子栏目
cmsNavigationParam.setParentId(navigationId);
final List<CmsNavigation> navigations = cmsNavigationMapper.selectListAllRel(cmsNavigationParam);
navigations.forEach(c -> {
cmsArticleParam.setCategoryId(c.getNavigationId());
c.setParentId(d.getNavigationId());
c.setTenantId(loginUser.getTenantId());
c.setUserId(loginUser.getUserId());
cmsNavigationService.save(c);
cmsNavigationService.saveAsync(c);
// 复制子栏目文章
final List<CmsArticle> articles2 = cmsArticleMapper.selectListAllRel(cmsArticleParam);
articles2.forEach(a2 -> {
a2.setCategoryId(c.getNavigationId());
a2.setParentId(c.getParentId());
a2.setUserId(loginUser.getUserId());
a2.setTenantId(loginUser.getTenantId());
if (cmsArticleService.save(a2)) {
final CmsArticleContent content = new CmsArticleContent();
content.setArticleId(a2.getArticleId());
content.setContent(a2.getContent());
cmsArticleContentService.save(content);
}
});
});
}
});
// 栏目与内容初始化:按 initDemoData 决定分支
if (Boolean.TRUE.equals(website.getInitDemoData())) {
// 注册流程勾选「填充示例内容」:生成标准示例栏目与内容(不克隆模板,避免千篇一律)
initDemoData(website, loginUser);
} else if (Boolean.FALSE.equals(website.getInitDemoData())) {
// 注册流程明确不勾选:仅保留基础配置克隆,跳过栏目与文章,站点保持空白骨架
log.info("initDemoData=false,跳过示例内容生成,站点保持空白骨架, tenantId={}", loginUser.getTenantId());
} else {
// 历史行为(后台手动建站等未传 initDemoData):克隆模板栏目与文章
cloneTemplateContent(website, loginUser, websiteUserId);
}
} else {
log.warn("没有有效的模板ID,跳过复制操作");
}
@@ -608,4 +575,218 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
}
return candidate;
}
// ===== 注册「填充示例内容」:标准示例数据初始化 =====
/** 产品示例数据:{ 产品名称, 简介 } */
private static final String[][] PRODUCT_DEMO = {
{"智能营销云平台", "一站式营销自动化工具,覆盖获客、培育、转化全链路,让增长可度量。"},
{"企业协同办公系统", "打通沟通、文档与流程,让团队协作更高效、信息不丢失。"},
{"数据可视化大屏", "多源数据接入,实时呈现业务全景,辅助经营决策。"},
{"客户关系管理 CRM", "沉淀客户资产,提升销售转化与复购,驱动业绩增长。"},
{"在线教育解决方案", "直播、录播、互动作业一体化的在线教学平台。"},
{"电商直播中台", "从商品到直播到交易,构建私域增长闭环。"}
};
/** 客户案例示例数据:{ 案例名称, 简介, 客户名称, 所属行业 } */
private static final String[][] CASE_DEMO = {
{"某制造业集团数字化转型", "以数字化中台打通生产与供应链,交付周期显著缩短。", "制造业集团", "制造业"},
{"连锁零售智慧门店升级", "门店数字化运营,会员复购率与坪效双提升。", "连锁零售企业", "零售"},
{"教育机构在线课堂改造", "线上线下融合教学,覆盖更多学员,扩大营收半径。", "教育科技公司", "教育"},
{"医疗机构患者服务中台", "统一预约、问诊与随访,患者满意度明显提升。", "三甲医院", "医疗"},
{"物流企业运输调度系统", "智能调度与轨迹监控,运力利用率大幅提高。", "物流企业", "物流"},
{"金融企业数据中台建设", "统一数据资产,风控与营销决策更加敏捷。", "城商银行", "金融"}
};
/** 新闻资讯示例数据:{ 标题, 摘要 }(标题支持 {company} 占位符,替换为注册企业名) */
private static final String[][] NEWS_DEMO = {
{"{company} 荣获年度数字化服务创新奖", "{company}凭借在企业官网与数字化服务领域的持续创新,荣获年度数字化服务创新奖。"},
{"企业官网建设的核心要素解析", "从品牌定位、信息架构到视觉体验,拆解一个高转化企业官网的关键要素。"},
{"如何选择适合自己的官网模板", "结合行业、预算与运营目标,教你快速匹配最合适的官网模板。"},
{"提升网站转化率的 5 个技巧", "落地页、CTA、信任背书、加载速度与内容策略,五招提升转化。"},
{"SEO 优化入门指南", "关键词、结构、内容与外链,新手也能上手的 SEO 基础框架。"},
{"品牌视觉设计与用户体验", "一致的视觉语言与顺畅的交互,是建立品牌信任的第一步。"}
};
/**
* 生成标准示例栏目与内容(注册流程勾选「填充示例内容」时调用)。
* 创建 5 个顶部导航栏目 + 关于我们单页 + 6 产品 + 6 案例 + 6 新闻。
* 不克隆模板内容,避免新站点千篇一律。
*/
private void initDemoData(CmsWebsite website, User loginUser) {
Integer tenantId = loginUser.getTenantId();
Integer userId = loginUser.getUserId();
final String companyName = StrUtil.isNotBlank(website.getWebsiteName()) ? website.getWebsiteName() : "贵公司";
// 1. 导航栏目:首页 / 关于我们 / 产品展示 / 客户案例 / 新闻资讯
CmsNavigation home = buildNav("首页", "page", "/", 1, 0, tenantId, userId);
CmsNavigation about = buildNav("关于我们", "page", "/about", 2, 1, tenantId, userId);
CmsNavigation productNav = buildNav("产品展示", "product", "/product", 3, 0, tenantId, userId);
CmsNavigation caseNav = buildNav("客户案例", "case", "/case", 4, 0, tenantId, userId);
CmsNavigation newsNav = buildNav("新闻资讯", "article", "/article", 5, 0, tenantId, userId);
for (CmsNavigation nav : Arrays.asList(home, about, productNav, caseNav, newsNav)) {
cmsNavigationService.save(nav);
}
// 2. 关于我们单页(path=about,status=1 已发布)
CmsPage aboutPage = new CmsPage();
aboutPage.setTitle("关于我们");
aboutPage.setPath("about");
aboutPage.setContent(buildAboutContent(companyName));
aboutPage.setDescription("了解" + companyName + "的发展历程、业务能力与核心价值观。");
aboutPage.setKeywords(companyName + ",关于我们,企业简介");
aboutPage.setStatus(1);
aboutPage.setTenantId(tenantId);
cmsPageService.save(aboutPage);
// 关联导航栏目(便于后台「单页管理」定位)
about.setPageId(aboutPage.getPageId());
cmsNavigationService.updateById(about);
// 3. 产品展示(6 条,categoryId = 产品栏目 navigationId,status=0 可见)
int pSort = 1;
for (String[] p : PRODUCT_DEMO) {
CmsProduct product = new CmsProduct();
product.setProductName(p[0]);
product.setSummary(p[1]);
product.setDescription(p[1]);
product.setCategoryId(productNav.getNavigationId());
product.setCategoryName(productNav.getTitle());
product.setPrice(BigDecimal.ZERO);
product.setOriginalPrice(BigDecimal.ZERO);
product.setUnit("套");
product.setStatus(0);
product.setSortNumber(pSort);
product.setTop(pSort <= 3 ? 1 : 0);
product.setTenantId(tenantId);
product.setUserId(userId);
cmsProductService.save(product);
pSort++;
}
// 4. 客户案例(6 个,categoryId = 案例栏目 navigationId,status=0 可见)
int cSort = 1;
for (String[] c : CASE_DEMO) {
CmsCase cmsCase = new CmsCase();
cmsCase.setCaseName(c[0]);
cmsCase.setSummary(c[1]);
cmsCase.setContent(c[1]);
cmsCase.setCustomer(c[2]);
cmsCase.setIndustry(c[3]);
cmsCase.setCategoryId(caseNav.getNavigationId());
cmsCase.setCategoryName(caseNav.getTitle());
cmsCase.setStatus(0);
cmsCase.setSortNumber(cSort);
cmsCase.setTenantId(tenantId);
cmsCaseService.save(cmsCase);
cSort++;
}
// 5. 新闻资讯(6 篇,categoryId = 资讯栏目 navigationId,status=0 已发布)
int aSort = 1;
for (String[] a : NEWS_DEMO) {
CmsArticle article = new CmsArticle();
article.setTitle(a[0].replace("{company}", companyName));
article.setModel("article");
article.setCategoryId(newsNav.getNavigationId());
article.setOverview(a[1].replace("{company}", companyName));
article.setStatus(0);
article.setSortNumber(aSort);
article.setUserId(userId);
article.setTenantId(tenantId);
article.setAuthor(companyName);
if (cmsArticleService.save(article)) {
CmsArticleContent content = new CmsArticleContent();
content.setArticleId(article.getArticleId());
content.setContent(a[1].replace("{company}", companyName));
content.setTenantId(tenantId);
cmsArticleContentService.save(content);
}
aSort++;
}
}
/**
* 克隆模板站点的栏目与文章(历史默认行为,未传 initDemoData 时走此分支,兼容后台手动建站等场景)。
*/
private void cloneTemplateContent(CmsWebsite website, User loginUser, Integer websiteUserId) {
CmsNavigationParam cmsNavigationParam = new CmsNavigationParam();
cmsNavigationParam.setWebsiteUserId(websiteUserId);
cmsNavigationParam.setParentId(0);
final List<CmsNavigation> parents = cmsNavigationMapper.selectListAllRel(cmsNavigationParam);
parents.forEach(d -> {
Integer navigationId = d.getNavigationId();
d.setTenantId(loginUser.getTenantId());
d.setUserId(loginUser.getUserId());
if (cmsNavigationService.save(d)) {
cmsNavigationService.saveAsync(d);
CmsArticleParam cmsArticleParam = new CmsArticleParam();
cmsArticleParam.setWebsiteUserId(websiteUserId);
cmsArticleParam.setCategoryId(navigationId);
final List<CmsArticle> articles = cmsArticleMapper.selectListAllRel(cmsArticleParam);
articles.forEach(a -> {
a.setCategoryId(d.getNavigationId());
a.setUserId(loginUser.getUserId());
a.setTenantId(loginUser.getTenantId());
if (cmsArticleService.save(a)) {
CmsArticleContent content = new CmsArticleContent();
content.setArticleId(a.getArticleId());
content.setContent(a.getContent());
cmsArticleContentService.save(content);
}
});
cmsNavigationParam.setParentId(navigationId);
final List<CmsNavigation> navigations = cmsNavigationMapper.selectListAllRel(cmsNavigationParam);
navigations.forEach(c -> {
cmsArticleParam.setCategoryId(c.getNavigationId());
c.setParentId(d.getNavigationId());
c.setTenantId(loginUser.getTenantId());
c.setUserId(loginUser.getUserId());
cmsNavigationService.save(c);
cmsNavigationService.saveAsync(c);
final List<CmsArticle> articles2 = cmsArticleMapper.selectListAllRel(cmsArticleParam);
articles2.forEach(a2 -> {
a2.setCategoryId(c.getNavigationId());
a2.setParentId(c.getParentId());
a2.setUserId(loginUser.getUserId());
a2.setTenantId(loginUser.getTenantId());
if (cmsArticleService.save(a2)) {
CmsArticleContent content = new CmsArticleContent();
content.setArticleId(a2.getArticleId());
content.setContent(a2.getContent());
cmsArticleContentService.save(content);
}
});
});
}
});
}
/** 构造一个顶部导航栏目 */
private CmsNavigation buildNav(String title, String model, String path, int sort, int type,
Integer tenantId, Integer userId) {
CmsNavigation nav = new CmsNavigation();
nav.setTitle(title);
nav.setModel(model);
nav.setPath(path);
nav.setType(type);
nav.setParentId(0);
nav.setTop(1);
nav.setBottom(0);
nav.setPosition(1);
nav.setHide(0);
nav.setStatus(0);
nav.setSortNumber(sort);
nav.setTenantId(tenantId);
nav.setUserId(userId);
return nav;
}
/** 生成关于我们单页正文(富文本 HTML) */
private String buildAboutContent(String companyName) {
return "<p>欢迎来到 <strong>" + companyName + "</strong>!我们是一家专注于为企业提供数字化解决方案的服务商。</p>"
+ "<p>自成立以来," + companyName + "始终以客户成功为中心,围绕官网建设、内容管理与营销增长等场景,"
+ "提供从咨询、设计到落地运营的一站式服务。</p>"
+ "<p>我们的团队由资深产品、设计与技术专家组成,已为众多行业客户交付稳定可靠的数字平台。"
+ "期待与您携手,共创更大价值。</p>";
}
}
@@ -0,0 +1,22 @@
-- cms_website 补 clone_tenant_id 列(幂等版,兼容 MySQL 5.7 / 8.0)
-- 该列用于记录"克隆来源租户UID(=sys_user.template_id)",建站时写入,
-- 供后端按模板分类站点,并作为内容克隆来源。
-- 注:生产 modules.cms_website 通常已存在该列(websopy 建站写入),
-- 本脚本仅作为安全网,缺列才补,可重复执行。
-- 通过存储过程判断存在性,已存在则跳过;
-- 需在支持 DELIMITER 的客户端执行(mysql CLI、Navicat、MySQL Workbench 等)。
DELIMITER $$
DROP PROCEDURE IF EXISTS `add_cms_website_clone_tenant_id`$$
CREATE PROCEDURE `add_cms_website_clone_tenant_id`()
BEGIN
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='modules' AND TABLE_NAME='cms_website' AND COLUMN_NAME='clone_tenant_id') THEN
ALTER TABLE `cms_website` ADD COLUMN `clone_tenant_id` INT NULL COMMENT '克隆来源租户UID(=sys_user.template_id,建站时写入,供后端按模板分类)' AFTER `template_id`;
END IF;
END$$
DELIMITER ;
CALL `add_cms_website_clone_tenant_id`();
DROP PROCEDURE IF EXISTS `add_cms_website_clone_tenant_id`;
@@ -45,6 +45,9 @@ public class ShopGoodsSku implements Serializable {
@Schema(description = "成本价")
private BigDecimal cost;
@Schema(description = "会员价")
private BigDecimal buyingPrice;
@Schema(description = "库存")
private Integer stock;