feat(news): 支持新闻中心多站点隔离功能

新增站点编码配置,支持新闻分类与文章按站点隔离。主要变更包括:
- 数据库表增加 site_code 字段及索引
- 后台管理界面支持按站点筛选
- 前台接口支持通过查询参数或请求头指定站点
- 新增站点配置与解析逻辑
This commit is contained in:
2026-05-05 15:09:49 +08:00
parent d129d64ebd
commit 3daa0273a4
76 changed files with 592 additions and 114 deletions

View File

@@ -1,11 +1,14 @@
package com.wuhansaga.server;
import com.wuhansaga.server.config.PortalSiteProperties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@MapperScan("com.wuhansaga.server.mapper")
@EnableConfigurationProperties(PortalSiteProperties.class)
public class WuhanSagaApplication {
public static void main(String[] args) {

View File

@@ -0,0 +1,31 @@
package com.wuhansaga.server.config;
import com.wuhansaga.server.constant.SiteCodes;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Arrays;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "app.portal")
public class PortalSiteProperties {
/**
* 本实例默认站点(单域名单部署时一般不设查询参数,仅用此值)
*/
private String siteCode = SiteCodes.WUHAN_SAGA;
/**
* 逗号分隔允许列表;请求中的 siteCode 参数或 X-Portal-Site 头必须命中此值之一
*/
private String allowedSiteCodes = SiteCodes.WUHAN_SAGA + "," + SiteCodes.SAGA_SECONDARY;
public List<String> allowedCodesList() {
return Arrays.stream(allowedSiteCodes.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(s -> s.toLowerCase())
.toList();
}
}

View File

@@ -0,0 +1,54 @@
package com.wuhansaga.server.config;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Locale;
/**
* 解析前台站点:优先级为 查询参数 siteCode &gt; 请求头 X-Portal-Site &gt; 配置文件 app.portal.site-code。
* 非法编码回退到默认站点并打 WARN避免扫库枚举。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PortalSiteResolver {
private static final String HEADER_PORTAL_SITE = "X-Portal-Site";
private final PortalSiteProperties portalSiteProperties;
public String resolve(HttpServletRequest request) {
List<String> allowed = portalSiteProperties.allowedCodesList();
String candidate = firstNonBlank(
request.getParameter("siteCode"),
request.getHeader(HEADER_PORTAL_SITE)
);
if (StringUtils.hasText(candidate)) {
String normalized = candidate.trim().toLowerCase(Locale.ROOT);
if (allowed.contains(normalized)) {
return normalized;
}
log.warn("[portal] 无效站点编码 [{}],将使用默认 [{}]", candidate, portalSiteProperties.getSiteCode());
}
String fallback = portalSiteProperties.getSiteCode().trim().toLowerCase(Locale.ROOT);
if (!allowed.contains(fallback)) {
log.warn("[portal] 默认站点 [{}] 不在 app.portal.allowed-site-codes 中,仍使用该值查询", fallback);
}
return fallback;
}
private static String firstNonBlank(String a, String b) {
if (StringUtils.hasText(a)) {
return a;
}
if (StringUtils.hasText(b)) {
return b;
}
return null;
}
}

View File

@@ -0,0 +1,15 @@
package com.wuhansaga.server.constant;
/**
* 站点编码常量:与库中 f_news.site_code / f_news_category.site_code 一致,扩展新站时在此与配置中同步增加。
*/
public final class SiteCodes {
private SiteCodes() {}
/** 武汉萨格主站 */
public static final String WUHAN_SAGA = "wuhansaga";
/** 第二站点(可按实际品牌改名,并同步改 application.yml 与库内数据) */
public static final String SAGA_SECONDARY = "saga-secondary";
}

View File

@@ -21,11 +21,12 @@ public class AdminNewsController {
private final NewsService newsService;
private final NewsCategoryService newsCategoryService;
@Operation(summary = "新闻列表")
@Operation(summary = "新闻列表(可选 siteCode 过滤站点,不传则全部站点)")
@GetMapping
public R<List<News>> list(@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) Integer isFeatured) {
return R.ok(newsService.listPublished(categoryId, isFeatured));
@RequestParam(required = false) Integer isFeatured,
@RequestParam(required = false) String siteCode) {
return R.ok(newsService.listForAdmin(categoryId, isFeatured, null, siteCode));
}
@Operation(summary = "新闻详情")
@@ -55,10 +56,10 @@ public class AdminNewsController {
return R.ok();
}
@Operation(summary = "新闻分类列表")
@Operation(summary = "新闻分类列表(可选 siteCode 过滤)")
@GetMapping("/category")
public R<List<NewsCategory>> listCategory() {
return R.ok(newsCategoryService.listAll());
public R<List<NewsCategory>> listCategory(@RequestParam(required = false) String siteCode) {
return R.ok(newsCategoryService.listAll(siteCode));
}
@Operation(summary = "新增新闻分类")

View File

@@ -1,12 +1,14 @@
package com.wuhansaga.server.controller.portal;
import com.wuhansaga.server.common.R;
import com.wuhansaga.server.config.PortalSiteResolver;
import com.wuhansaga.server.entity.News;
import com.wuhansaga.server.entity.NewsCategory;
import com.wuhansaga.server.service.NewsCategoryService;
import com.wuhansaga.server.service.NewsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -20,23 +22,28 @@ public class PortalNewsController {
private final NewsService newsService;
private final NewsCategoryService newsCategoryService;
private final PortalSiteResolver portalSiteResolver;
@Operation(summary = "新闻分类列表")
@Operation(summary = "新闻分类列表(按站点隔离)")
@GetMapping("/category")
public R<List<NewsCategory>> listCategory() {
return R.ok(newsCategoryService.listPublished());
public R<List<NewsCategory>> listCategory(HttpServletRequest request) {
String siteCode = portalSiteResolver.resolve(request);
return R.ok(newsCategoryService.listPublished(siteCode));
}
@Operation(summary = "新闻列表")
@Operation(summary = "新闻列表(按站点隔离;可选 ?siteCode=、请求头 X-Portal-Site")
@GetMapping
public R<List<News>> list(@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) Integer isFeatured) {
return R.ok(newsService.listPublished(categoryId, isFeatured));
public R<List<News>> list(HttpServletRequest request,
@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) Integer isFeatured) {
String siteCode = portalSiteResolver.resolve(request);
return R.ok(newsService.listPublished(categoryId, isFeatured, siteCode));
}
@Operation(summary = "新闻详情")
@Operation(summary = "新闻详情(仅返回当前站点已发布稿件)")
@GetMapping("/{id}")
public R<News> getById(@PathVariable Long id) {
return R.ok(newsService.getPublishedById(id));
public R<News> getById(HttpServletRequest request, @PathVariable Long id) {
String siteCode = portalSiteResolver.resolve(request);
return R.ok(newsService.getPublishedById(id, siteCode));
}
}

View File

@@ -9,6 +9,8 @@ public class News {
private Long newsId;
private Long categoryId;
/** 站点编码:与新闻分类 siteCode 一致 */
private String siteCode;
private String titleZh;
private String titleEn;
private String excerptZh;

View File

@@ -8,6 +8,8 @@ import java.time.LocalDateTime;
public class NewsCategory {
private Long newsCategoryId;
/** 站点编码:同站新闻、分类共用一套取值域 */
private String siteCode;
private String nameZh;
private String nameEn;
private Integer sortOrder;

View File

@@ -2,13 +2,14 @@ package com.wuhansaga.server.mapper;
import com.wuhansaga.server.entity.NewsCategory;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface NewsCategoryMapper {
List<NewsCategory> selectAll();
List<NewsCategory> selectAll(@Param("siteCode") String siteCode);
NewsCategory selectById(Long id);
@@ -18,5 +19,5 @@ public interface NewsCategoryMapper {
int deleteById(Long id);
List<NewsCategory> selectPublished();
List<NewsCategory> selectPublished(@Param("siteCode") String siteCode);
}

View File

@@ -11,11 +11,15 @@ public interface NewsMapper {
List<News> selectList(@Param("categoryId") Long categoryId,
@Param("isFeatured") Integer isFeatured,
@Param("keyword") String keyword);
@Param("keyword") String keyword,
@Param("isPublished") Integer isPublished,
@Param("siteCode") String siteCode);
long selectCount(@Param("categoryId") Long categoryId,
@Param("isFeatured") Integer isFeatured,
@Param("keyword") String keyword);
@Param("keyword") String keyword,
@Param("isPublished") Integer isPublished,
@Param("siteCode") String siteCode);
News selectById(Long id);
@@ -26,7 +30,10 @@ public interface NewsMapper {
int deleteById(Long id);
List<News> selectPublished(@Param("categoryId") Long categoryId,
@Param("isFeatured") Integer isFeatured);
@Param("isFeatured") Integer isFeatured,
@Param("siteCode") String siteCode);
News selectPublishedById(@Param("id") Long id, @Param("siteCode") String siteCode);
int incrementViewCount(Long id);
}

View File

@@ -1,9 +1,11 @@
package com.wuhansaga.server.service;
import com.wuhansaga.server.constant.SiteCodes;
import com.wuhansaga.server.entity.NewsCategory;
import com.wuhansaga.server.mapper.NewsCategoryMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
@@ -13,12 +15,13 @@ public class NewsCategoryService {
private final NewsCategoryMapper newsCategoryMapper;
public List<NewsCategory> listAll() {
return newsCategoryMapper.selectAll();
/** siteCode 为空时不过滤站点(后台全量) */
public List<NewsCategory> listAll(String siteCode) {
return newsCategoryMapper.selectAll(siteCode);
}
public List<NewsCategory> listPublished() {
return newsCategoryMapper.selectPublished();
public List<NewsCategory> listPublished(String siteCode) {
return newsCategoryMapper.selectPublished(siteCode);
}
public NewsCategory getById(Long id) {
@@ -26,11 +29,18 @@ public class NewsCategoryService {
}
public void add(NewsCategory entity) {
if (!StringUtils.hasText(entity.getSiteCode())) {
entity.setSiteCode(SiteCodes.WUHAN_SAGA);
}
entity.setSiteCode(entity.getSiteCode().trim().toLowerCase());
entity.setIsPublished(1);
newsCategoryMapper.insert(entity);
}
public void update(NewsCategory entity) {
if (StringUtils.hasText(entity.getSiteCode())) {
entity.setSiteCode(entity.getSiteCode().trim().toLowerCase());
}
newsCategoryMapper.update(entity);
}

View File

@@ -2,10 +2,14 @@ package com.wuhansaga.server.service;
import com.wuhansaga.server.common.PageQuery;
import com.wuhansaga.server.common.PageResult;
import com.wuhansaga.server.constant.SiteCodes;
import com.wuhansaga.server.entity.News;
import com.wuhansaga.server.entity.NewsCategory;
import com.wuhansaga.server.mapper.NewsCategoryMapper;
import com.wuhansaga.server.mapper.NewsMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
@@ -14,40 +18,75 @@ import java.util.List;
public class NewsService {
private final NewsMapper newsMapper;
private final NewsCategoryMapper newsCategoryMapper;
public PageResult<News> page(Long categoryId, Integer isFeatured, String keyword, PageQuery query) {
List<News> records = newsMapper.selectList(categoryId, isFeatured, keyword);
List<News> records = newsMapper.selectList(categoryId, isFeatured, keyword, null, null);
return new PageResult<>(records, records.size(), query.getPage(), query.getSize());
}
public List<News> listPublished(Long categoryId, Integer isFeatured) {
return newsMapper.selectPublished(categoryId, isFeatured);
public List<News> listPublished(Long categoryId, Integer isFeatured, String siteCode) {
return newsMapper.selectPublished(categoryId, isFeatured, siteCode);
}
/** 后台列表siteCode 为空则跨站点查询 */
public List<News> listForAdmin(Long categoryId, Integer isFeatured, String keyword, String siteCode) {
return newsMapper.selectList(categoryId, isFeatured, keyword, null, siteCode);
}
public News getById(Long id) {
return newsMapper.selectById(id);
}
public News getPublishedById(Long id) {
News news = newsMapper.selectById(id);
if (news != null && news.getIsPublished() == 1) {
public News getPublishedById(Long id, String siteCode) {
News news = newsMapper.selectPublishedById(id, siteCode);
if (news != null) {
newsMapper.incrementViewCount(id);
return news;
}
return null;
return news;
}
public void add(News entity) {
if (!StringUtils.hasText(entity.getSiteCode())) {
entity.setSiteCode(SiteCodes.WUHAN_SAGA);
}
entity.setSiteCode(entity.getSiteCode().trim().toLowerCase());
ensureNewsCategorySite(entity);
entity.setIsPublished(1);
entity.setViewCount(0);
newsMapper.insert(entity);
}
public void update(News entity) {
if (StringUtils.hasText(entity.getSiteCode())) {
entity.setSiteCode(entity.getSiteCode().trim().toLowerCase());
}
News existing = entity.getNewsId() != null ? newsMapper.selectById(entity.getNewsId()) : null;
Long categoryId = entity.getCategoryId() != null ? entity.getCategoryId() : (existing != null ? existing.getCategoryId() : null);
String siteCode = StringUtils.hasText(entity.getSiteCode())
? entity.getSiteCode()
: (existing != null ? existing.getSiteCode() : null);
News probe = new News();
probe.setCategoryId(categoryId);
probe.setSiteCode(siteCode);
ensureNewsCategorySite(probe);
newsMapper.update(entity);
}
public void delete(Long id) {
newsMapper.deleteById(id);
}
private void ensureNewsCategorySite(News entity) {
if (!StringUtils.hasText(entity.getSiteCode()) || entity.getCategoryId() == null) {
return;
}
NewsCategory cat = newsCategoryMapper.selectById(entity.getCategoryId());
if (cat == null) {
throw new IllegalArgumentException("新闻分类不存在");
}
if (!entity.getSiteCode().equals(cat.getSiteCode())) {
throw new IllegalArgumentException("新闻站点与分类站点不一致");
}
}
}

View File

@@ -60,3 +60,9 @@ upload:
path: uploads/
allowed-types: image/jpeg,image/png,image/gif,image/webp,image/svg+xml,video/mp4,video/webm
max-size: 52428800
# 新闻中心多站点:单部署实例默认站点;扩展编码时改 allowed-site-codes 与库内数据
app:
portal:
site-code: wuhansaga
allowed-site-codes: wuhansaga,saga-secondary

View File

@@ -106,30 +106,31 @@ ON DUPLICATE KEY UPDATE
-- ================================================================
-- 新闻分类
-- ================================================================
INSERT INTO f_news_category (name_zh, name_en, sort_order) VALUES
('公司新闻', 'Company News', 10),
('行业动态', 'Industry News', 20),
('技术文章', 'Technical Articles', 30)
INSERT INTO f_news_category (site_code, name_zh, name_en, sort_order) VALUES
('wuhansaga', '公司新闻', 'Company News', 10),
('wuhansaga', '行业动态', 'Industry News', 20),
('wuhansaga', '技术文章', 'Technical Articles', 30)
ON DUPLICATE KEY UPDATE
site_code = VALUES(site_code),
name_zh = VALUES(name_zh), name_en = VALUES(name_en), sort_order = VALUES(sort_order);
-- ================================================================
-- 新闻文章(示例;生产环境可替换为正式稿件)
-- ================================================================
INSERT INTO f_news (category_id, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en, is_featured, is_published, sort_order) VALUES
(1, '武汉萨格官网更新上线', 'Wuhan Saga Website Refresh',
INSERT INTO f_news (category_id, site_code, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en, is_featured, is_published, sort_order) VALUES
(1, 'wuhansaga', '武汉萨格官网更新上线', 'Wuhan Saga Website Refresh',
'集中展示连续板带处理产线、单体设备及工程案例。',
'Highlighting strip processing lines, equipment and engineering cases.',
'<p>武汉萨格工程技术有限公司官网已升级,客户可通过产品中心了解热镀锌、退火、酸洗及感应加热等成套装备与单体设备,并查阅成功案例与新闻资讯。</p>',
'<p>Wuhan Saga Engineering has refreshed its website for strip galvanizing, annealing, pickling, induction heating solutions, case studies and news.</p>',
1, 1, 10),
(2, '板带处理装备市场观察', 'Strip Processing Equipment Market Notes',
(2, 'wuhansaga', '板带处理装备市场观察', 'Strip Processing Equipment Market Notes',
'节能与智能化连续处理机组需求持续增长。',
'Growing demand for energy-efficient intelligent continuous lines.',
'<p>国内外钢铁及加工企业持续投资连续镀锌、退火与酸洗机组。武汉萨格团队在设计与改造项目中注重节能环保与自动化控制。</p>',
'<p>Steel and processing firms continue to invest in continuous galvanizing, annealing and pickling lines with emphasis on energy saving and automation.</p>',
0, 1, 20),
(3, '感应加热在涂层干燥中的应用', 'Induction Heating in Coating Drying',
(3, 'wuhansaga', '感应加热在涂层干燥中的应用', 'Induction Heating in Coating Drying',
'非接触加热可提高涂层烘干效率并降低能耗。',
'Non-contact heating can improve drying efficiency and save energy.',
'<p>感应加热适用于涂层干燥等工艺环节,可与产线控制深度集成,实现分区温控与节能运行。</p>',

View File

@@ -7,11 +7,15 @@
</resultMap>
<select id="selectAll" resultMap="newsCategoryResult">
SELECT * FROM f_news_category WHERE del_flag = 0 ORDER BY sort_order ASC
SELECT * FROM f_news_category WHERE del_flag = 0
<if test="siteCode != null and siteCode != ''">AND site_code = #{siteCode}</if>
ORDER BY sort_order ASC
</select>
<select id="selectPublished" resultMap="newsCategoryResult">
SELECT * FROM f_news_category WHERE del_flag = 0 AND is_published = 1 ORDER BY sort_order ASC
SELECT * FROM f_news_category WHERE del_flag = 0 AND is_published = 1
<if test="siteCode != null and siteCode != ''">AND site_code = #{siteCode}</if>
ORDER BY sort_order ASC
</select>
<select id="selectById" resultMap="newsCategoryResult">
@@ -19,13 +23,14 @@
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="newsCategoryId" keyColumn="category_id">
INSERT INTO f_news_category (name_zh, name_en, sort_order, is_published, create_by, remark)
VALUES (#{nameZh}, #{nameEn}, #{sortOrder}, #{isPublished}, #{createBy}, #{remark})
INSERT INTO f_news_category (site_code, name_zh, name_en, sort_order, is_published, create_by, remark)
VALUES (#{siteCode}, #{nameZh}, #{nameEn}, #{sortOrder}, #{isPublished}, #{createBy}, #{remark})
</insert>
<update id="update" parameterType="com.wuhansaga.server.entity.NewsCategory">
UPDATE f_news_category
<set>
<if test="siteCode != null">site_code = #{siteCode},</if>
<if test="nameZh != null">name_zh = #{nameZh},</if>
<if test="nameEn != null">name_en = #{nameEn},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if>

View File

@@ -5,6 +5,7 @@
<resultMap id="newsResult" type="com.wuhansaga.server.entity.News">
<id property="newsId" column="news_id"/>
<result property="categoryId" column="category_id"/>
<result property="siteCode" column="site_code"/>
<result property="titleZh" column="title_zh"/>
<result property="titleEn" column="title_en"/>
<result property="excerptZh" column="excerpt_zh"/>
@@ -12,11 +13,21 @@
<result property="contentZh" column="content_zh"/>
<result property="contentEn" column="content_en"/>
<result property="coverImage" column="cover_image"/>
<result property="metaTitleZh" column="meta_title_zh"/>
<result property="metaTitleEn" column="meta_title_en"/>
<result property="metaDescriptionZh" column="meta_description_zh"/>
<result property="metaDescriptionEn" column="meta_description_en"/>
<result property="metaKeywords" column="meta_keywords"/>
<result property="isFeatured" column="is_featured"/>
<result property="isPublished" column="is_published"/>
<result property="viewCount" column="view_count"/>
<result property="sortOrder" column="sort_order"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<result property="delFlag" column="del_flag"/>
<result property="categoryNameZh" column="category_name_zh"/>
<result property="categoryNameEn" column="category_name_en"/>
</resultMap>
@@ -26,8 +37,10 @@
FROM f_news n
LEFT JOIN f_news_category nc ON n.category_id = nc.category_id
WHERE n.del_flag = 0
<if test="siteCode != null and siteCode != ''">AND n.site_code = #{siteCode}</if>
<if test="categoryId != null">AND n.category_id = #{categoryId}</if>
<if test="isFeatured != null">AND n.is_featured = #{isFeatured}</if>
<if test="isPublished != null">AND n.is_published = #{isPublished}</if>
<if test="keyword != null and keyword != ''">
AND (n.title_zh LIKE CONCAT('%',#{keyword},'%') OR n.title_en LIKE CONCAT('%',#{keyword},'%'))
</if>
@@ -35,11 +48,14 @@
</select>
<select id="selectCount" resultType="long">
SELECT COUNT(*) FROM f_news WHERE del_flag = 0
<if test="categoryId != null">AND category_id = #{categoryId}</if>
<if test="isFeatured != null">AND is_featured = #{isFeatured}</if>
SELECT COUNT(*) FROM f_news n
WHERE n.del_flag = 0
<if test="siteCode != null and siteCode != ''">AND n.site_code = #{siteCode}</if>
<if test="categoryId != null">AND n.category_id = #{categoryId}</if>
<if test="isFeatured != null">AND n.is_featured = #{isFeatured}</if>
<if test="isPublished != null">AND n.is_published = #{isPublished}</if>
<if test="keyword != null and keyword != ''">
AND (title_zh LIKE CONCAT('%',#{keyword},'%') OR title_en LIKE CONCAT('%',#{keyword},'%'))
AND (n.title_zh LIKE CONCAT('%',#{keyword},'%') OR n.title_en LIKE CONCAT('%',#{keyword},'%'))
</if>
</select>
@@ -48,6 +64,7 @@
FROM f_news n
LEFT JOIN f_news_category nc ON n.category_id = nc.category_id
WHERE n.del_flag = 0 AND n.is_published = 1
AND n.site_code = #{siteCode}
<if test="categoryId != null">AND n.category_id = #{categoryId}</if>
<if test="isFeatured != null">AND n.is_featured = #{isFeatured}</if>
ORDER BY n.sort_order ASC, n.create_time DESC
@@ -60,11 +77,19 @@
WHERE n.news_id = #{id} AND n.del_flag = 0
</select>
<select id="selectPublishedById" resultMap="newsResult">
SELECT n.*, nc.name_zh AS category_name_zh, nc.name_en AS category_name_en
FROM f_news n
LEFT JOIN f_news_category nc ON n.category_id = nc.category_id
WHERE n.news_id = #{id} AND n.del_flag = 0 AND n.is_published = 1
AND n.site_code = #{siteCode}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="newsId">
INSERT INTO f_news (category_id, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en,
INSERT INTO f_news (category_id, site_code, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en,
cover_image, meta_title_zh, meta_title_en, meta_description_zh, meta_description_en,
meta_keywords, is_featured, is_published, sort_order, create_by, remark)
VALUES (#{categoryId}, #{titleZh}, #{titleEn}, #{excerptZh}, #{excerptEn}, #{contentZh}, #{contentEn},
VALUES (#{categoryId}, #{siteCode}, #{titleZh}, #{titleEn}, #{excerptZh}, #{excerptEn}, #{contentZh}, #{contentEn},
#{coverImage}, #{metaTitleZh}, #{metaTitleEn}, #{metaDescriptionZh}, #{metaDescriptionEn},
#{metaKeywords}, #{isFeatured}, #{isPublished}, #{sortOrder}, #{createBy}, #{remark})
</insert>
@@ -73,6 +98,7 @@
UPDATE f_news
<set>
<if test="categoryId != null">category_id = #{categoryId},</if>
<if test="siteCode != null">site_code = #{siteCode},</if>
<if test="titleZh != null">title_zh = #{titleZh},</if>
<if test="titleEn != null">title_en = #{titleEn},</if>
<if test="excerptZh != null">excerpt_zh = #{excerptZh},</if>
@@ -80,6 +106,11 @@
<if test="contentZh != null">content_zh = #{contentZh},</if>
<if test="contentEn != null">content_en = #{contentEn},</if>
<if test="coverImage != null">cover_image = #{coverImage},</if>
<if test="metaTitleZh != null">meta_title_zh = #{metaTitleZh},</if>
<if test="metaTitleEn != null">meta_title_en = #{metaTitleEn},</if>
<if test="metaDescriptionZh != null">meta_description_zh = #{metaDescriptionZh},</if>
<if test="metaDescriptionEn != null">meta_description_en = #{metaDescriptionEn},</if>
<if test="metaKeywords != null">meta_keywords = #{metaKeywords},</if>
<if test="isFeatured != null">is_featured = #{isFeatured},</if>
<if test="isPublished != null">is_published = #{isPublished},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if>

View File

@@ -380,6 +380,7 @@ CREATE TABLE IF NOT EXISTS f_case_study (
-- ================================================================
CREATE TABLE IF NOT EXISTS f_news_category (
category_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
site_code VARCHAR(32) NOT NULL DEFAULT 'wuhansaga' COMMENT '站点编码:小写英文短码,与 f_news.site_code 取值域一致',
name_zh VARCHAR(100) NOT NULL COMMENT '分类名称(中文)',
name_en VARCHAR(100) NOT NULL COMMENT '分类名称(英文)',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
@@ -390,7 +391,8 @@ CREATE TABLE IF NOT EXISTS f_news_category (
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
del_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0正常 1删除',
PRIMARY KEY (category_id)
PRIMARY KEY (category_id),
KEY idx_f_news_category_site (site_code, del_flag, is_published, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='新闻分类';
-- ================================================================
@@ -399,6 +401,7 @@ CREATE TABLE IF NOT EXISTS f_news_category (
CREATE TABLE IF NOT EXISTS f_news (
news_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
category_id BIGINT NOT NULL COMMENT '所属分类',
site_code VARCHAR(32) NOT NULL DEFAULT 'wuhansaga' COMMENT '站点编码:与分类 site_code 一致,用于多站点数据隔离',
title_zh VARCHAR(200) NOT NULL COMMENT '标题(中文)',
title_en VARCHAR(200) NOT NULL COMMENT '标题(英文)',
excerpt_zh TEXT COMMENT '摘要(中文)',
@@ -422,6 +425,8 @@ CREATE TABLE IF NOT EXISTS f_news (
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
del_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0正常 1删除',
PRIMARY KEY (news_id),
KEY idx_f_news_site_list (site_code, del_flag, is_published, category_id),
KEY idx_f_news_site_time (site_code, del_flag, is_published, create_time),
KEY idx_f_news_category (category_id),
KEY idx_f_news_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='新闻文章';

View File

@@ -60,3 +60,9 @@ upload:
path: uploads/
allowed-types: image/jpeg,image/png,image/gif,image/webp,image/svg+xml,video/mp4,video/webm
max-size: 52428800
# 新闻中心多站点:单部署实例默认站点;扩展编码时改 allowed-site-codes 与库内数据
app:
portal:
site-code: wuhansaga
allowed-site-codes: wuhansaga,saga-secondary

View File

@@ -60,26 +60,33 @@ INSERT INTO f_company_info (
-- ================================================================
-- 产品分类
-- ================================================================
INSERT INTO f_product_category (product_category_id, name_zh, name_en, parent_id, sort_order) VALUES
(1, '连续涂镀机组', 'Continuous Coating Lines', NULL, 10),
(2, '连续热处理机组', 'Continuous Heat Treatment Lines', NULL, 20),
(3, '表面处理机组', 'Surface Treatment Lines', NULL, 30),
(4, '后处理及辅机', 'Post-processing & Auxiliary', NULL, 40),
(5, '单体设备', 'Single Equipment', NULL, 50),
(6, '备品备件', 'Spare Parts', NULL, 60),
(11, '热镀锌/镀铝锌机组', 'Hot Dipped Galvanizing / Galvalume Line', 1, 11),
(12, '锌铝镁机组', 'Zinc Aluminum Magnesium (ZAM) Line', 1, 12),
(13, '彩涂机组', 'Color Coating Line (CCL)', 1, 13),
(21, '普碳钢连续退火线', 'Plain Carbon Steel Continuous Annealing Line', 2, 21),
(22, '冷轧退火镀锌两用机组', 'Annealing & Galvanizing Combo Line', 2, 22),
(23, '硅钢连续处理机组', 'Silicon Steel Processing Line', 2, 23),
(31, '碳钢酸洗机组', 'Carbon Steel Pickling Line', 3, 31),
(32, '不锈钢退火酸洗机组', 'Stainless Steel Annealing & Pickling Line', 3, 32),
(41, '拉矫/脱脂/重卷线', 'Tension Leveling, Degreasing, Re-coiling', 4, 41)
INSERT INTO f_product_category (product_category_id, name_zh, name_en, module_type, parent_id, sort_order) VALUES
(1, '连续涂镀机组', 'Continuous Coating Lines', 'product_line', NULL, 10),
(2, '连续热处理机组', 'Continuous Heat Treatment Lines', 'product_line', NULL, 20),
(3, '表面处理机组', 'Surface Treatment Lines', 'product_line', NULL, 30),
(4, '后处理及辅机', 'Post-processing & Auxiliary', 'product_line', NULL, 40),
(5, '单体设备', 'Single Equipment', 'equipment', NULL, 50),
(6, '备品备件', 'Spare Parts', 'spare_part', NULL, 60),
(11, '热镀锌/镀铝锌机组', 'Hot Dipped Galvanizing / Galvalume Line', 'product_line', 1, 11),
(12, '锌铝镁机组', 'Zinc Aluminum Magnesium (ZAM) Line', 'product_line', 1, 12),
(13, '彩涂机组', 'Color Coating Line (CCL)', 'product_line', 1, 13),
(21, '普碳钢连续退火线', 'Plain Carbon Steel Continuous Annealing Line', 'product_line', 2, 21),
(22, '冷轧退火镀锌两用机组', 'Annealing & Galvanizing Combo Line', 'product_line', 2, 22),
(23, '硅钢连续处理机组', 'Silicon Steel Processing Line', 'product_line', 2, 23),
(31, '碳钢酸洗机组', 'Carbon Steel Pickling Line', 'product_line', 3, 31),
(32, '不锈钢退火酸洗机组', 'Stainless Steel Annealing & Pickling Line', 'product_line', 3, 32),
(41, '拉矫/脱脂/重卷线', 'Tension Leveling, Degreasing, Re-coiling', 'product_line', 4, 41),
(200, '卷取设备', 'Reeling & Coiling Equipment', 'equipment', 5, 200),
(201, '炉类设备', 'Furnace Equipment', 'equipment', 5, 201),
(202, '涂布与焊接设备', 'Coating & Welding Equipment', 'equipment', 5, 202),
(203, '破鳞设备', 'Scale Breaking Equipment', 'equipment', 5, 203),
(204, '拉矫矫直设备', 'Leveling & Straightening Equipment', 'equipment', 5, 204),
(205, '剪切修边设备', 'Shearing & Trimming Equipment', 'equipment', 5, 205)
ON DUPLICATE KEY UPDATE
name_zh = VALUES(name_zh), name_en = VALUES(name_en), parent_id = VALUES(parent_id), sort_order = VALUES(sort_order);
name_zh = VALUES(name_zh), name_en = VALUES(name_en), module_type = VALUES(module_type),
parent_id = VALUES(parent_id), sort_order = VALUES(sort_order);
ALTER TABLE f_product_category AUTO_INCREMENT = 100;
ALTER TABLE f_product_category AUTO_INCREMENT = 210;
-- ================================================================
-- 案例分类
@@ -99,13 +106,37 @@ ON DUPLICATE KEY UPDATE
-- ================================================================
-- 新闻分类
-- ================================================================
INSERT INTO f_news_category (name_zh, name_en, sort_order) VALUES
('公司新闻', 'Company News', 10),
('行业动态', 'Industry News', 20),
('技术文章', 'Technical Articles', 30)
INSERT INTO f_news_category (site_code, name_zh, name_en, sort_order) VALUES
('wuhansaga', '公司新闻', 'Company News', 10),
('wuhansaga', '行业动态', 'Industry News', 20),
('wuhansaga', '技术文章', 'Technical Articles', 30)
ON DUPLICATE KEY UPDATE
site_code = VALUES(site_code),
name_zh = VALUES(name_zh), name_en = VALUES(name_en), sort_order = VALUES(sort_order);
-- ================================================================
-- 新闻文章(示例;生产环境可替换为正式稿件)
-- ================================================================
INSERT INTO f_news (category_id, site_code, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en, is_featured, is_published, sort_order) VALUES
(1, 'wuhansaga', '武汉萨格官网更新上线', 'Wuhan Saga Website Refresh',
'集中展示连续板带处理产线、单体设备及工程案例。',
'Highlighting strip processing lines, equipment and engineering cases.',
'<p>武汉萨格工程技术有限公司官网已升级,客户可通过产品中心了解热镀锌、退火、酸洗及感应加热等成套装备与单体设备,并查阅成功案例与新闻资讯。</p>',
'<p>Wuhan Saga Engineering has refreshed its website for strip galvanizing, annealing, pickling, induction heating solutions, case studies and news.</p>',
1, 1, 10),
(2, 'wuhansaga', '板带处理装备市场观察', 'Strip Processing Equipment Market Notes',
'节能与智能化连续处理机组需求持续增长。',
'Growing demand for energy-efficient intelligent continuous lines.',
'<p>国内外钢铁及加工企业持续投资连续镀锌、退火与酸洗机组。武汉萨格团队在设计与改造项目中注重节能环保与自动化控制。</p>',
'<p>Steel and processing firms continue to invest in continuous galvanizing, annealing and pickling lines with emphasis on energy saving and automation.</p>',
0, 1, 20),
(3, 'wuhansaga', '感应加热在涂层干燥中的应用', 'Induction Heating in Coating Drying',
'非接触加热可提高涂层烘干效率并降低能耗。',
'Non-contact heating can improve drying efficiency and save energy.',
'<p>感应加热适用于涂层干燥等工艺环节,可与产线控制深度集成,实现分区温控与节能运行。</p>',
'<p>Induction heating suits coating drying processes and integrates with line control for zoned temperature and energy saving.</p>',
0, 1, 30);
-- ================================================================
-- 关于我们
-- ================================================================
@@ -278,28 +309,69 @@ ON DUPLICATE KEY UPDATE
-- ================================================================
-- 单体设备
-- ================================================================
INSERT INTO f_single_equipment (category_id, name_zh, name_en, technical_highlights_zh, technical_highlights_en, sort_order) VALUES
(5, '感应加热系统', 'Induction Heating System', '焊缝退火、硅钢轧前加热、涂层干燥', 'Weld annealing, Si-steel preheat, coating drying', 10),
(5, '镀后冷却系统', 'Post-plating Cooling System', '喷气冷却、气雾冷却BLADE喷嘴', 'Jet and air-mist cooling (BLADE nozzles)', 20),
(5, '电磁驱渣器', 'Electromagnetic Slag Drive', '替代人工除渣,自动化锌锅', 'Automated zinc pot skimming', 30),
(5, '红外辐射炉', 'Infrared Radiation Furnace', '涂层干燥,非接触加热', 'Non-contact coating drying', 40),
(5, '氢气回收装置', 'Hydrogen Recovery Unit', '回收率>75%2-3年回本', 'Recovery rate >75%, typical payback 23 years', 50),
(5, '合金化炉', 'Alloy Coating Furnace', '直接火焰加热FLOX/MILDNOx降50-70%', 'FLOX/MILD firing, NOx reduction 50-70%', 60),
(5, '燃烧系统改造', 'Combustion System Revamp', '双燃料切换LPG/NG/COG', 'Dual-fuel switching (LPG/NG/COG)', 70)
ON DUPLICATE KEY UPDATE
name_zh = VALUES(name_zh), name_en = VALUES(name_en), technical_highlights_zh = VALUES(technical_highlights_zh),
INSERT INTO f_single_equipment (category_id, name_zh, name_en, technical_highlights_zh, technical_highlights_en, sort_order, is_published) VALUES
(5, '感应加热系统', 'Induction Heating System', '焊缝退火、硅钢轧前加热、涂层干燥', 'Weld annealing, Si-steel preheat, coating drying', 10, 1),
(5, '镀后冷却系统', 'Post-plating Cooling System', '喷气冷却、气雾冷却BLADE喷嘴', 'Jet and air-mist cooling (BLADE nozzles)', 20, 1),
(5, '电磁驱渣器', 'Electromagnetic Slag Drive', '替代人工除渣,自动化锌锅', 'Automated zinc pot skimming', 30, 1),
(5, '红外辐射炉', 'Infrared Radiation Furnace', '涂层干燥,非接触加热', 'Non-contact coating drying', 40, 1),
(5, '氢气回收装置', 'Hydrogen Recovery Unit', '回收率>75%2-3年回本', 'Recovery rate >75%, typical payback 23 years', 50, 1),
(5, '合金化炉', 'Alloy Coating Furnace', '直接火焰加热FLOX/MILDNOx降50-70%', 'FLOX/MILD firing, NOx reduction 50-70%', 60, 1),
(5, '燃烧系统改造', 'Combustion System Revamp', '双燃料切换LPG/NG/COG', 'Dual-fuel switching (LPG/NG/COG)', 70, 1),
(200, '开收卷机(机型一)', 'Payoff / Tension Reel (Type A)', '板材开卷、张力卷取与收卷', 'Strip payoff, tension reel and recoiling', 200, 1),
(200, '开收卷机(机型二)', 'Payoff / Tension Reel (Type B)', '重载卷取与张力控制', 'Heavy-duty recoiling with tension control', 210, 1),
(201, '立式炉', 'Vertical Furnace', '立式布置热处理炉', 'Vertically arranged heat-treatment furnace', 220, 1),
(201, '卧式炉', 'Horizontal Furnace', '卧式连续热处理炉', 'Horizontal continuous heat-treatment furnace', 230, 1),
(202, '焊机', 'Welder', '带钢闪光焊、搭接焊等', 'Flash welding, lap welding for strip joining', 240, 1),
(202, '立式涂机', 'Vertical Coating Machine', '立式辊涂、涂层施加', 'Vertical roll coater / film application', 250, 1),
(202, '卧式涂机', 'Horizontal Coating Machine', '水平涂覆与烘干前道工序', 'Horizontal coating application', 260, 1),
(203, '破鳞机(机型一)', 'Scale Breaker (Type I)', '热轧氧化铁皮破碎与疏松', 'Breaking and loosening hot-rolled scale', 270, 1),
(203, '破鳞机(机型二)', 'Scale Breaker (Type II)', '另一型式破鳞与延伸', 'Alternative scale breaking configuration', 280, 1),
(204, '拉矫机', 'Tension Leveler', '带钢拉伸弯曲矫直', 'Tension leveling / stretch leveling', 290, 1),
(204, '两弯两矫拉弯矫直机', 'Two-bend Two-straight Stretch-bend Leveler', '多辊拉弯矫直改善板形', 'Multi-roll stretch-bend leveling for strip shape', 300, 1),
(205, '圆盘剪', 'Side Trimmer', '两侧碎边裁剪宽度', 'Side trimming for strip width', 310, 1),
(205, '碎边剪', 'Edge Chop Shear', '碎边切断与收集', 'Chopping and handling of edge trim', 320, 1),
(205, '圆盘剪(双刀头)', 'Side Trimmer (Twin Knife Head)', '双刀头提高修边效率', 'Twin knife heads for trimming efficiency', 330, 1),
(205, '双刀头碎边圆盘剪', 'Twin-head Edge-chop Disc Shear', '碎边与圆盘剪组合结构', 'Combined edge chop and disc shear', 340, 1)
ON DUPLICATE KEY UPDATE
name_zh = VALUES(name_zh), name_en = VALUES(name_en), technical_highlights_zh = VALUES(technical_highlights_zh),
technical_highlights_en = VALUES(technical_highlights_en), sort_order = VALUES(sort_order);
-- ================================================================
-- 备品备件
-- 成套机组与单体设备关联(与 f_single_equipment 自增 ID 顺序一致,可后台调整)
-- ================================================================
INSERT INTO f_spare_part (category_id, name_zh, name_en, description_zh, description_en, sort_order) VALUES
(6, '辐射管W/I/U型', 'Radiant Tubes (W/I/U)', '工业炉用辐射管', 'Radiant tubes for industrial furnaces', 10),
(6, '换热器', 'Heat Exchangers', '翅片管换热器、RJC换热器等', 'Finned tube and RJC-type exchangers', 20),
(6, '炉辊与张力辊', 'Hearth & Tension Rolls', '炉辊、大炉辊、张力辊、转向辊等', 'Hearth rolls, tension rolls, deflector rolls', 30)
ON DUPLICATE KEY UPDATE
name_zh = VALUES(name_zh), name_en = VALUES(name_en), description_zh = VALUES(description_zh),
description_en = VALUES(description_en), sort_order = VALUES(sort_order);
INSERT INTO f_product_line_equipment (product_line_id, equipment_id, sort_order) VALUES
(1, 3, 10), (1, 6, 20), (1, 2, 30), (1, 5, 40), (1, 1, 50), (1, 7, 60),
(2, 2, 10), (2, 5, 20), (2, 6, 30), (2, 7, 40),
(3, 10, 10), (3, 11, 20), (3, 4, 30), (3, 17, 40),
(4, 10, 10), (4, 11, 20), (4, 8, 30), (4, 9, 40), (4, 2, 50), (4, 6, 60),
(5, 4, 10), (5, 1, 20), (5, 13, 30), (5, 14, 40),
(6, 1, 10), (6, 10, 20), (6, 11, 30), (6, 5, 40),
(7, 15, 10), (7, 16, 20), (7, 17, 30), (7, 19, 40),
(8, 10, 10), (8, 11, 20), (8, 12, 30),
(9, 17, 10), (9, 18, 20), (9, 19, 30), (9, 20, 40), (9, 8, 50)
ON DUPLICATE KEY UPDATE sort_order = VALUES(sort_order);
-- ================================================================
-- 备品备件(产品目录单品,与官网图册一致;封面请在后台上传 cover_image
-- ================================================================
INSERT INTO f_spare_part (category_id, name_zh, name_en, description_zh, description_en, sort_order, is_published) VALUES
(6, '翅片管换热器', 'Finned Tube Heat Exchanger', '备品备件', 'Spare parts', 10, 1),
(6, 'RJC 换热器', 'RJC Heat exchanger', '备品备件', 'Spare parts', 20, 1),
(6, 'W 型辐射管', 'W-radiant tube', '备品备件', 'Spare parts', 30, 1),
(6, 'I 型辐射管', 'I-type Radiant Tube', '备品备件', 'Spare parts', 40, 1),
(6, 'U 型辐射管', 'U-Radiant Tube', '备品备件', 'Spare parts', 50, 1),
(6, '滑块', 'Slide Block', '备品备件', 'Spare parts', 60, 1),
(6, '轴套衬套', 'Bush and Sleeve', '备品备件', 'Spare parts', 70, 1),
(6, '支臂', 'Support Arm', '备品备件', 'Spare parts', 80, 1),
(6, '炉辊', 'Hearth Roll', '备品备件', 'Spare parts', 90, 1),
(6, '大炉辊', 'Big Hearth Roll', '备品备件', 'Spare parts', 100, 1),
(6, '镀铬辊', 'Chromed Roll', '备品备件', 'Spare parts', 110, 1),
(6, '氯丁胶转向辊', 'Rubber Turning Roll', '备品备件', 'Spare parts', 120, 1),
(6, '聚氨酯辊', 'PU Roll', '备品备件', 'Spare parts', 130, 1),
(6, '汲料辊', 'Pick Up Roll', '备品备件', 'Spare parts', 140, 1),
(6, '托辊', 'Support Roll', '备品备件', 'Spare parts', 150, 1),
(6, '水淬辊', 'Water Quenching Roll', '备品备件', 'Spare parts', 160, 1),
(6, '张力辊', 'Bridle Roll', '备品备件', 'Spare parts', 170, 1);
-- ================================================================
-- 成功案例
@@ -321,4 +393,5 @@ ON DUPLICATE KEY UPDATE
ALTER TABLE f_company_info AUTO_INCREMENT = 2;
ALTER TABLE f_case_category AUTO_INCREMENT = 9;
ALTER TABLE f_news_category AUTO_INCREMENT = 4;
ALTER TABLE f_news AUTO_INCREMENT = 4;
ALTER TABLE f_media_library AUTO_INCREMENT = 500;

View File

@@ -7,11 +7,15 @@
</resultMap>
<select id="selectAll" resultMap="newsCategoryResult">
SELECT * FROM f_news_category WHERE del_flag = 0 ORDER BY sort_order ASC
SELECT * FROM f_news_category WHERE del_flag = 0
<if test="siteCode != null and siteCode != ''">AND site_code = #{siteCode}</if>
ORDER BY sort_order ASC
</select>
<select id="selectPublished" resultMap="newsCategoryResult">
SELECT * FROM f_news_category WHERE del_flag = 0 AND is_published = 1 ORDER BY sort_order ASC
SELECT * FROM f_news_category WHERE del_flag = 0 AND is_published = 1
<if test="siteCode != null and siteCode != ''">AND site_code = #{siteCode}</if>
ORDER BY sort_order ASC
</select>
<select id="selectById" resultMap="newsCategoryResult">
@@ -19,13 +23,14 @@
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="newsCategoryId" keyColumn="category_id">
INSERT INTO f_news_category (name_zh, name_en, sort_order, is_published, create_by, remark)
VALUES (#{nameZh}, #{nameEn}, #{sortOrder}, #{isPublished}, #{createBy}, #{remark})
INSERT INTO f_news_category (site_code, name_zh, name_en, sort_order, is_published, create_by, remark)
VALUES (#{siteCode}, #{nameZh}, #{nameEn}, #{sortOrder}, #{isPublished}, #{createBy}, #{remark})
</insert>
<update id="update" parameterType="com.wuhansaga.server.entity.NewsCategory">
UPDATE f_news_category
<set>
<if test="siteCode != null">site_code = #{siteCode},</if>
<if test="nameZh != null">name_zh = #{nameZh},</if>
<if test="nameEn != null">name_en = #{nameEn},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if>

View File

@@ -5,6 +5,7 @@
<resultMap id="newsResult" type="com.wuhansaga.server.entity.News">
<id property="newsId" column="news_id"/>
<result property="categoryId" column="category_id"/>
<result property="siteCode" column="site_code"/>
<result property="titleZh" column="title_zh"/>
<result property="titleEn" column="title_en"/>
<result property="excerptZh" column="excerpt_zh"/>
@@ -12,11 +13,21 @@
<result property="contentZh" column="content_zh"/>
<result property="contentEn" column="content_en"/>
<result property="coverImage" column="cover_image"/>
<result property="metaTitleZh" column="meta_title_zh"/>
<result property="metaTitleEn" column="meta_title_en"/>
<result property="metaDescriptionZh" column="meta_description_zh"/>
<result property="metaDescriptionEn" column="meta_description_en"/>
<result property="metaKeywords" column="meta_keywords"/>
<result property="isFeatured" column="is_featured"/>
<result property="isPublished" column="is_published"/>
<result property="viewCount" column="view_count"/>
<result property="sortOrder" column="sort_order"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<result property="delFlag" column="del_flag"/>
<result property="categoryNameZh" column="category_name_zh"/>
<result property="categoryNameEn" column="category_name_en"/>
</resultMap>
@@ -26,8 +37,10 @@
FROM f_news n
LEFT JOIN f_news_category nc ON n.category_id = nc.category_id
WHERE n.del_flag = 0
<if test="siteCode != null and siteCode != ''">AND n.site_code = #{siteCode}</if>
<if test="categoryId != null">AND n.category_id = #{categoryId}</if>
<if test="isFeatured != null">AND n.is_featured = #{isFeatured}</if>
<if test="isPublished != null">AND n.is_published = #{isPublished}</if>
<if test="keyword != null and keyword != ''">
AND (n.title_zh LIKE CONCAT('%',#{keyword},'%') OR n.title_en LIKE CONCAT('%',#{keyword},'%'))
</if>
@@ -35,11 +48,14 @@
</select>
<select id="selectCount" resultType="long">
SELECT COUNT(*) FROM f_news WHERE del_flag = 0
<if test="categoryId != null">AND category_id = #{categoryId}</if>
<if test="isFeatured != null">AND is_featured = #{isFeatured}</if>
SELECT COUNT(*) FROM f_news n
WHERE n.del_flag = 0
<if test="siteCode != null and siteCode != ''">AND n.site_code = #{siteCode}</if>
<if test="categoryId != null">AND n.category_id = #{categoryId}</if>
<if test="isFeatured != null">AND n.is_featured = #{isFeatured}</if>
<if test="isPublished != null">AND n.is_published = #{isPublished}</if>
<if test="keyword != null and keyword != ''">
AND (title_zh LIKE CONCAT('%',#{keyword},'%') OR title_en LIKE CONCAT('%',#{keyword},'%'))
AND (n.title_zh LIKE CONCAT('%',#{keyword},'%') OR n.title_en LIKE CONCAT('%',#{keyword},'%'))
</if>
</select>
@@ -48,6 +64,7 @@
FROM f_news n
LEFT JOIN f_news_category nc ON n.category_id = nc.category_id
WHERE n.del_flag = 0 AND n.is_published = 1
AND n.site_code = #{siteCode}
<if test="categoryId != null">AND n.category_id = #{categoryId}</if>
<if test="isFeatured != null">AND n.is_featured = #{isFeatured}</if>
ORDER BY n.sort_order ASC, n.create_time DESC
@@ -60,11 +77,19 @@
WHERE n.news_id = #{id} AND n.del_flag = 0
</select>
<select id="selectPublishedById" resultMap="newsResult">
SELECT n.*, nc.name_zh AS category_name_zh, nc.name_en AS category_name_en
FROM f_news n
LEFT JOIN f_news_category nc ON n.category_id = nc.category_id
WHERE n.news_id = #{id} AND n.del_flag = 0 AND n.is_published = 1
AND n.site_code = #{siteCode}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="newsId">
INSERT INTO f_news (category_id, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en,
INSERT INTO f_news (category_id, site_code, title_zh, title_en, excerpt_zh, excerpt_en, content_zh, content_en,
cover_image, meta_title_zh, meta_title_en, meta_description_zh, meta_description_en,
meta_keywords, is_featured, is_published, sort_order, create_by, remark)
VALUES (#{categoryId}, #{titleZh}, #{titleEn}, #{excerptZh}, #{excerptEn}, #{contentZh}, #{contentEn},
VALUES (#{categoryId}, #{siteCode}, #{titleZh}, #{titleEn}, #{excerptZh}, #{excerptEn}, #{contentZh}, #{contentEn},
#{coverImage}, #{metaTitleZh}, #{metaTitleEn}, #{metaDescriptionZh}, #{metaDescriptionEn},
#{metaKeywords}, #{isFeatured}, #{isPublished}, #{sortOrder}, #{createBy}, #{remark})
</insert>
@@ -73,6 +98,7 @@
UPDATE f_news
<set>
<if test="categoryId != null">category_id = #{categoryId},</if>
<if test="siteCode != null">site_code = #{siteCode},</if>
<if test="titleZh != null">title_zh = #{titleZh},</if>
<if test="titleEn != null">title_en = #{titleEn},</if>
<if test="excerptZh != null">excerpt_zh = #{excerptZh},</if>
@@ -80,6 +106,11 @@
<if test="contentZh != null">content_zh = #{contentZh},</if>
<if test="contentEn != null">content_en = #{contentEn},</if>
<if test="coverImage != null">cover_image = #{coverImage},</if>
<if test="metaTitleZh != null">meta_title_zh = #{metaTitleZh},</if>
<if test="metaTitleEn != null">meta_title_en = #{metaTitleEn},</if>
<if test="metaDescriptionZh != null">meta_description_zh = #{metaDescriptionZh},</if>
<if test="metaDescriptionEn != null">meta_description_en = #{metaDescriptionEn},</if>
<if test="metaKeywords != null">meta_keywords = #{metaKeywords},</if>
<if test="isFeatured != null">is_featured = #{isFeatured},</if>
<if test="isPublished != null">is_published = #{isPublished},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if>

View File

@@ -195,6 +195,7 @@ CREATE TABLE IF NOT EXISTS f_product_category (
product_category_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
name_zh VARCHAR(100) NOT NULL COMMENT '分类名称(中文)',
name_en VARCHAR(100) NOT NULL COMMENT '分类名称(英文)',
module_type VARCHAR(20) NOT NULL DEFAULT 'product_line' COMMENT '模块类型 product_line/equipment/spare_part',
parent_id BIGINT NULL COMMENT '父分类ID',
icon_class VARCHAR(100) COMMENT '图标类名',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
@@ -280,6 +281,19 @@ CREATE TABLE IF NOT EXISTS f_single_equipment (
KEY idx_f_single_equipment_category (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='单体设备';
-- ================================================================
-- 成套机组与单体设备关联表(多对多)
-- ================================================================
CREATE TABLE IF NOT EXISTS f_product_line_equipment (
product_line_equipment_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
product_line_id BIGINT NOT NULL COMMENT '成套机组ID',
equipment_id BIGINT NOT NULL COMMENT '单体设备IDf_single_equipment',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
PRIMARY KEY (product_line_equipment_id),
UNIQUE KEY uk_f_ple_line_equipment (product_line_id, equipment_id),
KEY idx_f_ple_equipment (equipment_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='成套机组关联单体设备';
-- ================================================================
-- 备品备件表
-- ================================================================
@@ -366,6 +380,7 @@ CREATE TABLE IF NOT EXISTS f_case_study (
-- ================================================================
CREATE TABLE IF NOT EXISTS f_news_category (
category_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
site_code VARCHAR(32) NOT NULL DEFAULT 'wuhansaga' COMMENT '站点编码:小写英文短码,与 f_news.site_code 取值域一致',
name_zh VARCHAR(100) NOT NULL COMMENT '分类名称(中文)',
name_en VARCHAR(100) NOT NULL COMMENT '分类名称(英文)',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
@@ -376,7 +391,8 @@ CREATE TABLE IF NOT EXISTS f_news_category (
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
del_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0正常 1删除',
PRIMARY KEY (category_id)
PRIMARY KEY (category_id),
KEY idx_f_news_category_site (site_code, del_flag, is_published, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='新闻分类';
-- ================================================================
@@ -385,6 +401,7 @@ CREATE TABLE IF NOT EXISTS f_news_category (
CREATE TABLE IF NOT EXISTS f_news (
news_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
category_id BIGINT NOT NULL COMMENT '所属分类',
site_code VARCHAR(32) NOT NULL DEFAULT 'wuhansaga' COMMENT '站点编码:与分类 site_code 一致,用于多站点数据隔离',
title_zh VARCHAR(200) NOT NULL COMMENT '标题(中文)',
title_en VARCHAR(200) NOT NULL COMMENT '标题(英文)',
excerpt_zh TEXT COMMENT '摘要(中文)',
@@ -408,6 +425,8 @@ CREATE TABLE IF NOT EXISTS f_news (
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
del_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标志0正常 1删除',
PRIMARY KEY (news_id),
KEY idx_f_news_site_list (site_code, del_flag, is_published, category_id),
KEY idx_f_news_site_time (site_code, del_flag, is_published, create_time),
KEY idx_f_news_category (category_id),
KEY idx_f_news_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='新闻文章';
@@ -534,6 +553,14 @@ ALTER TABLE f_single_equipment
FOREIGN KEY (category_id) REFERENCES f_product_category (product_category_id)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE f_product_line_equipment
ADD CONSTRAINT fk_f_ple_product_line
FOREIGN KEY (product_line_id) REFERENCES f_product_line (product_line_id)
ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT fk_f_ple_single_equipment
FOREIGN KEY (equipment_id) REFERENCES f_single_equipment (single_equipment_id)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE f_spare_part
ADD CONSTRAINT fk_f_spare_part_category
FOREIGN KEY (category_id) REFERENCES f_product_category (product_category_id)

View File

@@ -2,6 +2,7 @@ com\wuhansaga\server\entity\CaseStudy.class
com\wuhansaga\server\WuhanSagaApplication.class
com\wuhansaga\server\entity\CompanyInfo.class
com\wuhansaga\server\controller\portal\PortalAboutController.class
com\wuhansaga\server\constant\SiteCodes.class
com\wuhansaga\server\mapper\SingleEquipmentMapper.class
com\wuhansaga\server\mapper\ProductCategoryMapper.class
com\wuhansaga\server\service\WorkshopService.class
@@ -33,6 +34,7 @@ com\wuhansaga\server\common\GlobalExceptionHandler.class
com\wuhansaga\server\mapper\NewsCategoryMapper.class
com\wuhansaga\server\controller\admin\AdminMediaController.class
com\wuhansaga\server\mapper\ProductMediaMapper.class
com\wuhansaga\server\config\PortalSiteResolver.class
com\wuhansaga\server\service\CaseCategoryService.class
com\wuhansaga\server\service\ProductLineService.class
com\wuhansaga\server\service\SingleEquipmentService.class
@@ -65,6 +67,7 @@ com\wuhansaga\server\service\SparePartService.class
com\wuhansaga\server\mapper\CoreTechnologyMapper.class
com\wuhansaga\server\mapper\ProductLineMapper.class
com\wuhansaga\server\controller\admin\AdminNewsController.class
com\wuhansaga\server\config\PortalSiteProperties.class
com\wuhansaga\server\controller\admin\AdminSparePartController.class
com\wuhansaga\server\service\NewsService.class
com\wuhansaga\server\controller\admin\AdminCoreTechnologyController.class

View File

@@ -4,9 +4,12 @@ D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\common\PageResult.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\common\R.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\config\OpenApiConfig.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\config\PortalSiteProperties.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\config\PortalSiteResolver.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\config\RedisConfig.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\config\SaTokenConfig.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\config\WebMvcConfig.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\constant\SiteCodes.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\controller\admin\AdminAboutController.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\controller\admin\AdminAuthController.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\controller\admin\AdminBannerController.java
@@ -58,6 +61,7 @@ D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\NewsCategoryMapper.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\NewsMapper.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\ProductCategoryMapper.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\ProductLineEquipmentMapper.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\ProductLineMapper.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\ProductMediaMapper.java
D:\DeXun_workspace\projects\wuhan-saga-official-website\server\src\main\java\com\wuhansaga\server\mapper\SingleEquipmentMapper.java