feat: Add news information management module with API and database support
- Introduced new API endpoints for querying and managing news information in the CMS. - Implemented data model for NewsInfo and NewsInfoQuery with validation. - Created NewsInfoMapper for database interactions and corresponding XML mappings. - Developed INewsInfoService interface and its implementation for business logic. - Added RichTextSanitizer utility for cleaning HTML content to prevent XSS attacks. - Created unit tests for RichTextSanitizer to ensure proper functionality. - Updated database schema with news_info table and necessary fields, including status and module. - Added migration scripts for setting up the news information module in the database. - Integrated jsoup library for HTML sanitization.
This commit is contained in:
207
docs/cms-news-info-api.md
Normal file
207
docs/cms-news-info-api.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# 新闻资讯 API
|
||||
|
||||
## 约定
|
||||
|
||||
- 基础地址:`/api`
|
||||
- CMS 接口需要登录,并按菜单权限校验。
|
||||
- 上下架状态:`0` 上架,`1` 下架。新增新闻默认下架。
|
||||
- `module` 保存业务字典 `news_module` 的 `dict_value`,展示名称来自 `dict_label`。管理员可以在 CMS 的“业务字典”中继续维护模块项。
|
||||
- `content` 为富文本 HTML。服务端保存前会按白名单清理脚本、事件属性、危险协议等内容。
|
||||
- 列表接口返回分页结构:`{ code, msg, total, rows }`;详情和写操作返回标准 RuoYi 结构。
|
||||
|
||||
## CMS 管理接口
|
||||
|
||||
### 1. 查询新闻资讯列表
|
||||
|
||||
```http
|
||||
GET /api/cms/newsInfo/list
|
||||
```
|
||||
|
||||
权限:`cms:newsInfo:list`
|
||||
|
||||
请求参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `pageNum` | integer | 否 | 页码,默认 `1` |
|
||||
| `pageSize` | integer | 否 | 每页条数,默认 `10` |
|
||||
| `title` | string | 否 | 标题关键词 |
|
||||
| `module` | string | 否 | `news_module` 字典值 |
|
||||
| `status` | string | 否 | `0` 上架,`1` 下架 |
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /api/cms/newsInfo/list?pageNum=1&pageSize=10&module=interview_tips&status=1
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"total": 1,
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"module": "interview_tips",
|
||||
"moduleName": "面试技巧",
|
||||
"title": "结构化面试准备指南",
|
||||
"status": "1",
|
||||
"createTime": "2026-07-20 15:00:00",
|
||||
"updateTime": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
列表不返回正文,查看正文使用详情接口。
|
||||
|
||||
### 2. 查询新闻资讯详情
|
||||
|
||||
```http
|
||||
GET /api/cms/newsInfo/{id}
|
||||
```
|
||||
|
||||
权限:`cms:newsInfo:query`
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"module": "interview_tips",
|
||||
"moduleName": "面试技巧",
|
||||
"title": "结构化面试准备指南",
|
||||
"content": "<h2>准备重点</h2><p><strong>先了解岗位要求。</strong></p>",
|
||||
"status": "1",
|
||||
"createBy": "admin",
|
||||
"createTime": "2026-07-20 15:00:00",
|
||||
"updateBy": "admin",
|
||||
"updateTime": null,
|
||||
"remark": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 新增新闻资讯
|
||||
|
||||
```http
|
||||
POST /api/cms/newsInfo
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
权限:`cms:newsInfo:add`
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "job_information",
|
||||
"title": "求职资讯标题",
|
||||
"content": "<p>经过白名单清理的富文本正文。</p>",
|
||||
"status": "1",
|
||||
"remark": ""
|
||||
}
|
||||
```
|
||||
|
||||
`module` 必须是启用状态的 `news_module` 字典值;`status` 省略时按下架处理。标题不能为空且最多 200 个字符,正文必须包含文字或图片内容。
|
||||
|
||||
### 4. 修改新闻资讯
|
||||
|
||||
```http
|
||||
PUT /api/cms/newsInfo
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
权限:`cms:newsInfo:edit`
|
||||
|
||||
请求体与新增相同,必须带 `id`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"module": "interview_tips",
|
||||
"title": "更新后的标题",
|
||||
"content": "<p>更新后的正文。</p>",
|
||||
"status": "1",
|
||||
"remark": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 上架或下架
|
||||
|
||||
```http
|
||||
PUT /api/cms/newsInfo/changeStatus?id=1&status=0
|
||||
```
|
||||
|
||||
权限:`cms:newsInfo:status`
|
||||
|
||||
参数 `status` 只能是 `0`(上架)或 `1`(下架)。接口会同时记录更新人和更新时间。
|
||||
|
||||
### 6. 删除新闻资讯
|
||||
|
||||
```http
|
||||
DELETE /api/cms/newsInfo/{ids}
|
||||
```
|
||||
|
||||
权限:`cms:newsInfo:remove`
|
||||
|
||||
`ids` 支持逗号分隔的多个 ID,例如:
|
||||
|
||||
```http
|
||||
DELETE /api/cms/newsInfo/1,2,3
|
||||
```
|
||||
|
||||
删除使用软删除,数据不会从数据库物理移除。
|
||||
|
||||
## 公开查询接口
|
||||
|
||||
### 7. 查询已上架新闻资讯
|
||||
|
||||
```http
|
||||
GET /api/app/newsInfo/list
|
||||
```
|
||||
|
||||
无需登录。参数与 CMS 列表相同,但服务端始终强制 `status=0`;调用方可使用 `title` 和 `module` 过滤。列表同样不返回正文。
|
||||
|
||||
### 8. 查询已上架新闻资讯详情
|
||||
|
||||
```http
|
||||
GET /api/app/newsInfo/{id}
|
||||
```
|
||||
|
||||
无需登录。只有同时满足“未删除、已上架”的记录才会返回;下架或不存在时 `data` 为 `null`。
|
||||
|
||||
## 业务字典
|
||||
|
||||
新闻模块选项通过现有接口查询:
|
||||
|
||||
```http
|
||||
GET /api/cms/dict/data/type/news_module
|
||||
```
|
||||
|
||||
典型初始化数据:
|
||||
|
||||
| `dict_value` | `dict_label` |
|
||||
| --- | --- |
|
||||
| `job_information` | 求职资讯 |
|
||||
| `interview_tips` | 面试技巧 |
|
||||
| `resume_guide` | 简历指南 |
|
||||
| `industry_guide` | 行业指南 |
|
||||
| `policy_regulations` | 政策法规 |
|
||||
|
||||
## 图片上传
|
||||
|
||||
富文本编辑器图片使用现有通用上传接口:
|
||||
|
||||
```http
|
||||
POST /api/common/upload
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
表单字段:`file`。响应中的 `url` 用于插入富文本图片。
|
||||
@@ -127,6 +127,12 @@
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.22</version>
|
||||
</dependency>
|
||||
<!-- 新闻资讯富文本 HTML 白名单清理(Java 8 兼容版本) -->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.15.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.cms.controller.app;
|
||||
|
||||
import com.ruoyi.cms.domain.news.NewsInfoQuery;
|
||||
import com.ruoyi.cms.service.news.INewsInfoService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 用户端新闻资讯公开查询接口。
|
||||
*/
|
||||
@Anonymous
|
||||
@RestController
|
||||
@RequestMapping("/app/newsInfo")
|
||||
@Api(tags = "移动端:新闻资讯")
|
||||
public class AppNewsInfoController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private INewsInfoService newsInfoService;
|
||||
|
||||
@ApiOperation("查询已上架新闻资讯列表")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(NewsInfoQuery query) {
|
||||
startPage();
|
||||
return getDataTable(newsInfoService.selectPublishedList(query));
|
||||
}
|
||||
|
||||
@ApiOperation("获取已上架新闻资讯详情")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable Long id) {
|
||||
return success(newsInfoService.selectPublishedById(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.ruoyi.cms.controller.cms;
|
||||
|
||||
import com.ruoyi.cms.domain.news.NewsInfo;
|
||||
import com.ruoyi.cms.domain.news.NewsInfoQuery;
|
||||
import com.ruoyi.cms.service.news.INewsInfoService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* CMS 新闻资讯管理。
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/cms/newsInfo")
|
||||
@Api(tags = "CMS:新闻资讯管理")
|
||||
public class CmsNewsInfoController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private INewsInfoService newsInfoService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('cms:newsInfo:list')")
|
||||
@ApiOperation("查询新闻资讯列表")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(NewsInfoQuery query) {
|
||||
startPage();
|
||||
return getDataTable(newsInfoService.selectList(query));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('cms:newsInfo:query')")
|
||||
@ApiOperation("获取新闻资讯详情")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable Long id) {
|
||||
return success(newsInfoService.selectById(id));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('cms:newsInfo:add')")
|
||||
@Log(title = "新闻资讯", businessType = BusinessType.INSERT)
|
||||
@ApiOperation("新增新闻资讯")
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody NewsInfo newsInfo) {
|
||||
return toAjax(newsInfoService.insert(newsInfo));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('cms:newsInfo:edit')")
|
||||
@Log(title = "新闻资讯", businessType = BusinessType.UPDATE)
|
||||
@ApiOperation("修改新闻资讯")
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody NewsInfo newsInfo) {
|
||||
return toAjax(newsInfoService.update(newsInfo));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('cms:newsInfo:remove')")
|
||||
@Log(title = "新闻资讯", businessType = BusinessType.DELETE)
|
||||
@ApiOperation("删除新闻资讯")
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(newsInfoService.deleteByIds(ids));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('cms:newsInfo:status')")
|
||||
@Log(title = "新闻资讯上下架", businessType = BusinessType.UPDATE)
|
||||
@ApiOperation("新闻资讯上架或下架")
|
||||
@PutMapping("/changeStatus")
|
||||
public AjaxResult changeStatus(@RequestParam Long id, @RequestParam String status) {
|
||||
return toAjax(newsInfoService.updateStatus(id, status));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.cms.domain.news;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import com.ruoyi.common.xss.Xss;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 新闻资讯对象。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("新闻资讯")
|
||||
public class NewsInfo extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("新闻资讯ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "资讯模块,对应业务字典 news_module 的 dict_value", required = true)
|
||||
@NotBlank(message = "资讯模块不能为空")
|
||||
@Size(max = 100, message = "资讯模块长度不能超过100个字符")
|
||||
private String module;
|
||||
|
||||
@ApiModelProperty("资讯模块名称(业务字典标签,只读)")
|
||||
private String moduleName;
|
||||
|
||||
@ApiModelProperty(value = "标题", required = true)
|
||||
@NotBlank(message = "标题不能为空")
|
||||
@Size(max = 200, message = "标题长度不能超过200个字符")
|
||||
@Xss(message = "标题不能包含脚本字符")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty(value = "富文本正文HTML", required = true)
|
||||
@NotBlank(message = "正文不能为空")
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty("上下架状态(0上架 1下架),新增时默认下架")
|
||||
@Pattern(regexp = "^[01]$", message = "状态只能是0(上架)或1(下架)")
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.cms.domain.news;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 新闻资讯查询参数。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("新闻资讯查询参数")
|
||||
public class NewsInfoQuery {
|
||||
|
||||
@ApiModelProperty("页码")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
@ApiModelProperty("每页条数")
|
||||
private Integer pageSize = 10;
|
||||
|
||||
@ApiModelProperty("标题关键词")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("资讯模块字典值")
|
||||
private String module;
|
||||
|
||||
@ApiModelProperty("上下架状态(0上架 1下架)")
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.cms.mapper.news;
|
||||
|
||||
import com.ruoyi.cms.domain.news.NewsInfo;
|
||||
import com.ruoyi.cms.domain.news.NewsInfoQuery;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新闻资讯数据访问层。
|
||||
*/
|
||||
public interface NewsInfoMapper {
|
||||
|
||||
List<NewsInfo> selectNewsInfoList(@Param("query") NewsInfoQuery query);
|
||||
|
||||
NewsInfo selectNewsInfoById(@Param("id") Long id);
|
||||
|
||||
NewsInfo selectPublishedNewsInfoById(@Param("id") Long id);
|
||||
|
||||
int insertNewsInfo(NewsInfo newsInfo);
|
||||
|
||||
int updateNewsInfo(NewsInfo newsInfo);
|
||||
|
||||
int updateNewsInfoStatus(@Param("id") Long id,
|
||||
@Param("status") String status,
|
||||
@Param("updateBy") String updateBy);
|
||||
|
||||
int deleteNewsInfoByIds(@Param("ids") Long[] ids, @Param("updateBy") String updateBy);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.cms.service.news;
|
||||
|
||||
import com.ruoyi.cms.domain.news.NewsInfo;
|
||||
import com.ruoyi.cms.domain.news.NewsInfoQuery;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新闻资讯业务接口。
|
||||
*/
|
||||
public interface INewsInfoService {
|
||||
|
||||
List<NewsInfo> selectList(NewsInfoQuery query);
|
||||
|
||||
List<NewsInfo> selectPublishedList(NewsInfoQuery query);
|
||||
|
||||
NewsInfo selectById(Long id);
|
||||
|
||||
NewsInfo selectPublishedById(Long id);
|
||||
|
||||
int insert(NewsInfo newsInfo);
|
||||
|
||||
int update(NewsInfo newsInfo);
|
||||
|
||||
int updateStatus(Long id, String status);
|
||||
|
||||
int deleteByIds(Long[] ids);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.ruoyi.cms.service.news.impl;
|
||||
|
||||
import com.ruoyi.cms.domain.BussinessDictData;
|
||||
import com.ruoyi.cms.domain.news.NewsInfo;
|
||||
import com.ruoyi.cms.domain.news.NewsInfoQuery;
|
||||
import com.ruoyi.cms.mapper.news.NewsInfoMapper;
|
||||
import com.ruoyi.cms.service.IBussinessDictTypeService;
|
||||
import com.ruoyi.cms.service.news.INewsInfoService;
|
||||
import com.ruoyi.cms.util.RichTextSanitizer;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 新闻资讯业务实现。
|
||||
*/
|
||||
@Service
|
||||
public class NewsInfoServiceImpl implements INewsInfoService {
|
||||
|
||||
public static final String NEWS_MODULE_DICT_TYPE = "news_module";
|
||||
public static final String STATUS_PUBLISHED = "0";
|
||||
public static final String STATUS_OFF_SHELF = "1";
|
||||
|
||||
@Autowired
|
||||
private NewsInfoMapper newsInfoMapper;
|
||||
|
||||
@Autowired
|
||||
private IBussinessDictTypeService businessDictTypeService;
|
||||
|
||||
@Override
|
||||
public List<NewsInfo> selectList(NewsInfoQuery query) {
|
||||
return newsInfoMapper.selectNewsInfoList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NewsInfo> selectPublishedList(NewsInfoQuery query) {
|
||||
query.setStatus(STATUS_PUBLISHED);
|
||||
return newsInfoMapper.selectNewsInfoList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewsInfo selectById(Long id) {
|
||||
return newsInfoMapper.selectNewsInfoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewsInfo selectPublishedById(Long id) {
|
||||
return newsInfoMapper.selectPublishedNewsInfoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int insert(NewsInfo newsInfo) {
|
||||
prepareAndValidate(newsInfo);
|
||||
if (StringUtils.isEmpty(newsInfo.getStatus())) {
|
||||
newsInfo.setStatus(STATUS_OFF_SHELF);
|
||||
}
|
||||
newsInfo.setCreateBy(SecurityUtils.getUsername());
|
||||
return newsInfoMapper.insertNewsInfo(newsInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int update(NewsInfo newsInfo) {
|
||||
if (newsInfo.getId() == null) {
|
||||
throw new ServiceException("新闻资讯ID不能为空");
|
||||
}
|
||||
prepareAndValidate(newsInfo);
|
||||
newsInfo.setUpdateBy(SecurityUtils.getUsername());
|
||||
return newsInfoMapper.updateNewsInfo(newsInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int updateStatus(Long id, String status) {
|
||||
if (id == null) {
|
||||
throw new ServiceException("新闻资讯ID不能为空");
|
||||
}
|
||||
if (!STATUS_PUBLISHED.equals(status) && !STATUS_OFF_SHELF.equals(status)) {
|
||||
throw new ServiceException("状态只能是0(上架)或1(下架)");
|
||||
}
|
||||
return newsInfoMapper.updateNewsInfoStatus(id, status, SecurityUtils.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteByIds(Long[] ids) {
|
||||
if (ids == null || ids.length == 0) {
|
||||
throw new ServiceException("请选择需要删除的新闻资讯");
|
||||
}
|
||||
return newsInfoMapper.deleteNewsInfoByIds(ids, SecurityUtils.getUsername());
|
||||
}
|
||||
|
||||
private void prepareAndValidate(NewsInfo newsInfo) {
|
||||
if (newsInfo == null || StringUtils.isEmpty(newsInfo.getModule())
|
||||
|| StringUtils.isEmpty(newsInfo.getTitle()) || StringUtils.isEmpty(newsInfo.getContent())) {
|
||||
throw new ServiceException("资讯模块、标题和正文不能为空");
|
||||
}
|
||||
newsInfo.setModule(newsInfo.getModule().trim());
|
||||
newsInfo.setTitle(newsInfo.getTitle().trim());
|
||||
if (newsInfo.getModule().isEmpty() || newsInfo.getTitle().isEmpty()) {
|
||||
throw new ServiceException("资讯模块和标题不能为空");
|
||||
}
|
||||
validateModule(newsInfo.getModule());
|
||||
|
||||
String cleanedContent = RichTextSanitizer.clean(newsInfo.getContent());
|
||||
if (!RichTextSanitizer.hasContent(cleanedContent)) {
|
||||
throw new ServiceException("正文不能为空");
|
||||
}
|
||||
newsInfo.setContent(cleanedContent);
|
||||
}
|
||||
|
||||
private void validateModule(String module) {
|
||||
List<BussinessDictData> modules = businessDictTypeService
|
||||
.selectDictDataByType(NEWS_MODULE_DICT_TYPE);
|
||||
boolean exists = modules != null && modules.stream()
|
||||
.anyMatch(item -> module.equals(item.getDictValue()));
|
||||
if (!exists) {
|
||||
throw new ServiceException("资讯模块不存在或已停用,请先在业务字典中维护");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.ruoyi.cms.util;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.safety.Safelist;
|
||||
|
||||
/**
|
||||
* 富文本 HTML 白名单清理。
|
||||
*
|
||||
* <p>保留新闻正文所需的常见排版、链接、图片和表格标签,移除脚本、事件属性、
|
||||
* iframe 以及 javascript/data 等危险协议。</p>
|
||||
*/
|
||||
public final class RichTextSanitizer {
|
||||
|
||||
private static final Safelist NEWS_CONTENT_SAFELIST = Safelist.relaxed()
|
||||
.addTags("figure", "figcaption", "s")
|
||||
.addAttributes("a", "target", "rel")
|
||||
.addAttributes("img", "loading")
|
||||
.addAttributes("table", "border", "cellpadding", "cellspacing")
|
||||
.addProtocols("a", "href", "http", "https", "mailto")
|
||||
.addProtocols("img", "src", "http", "https");
|
||||
|
||||
private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings()
|
||||
.prettyPrint(false);
|
||||
|
||||
private RichTextSanitizer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理并规范化富文本 HTML。
|
||||
*/
|
||||
public static String clean(String html) {
|
||||
if (html == null) {
|
||||
return null;
|
||||
}
|
||||
return Jsoup.clean(html, "", NEWS_CONTENT_SAFELIST, OUTPUT_SETTINGS).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断清理后的正文是否包含文字或图片内容。
|
||||
*/
|
||||
public static boolean hasContent(String cleanedHtml) {
|
||||
if (cleanedHtml == null || cleanedHtml.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Document document = Jsoup.parseBodyFragment(cleanedHtml);
|
||||
return !document.text().trim().isEmpty() || !document.select("img").isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.cms.mapper.news.NewsInfoMapper">
|
||||
|
||||
<resultMap type="com.ruoyi.cms.domain.news.NewsInfo" id="NewsInfoResult">
|
||||
<id property="id" column="id"/>
|
||||
<result property="module" column="module"/>
|
||||
<result property="moduleName" column="module_name"/>
|
||||
<result property="title" column="title"/>
|
||||
<result property="content" column="content"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="moduleNameColumn">
|
||||
(select data.dict_label
|
||||
from bussiness_dict_data data
|
||||
where data.dict_type = 'news_module'
|
||||
and data.dict_value = news.module
|
||||
order by data.dict_sort, data.dict_code
|
||||
limit 1) as module_name
|
||||
</sql>
|
||||
|
||||
<sql id="selectNewsInfoListVo">
|
||||
select news.id, news.module, <include refid="moduleNameColumn"/>, news.title,
|
||||
news.status, news.create_time, news.update_time
|
||||
from news_info news
|
||||
</sql>
|
||||
|
||||
<sql id="selectNewsInfoDetailVo">
|
||||
select news.id, news.module, <include refid="moduleNameColumn"/>, news.title,
|
||||
news.content, news.status, news.create_by, news.create_time,
|
||||
news.update_by, news.update_time, news.remark
|
||||
from news_info news
|
||||
</sql>
|
||||
|
||||
<select id="selectNewsInfoList" resultMap="NewsInfoResult">
|
||||
<include refid="selectNewsInfoListVo"/>
|
||||
<where>
|
||||
news.del_flag = '0'
|
||||
<if test="query.title != null and query.title != ''">
|
||||
and news.title like '%' || #{query.title}::varchar || '%'
|
||||
</if>
|
||||
<if test="query.module != null and query.module != ''">
|
||||
and news.module = #{query.module}
|
||||
</if>
|
||||
<if test="query.status != null and query.status != ''">
|
||||
and news.status = #{query.status}
|
||||
</if>
|
||||
</where>
|
||||
order by news.create_time desc, news.id desc
|
||||
</select>
|
||||
|
||||
<select id="selectNewsInfoById" resultMap="NewsInfoResult">
|
||||
<include refid="selectNewsInfoDetailVo"/>
|
||||
where news.id = #{id} and news.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectPublishedNewsInfoById" resultMap="NewsInfoResult">
|
||||
<include refid="selectNewsInfoDetailVo"/>
|
||||
where news.id = #{id} and news.del_flag = '0' and news.status = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertNewsInfo" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into news_info (
|
||||
module, title, content, status, create_by, create_time, del_flag, remark
|
||||
) values (
|
||||
#{module}, #{title}, #{content}, #{status}, #{createBy}, CURRENT_TIMESTAMP, '0',
|
||||
COALESCE(#{remark}, '')
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateNewsInfo">
|
||||
update news_info
|
||||
set module = #{module},
|
||||
title = #{title},
|
||||
content = #{content},
|
||||
<if test="status != null and status != ''">
|
||||
status = #{status},
|
||||
</if>
|
||||
update_by = #{updateBy},
|
||||
update_time = CURRENT_TIMESTAMP,
|
||||
remark = COALESCE(#{remark}, '')
|
||||
where id = #{id} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<update id="updateNewsInfoStatus">
|
||||
update news_info
|
||||
set status = #{status}, update_by = #{updateBy}, update_time = CURRENT_TIMESTAMP
|
||||
where id = #{id} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<update id="deleteNewsInfoByIds">
|
||||
update news_info
|
||||
set del_flag = '2', update_by = #{updateBy}, update_time = CURRENT_TIMESTAMP
|
||||
where del_flag = '0'
|
||||
and id in
|
||||
<foreach item="id" collection="ids" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.cms.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RichTextSanitizerTest {
|
||||
|
||||
@Test
|
||||
void shouldKeepCommonRichTextAndRemoveExecutableContent() {
|
||||
String source = "<h2>标题</h2><p onclick=\"alert(1)\"><strong>正文</strong></p>"
|
||||
+ "<script>alert('xss')</script>"
|
||||
+ "<a href=\"javascript:alert(2)\">危险链接</a>"
|
||||
+ "<img src=\"https://example.com/news.png\" onerror=\"alert(3)\">";
|
||||
|
||||
String cleaned = RichTextSanitizer.clean(source);
|
||||
|
||||
assertTrue(cleaned.contains("<h2>标题</h2>"));
|
||||
assertTrue(cleaned.contains("<strong>正文</strong>"));
|
||||
assertTrue(cleaned.contains("https://example.com/news.png"));
|
||||
assertFalse(cleaned.contains("script"));
|
||||
assertFalse(cleaned.contains("alert"));
|
||||
assertFalse(cleaned.contains("onclick"));
|
||||
assertFalse(cleaned.contains("onerror"));
|
||||
assertFalse(cleaned.contains("javascript:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTreatEditorPlaceholderAsEmpty() {
|
||||
String cleaned = RichTextSanitizer.clean("<p><br></p>");
|
||||
|
||||
assertFalse(RichTextSanitizer.hasContent(cleaned));
|
||||
assertTrue(RichTextSanitizer.hasContent("<p>有效正文</p>"));
|
||||
assertTrue(RichTextSanitizer.hasContent("<img src=\"https://example.com/a.png\">"));
|
||||
}
|
||||
}
|
||||
20
sql/migration_add_job_status_and_key_populations.sql
Normal file
20
sql/migration_add_job_status_and_key_populations.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- 岗位上下架状态、是否重点人群及重点人群类型字段。
|
||||
-- 执行目标:对应环境 HighGo 数据库的 shz schema。
|
||||
-- 可重复执行:字段使用 IF NOT EXISTS,列注释可重复设置。
|
||||
-- 字段定义与测试库 shz.job 保持一致:允许为空且不设置默认值。
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE "shz"."job"
|
||||
ADD COLUMN IF NOT EXISTS "job_status" VARCHAR(2),
|
||||
ADD COLUMN IF NOT EXISTS "is_key_populations" VARCHAR(2),
|
||||
ADD COLUMN IF NOT EXISTS "key_populations" VARCHAR(20);
|
||||
|
||||
COMMENT ON COLUMN "shz"."job"."job_status"
|
||||
IS '是否下架(0上架,1下架)';
|
||||
COMMENT ON COLUMN "shz"."job"."is_key_populations"
|
||||
IS '是否重点人群';
|
||||
COMMENT ON COLUMN "shz"."job"."key_populations"
|
||||
IS '重点人群(1农民工、2团场职工、3失业人员、4零就业家庭、5零工人员、6退役军人)';
|
||||
|
||||
COMMIT;
|
||||
212
sql/news_info.sql
Normal file
212
sql/news_info.sql
Normal file
@@ -0,0 +1,212 @@
|
||||
-- 新闻资讯:数据表、业务字典与 CMS 菜单权限。
|
||||
-- 执行目标:测试/生产 HighGo 数据库的 shz schema。
|
||||
-- 可重复执行:表/索引、字典类型/字典项、菜单/按钮均做幂等处理。
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 显式创建序列以兼容生产 HighGo(生产环境不识别 BIGSERIAL 类型别名)。
|
||||
CREATE SEQUENCE IF NOT EXISTS "shz"."news_info_id_seq"
|
||||
START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "shz"."news_info" (
|
||||
"id" BIGINT NOT NULL DEFAULT nextval('shz.news_info_id_seq'::regclass) PRIMARY KEY,
|
||||
"module" VARCHAR(100) NOT NULL,
|
||||
"title" VARCHAR(200) NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"status" CHAR(1) NOT NULL DEFAULT '1',
|
||||
"create_by" VARCHAR(64) NOT NULL DEFAULT '',
|
||||
"create_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"update_by" VARCHAR(64) NOT NULL DEFAULT '',
|
||||
"update_time" TIMESTAMP,
|
||||
"del_flag" CHAR(1) NOT NULL DEFAULT '0',
|
||||
"remark" VARCHAR(500) NOT NULL DEFAULT '',
|
||||
CONSTRAINT "chk_news_info_status" CHECK ("status" IN ('0', '1')),
|
||||
CONSTRAINT "chk_news_info_del_flag" CHECK ("del_flag" IN ('0', '2'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE "shz"."news_info" IS '新闻资讯表';
|
||||
COMMENT ON COLUMN "shz"."news_info"."id" IS '新闻资讯ID';
|
||||
COMMENT ON COLUMN "shz"."news_info"."module" IS '资讯模块,对应业务字典 news_module 的 dict_value';
|
||||
COMMENT ON COLUMN "shz"."news_info"."title" IS '标题';
|
||||
COMMENT ON COLUMN "shz"."news_info"."content" IS '富文本正文(经过服务端白名单清理的HTML)';
|
||||
COMMENT ON COLUMN "shz"."news_info"."status" IS '上下架状态(0上架 1下架)';
|
||||
COMMENT ON COLUMN "shz"."news_info"."create_by" IS '创建者';
|
||||
COMMENT ON COLUMN "shz"."news_info"."create_time" IS '创建时间';
|
||||
COMMENT ON COLUMN "shz"."news_info"."update_by" IS '更新者';
|
||||
COMMENT ON COLUMN "shz"."news_info"."update_time" IS '更新时间';
|
||||
COMMENT ON COLUMN "shz"."news_info"."del_flag" IS '删除标志(0存在 2删除)';
|
||||
COMMENT ON COLUMN "shz"."news_info"."remark" IS '备注';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "idx_news_info_module"
|
||||
ON "shz"."news_info" ("module");
|
||||
CREATE INDEX IF NOT EXISTS "idx_news_info_status_created"
|
||||
ON "shz"."news_info" ("status", "create_time" DESC)
|
||||
WHERE "del_flag" = '0';
|
||||
|
||||
-- 资讯模块使用“业务字典”,资讯表只保存 dict_value;管理员可在现有业务字典页面继续添加模块项。
|
||||
INSERT INTO "shz"."bussiness_dict_type" (
|
||||
"dict_id", "dict_name", "dict_type", "status", "create_by", "create_time", "remark"
|
||||
)
|
||||
SELECT
|
||||
(SELECT COALESCE(MAX("dict_id"), 0) + 1 FROM "shz"."bussiness_dict_type"),
|
||||
'新闻资讯模块',
|
||||
'news_module',
|
||||
'0',
|
||||
'system',
|
||||
CURRENT_TIMESTAMP,
|
||||
'新闻资讯模块,资讯表保存 dict_value,可在业务字典中扩展'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "shz"."bussiness_dict_type"
|
||||
WHERE "dict_type" = 'news_module'
|
||||
);
|
||||
|
||||
WITH seed("dict_sort", "dict_label", "dict_value", "list_class", "is_default") AS (
|
||||
VALUES
|
||||
(1, '求职资讯', 'job_information', 'primary', 'Y'),
|
||||
(2, '面试技巧', 'interview_tips', 'success', 'N'),
|
||||
(3, '简历指南', 'resume_guide', 'warning', 'N'),
|
||||
(4, '行业指南', 'industry_guide', 'info', 'N'),
|
||||
(5, '政策法规', 'policy_regulations', 'danger', 'N')
|
||||
),
|
||||
new_rows AS (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY seed."dict_sort") AS rn,
|
||||
seed.*
|
||||
FROM seed
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "shz"."bussiness_dict_data" data
|
||||
WHERE data."dict_type" = 'news_module'
|
||||
AND data."dict_value" = seed."dict_value"
|
||||
)
|
||||
),
|
||||
base AS (
|
||||
SELECT COALESCE(MAX("dict_code"), 0) AS max_code
|
||||
FROM "shz"."bussiness_dict_data"
|
||||
)
|
||||
INSERT INTO "shz"."bussiness_dict_data" (
|
||||
"dict_code", "dict_sort", "dict_label", "dict_value", "dict_type",
|
||||
"css_class", "list_class", "is_default", "status", "create_by", "create_time", "remark"
|
||||
)
|
||||
SELECT
|
||||
base.max_code + new_rows.rn,
|
||||
new_rows."dict_sort",
|
||||
new_rows."dict_label",
|
||||
new_rows."dict_value",
|
||||
'news_module',
|
||||
NULL,
|
||||
new_rows."list_class",
|
||||
new_rows."is_default",
|
||||
'0',
|
||||
'system',
|
||||
CURRENT_TIMESTAMP,
|
||||
'新闻资讯模块'
|
||||
FROM new_rows
|
||||
CROSS JOIN base;
|
||||
|
||||
-- CMS 一级目录。
|
||||
INSERT INTO "shz"."sys_menu" (
|
||||
"menu_name", "parent_id", "order_num", "path", "component", "query", "route_name",
|
||||
"is_frame", "is_cache", "menu_type", "visible", "status", "perms", "icon",
|
||||
"create_by", "create_time", "update_by", "update_time", "remark", "menu_id"
|
||||
)
|
||||
SELECT
|
||||
'新闻资讯', 0, 8, 'news-information', '', '', 'NewsInformation',
|
||||
1, 0, 'M', '0', '0', '', 'ReadOutlined',
|
||||
'system', CURRENT_TIMESTAMP, '', NULL, '新闻资讯管理目录', ids."menu_id"
|
||||
FROM (
|
||||
SELECT COALESCE(MAX("menu_id"), 0) + 1 AS "menu_id"
|
||||
FROM "shz"."sys_menu"
|
||||
) ids
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "shz"."sys_menu"
|
||||
WHERE "parent_id" = 0 AND "path" = 'news-information'
|
||||
);
|
||||
|
||||
-- CMS 资讯管理页面;页面权限同时作为列表权限。
|
||||
INSERT INTO "shz"."sys_menu" (
|
||||
"menu_name", "parent_id", "order_num", "path", "component", "query", "route_name",
|
||||
"is_frame", "is_cache", "menu_type", "visible", "status", "perms", "icon",
|
||||
"create_by", "create_time", "update_by", "update_time", "remark", "menu_id"
|
||||
)
|
||||
SELECT
|
||||
'资讯管理', parent."menu_id", 1, 'news-info', 'NewsInfo/index', '', 'NewsInfo',
|
||||
1, 0, 'C', '0', '0', 'cms:newsInfo:list', 'ProfileOutlined',
|
||||
'system', CURRENT_TIMESTAMP, '', NULL, '新闻资讯管理页面', ids."menu_id"
|
||||
FROM (
|
||||
SELECT "menu_id"
|
||||
FROM "shz"."sys_menu"
|
||||
WHERE "parent_id" = 0 AND "path" = 'news-information'
|
||||
ORDER BY "menu_id"
|
||||
LIMIT 1
|
||||
) parent
|
||||
CROSS JOIN (
|
||||
SELECT COALESCE(MAX("menu_id"), 0) + 1 AS "menu_id"
|
||||
FROM "shz"."sys_menu"
|
||||
) ids
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "shz"."sys_menu"
|
||||
WHERE "perms" = 'cms:newsInfo:list'
|
||||
);
|
||||
|
||||
-- 页面按钮权限。
|
||||
WITH parent AS (
|
||||
SELECT "menu_id"
|
||||
FROM "shz"."sys_menu"
|
||||
WHERE "perms" = 'cms:newsInfo:list'
|
||||
ORDER BY "menu_id"
|
||||
LIMIT 1
|
||||
),
|
||||
seed("order_num", "menu_name", "perms", "remark") AS (
|
||||
VALUES
|
||||
(1, '新闻资讯查询', 'cms:newsInfo:query', '查询新闻资讯详情'),
|
||||
(2, '新闻资讯新增', 'cms:newsInfo:add', '新增新闻资讯'),
|
||||
(3, '新闻资讯修改', 'cms:newsInfo:edit', '修改新闻资讯'),
|
||||
(4, '新闻资讯删除', 'cms:newsInfo:remove', '删除新闻资讯'),
|
||||
(5, '新闻资讯上下架', 'cms:newsInfo:status', '新闻资讯上架或下架')
|
||||
),
|
||||
new_rows AS (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY seed."order_num") AS rn,
|
||||
seed.*
|
||||
FROM seed
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "shz"."sys_menu" existing
|
||||
WHERE existing."perms" = seed."perms"
|
||||
)
|
||||
),
|
||||
base AS (
|
||||
SELECT COALESCE(MAX("menu_id"), 0) AS max_id
|
||||
FROM "shz"."sys_menu"
|
||||
)
|
||||
INSERT INTO "shz"."sys_menu" (
|
||||
"menu_id", "menu_name", "parent_id", "order_num", "path", "component", "query", "route_name",
|
||||
"is_frame", "is_cache", "menu_type", "visible", "status", "perms", "icon",
|
||||
"create_by", "create_time", "update_by", "update_time", "remark"
|
||||
)
|
||||
SELECT
|
||||
base.max_id + new_rows.rn,
|
||||
new_rows."menu_name",
|
||||
parent."menu_id",
|
||||
new_rows."order_num",
|
||||
'', '', '', '',
|
||||
1, 0, 'F', '0', '0', new_rows."perms", '#',
|
||||
'system', CURRENT_TIMESTAMP, '', NULL, new_rows."remark"
|
||||
FROM new_rows
|
||||
CROSS JOIN parent
|
||||
CROSS JOIN base;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 说明:管理员角色通过 *:*:* 自动拥有全部权限;其他角色请在 CMS“角色管理”中按需分配“新闻资讯”。
|
||||
-- 验证:
|
||||
-- SELECT column_name, data_type FROM information_schema.columns
|
||||
-- WHERE table_schema = 'shz' AND table_name = 'news_info' ORDER BY ordinal_position;
|
||||
-- SELECT dict_label, dict_value FROM shz.bussiness_dict_data
|
||||
-- WHERE dict_type = 'news_module' ORDER BY dict_sort;
|
||||
-- SELECT menu_id, menu_name, parent_id, menu_type, perms FROM shz.sys_menu
|
||||
-- WHERE path = 'news-information' OR perms LIKE 'cms:newsInfo:%' ORDER BY menu_id;
|
||||
Reference in New Issue
Block a user