feat: enhance coding harness, model security and system features

This commit is contained in:
ageerle
2026-09-08 23:33:29 +08:00
parent d4ed2b8672
commit bcff4fd9e3
259 changed files with 15183 additions and 2713 deletions

View File

@@ -110,6 +110,12 @@
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -81,7 +81,8 @@ public class SysUserOnlineController extends BaseController {
* @param tokenId token值
*/
@SaCheckPermission("monitor:online:forceLogout")
@Log(title = "在线用户", businessType = BusinessType.FORCE)
@Log(title = "在线用户", businessType = BusinessType.FORCE,
isSaveRequestData = false, isSaveResponseData = false)
@RepeatSubmit()
@DeleteMapping("/{tokenId}")
public R<Void> forceLogout(@PathVariable String tokenId) {
@@ -115,7 +116,8 @@ public class SysUserOnlineController extends BaseController {
*
* @param tokenId token值
*/
@Log(title = "在线设备", businessType = BusinessType.FORCE)
@Log(title = "在线设备", businessType = BusinessType.FORCE,
isSaveRequestData = false, isSaveResponseData = false)
@RepeatSubmit()
@DeleteMapping("/myself/{tokenId}")
public R<Void> remove(@PathVariable("tokenId") String tokenId) {

View File

@@ -1,80 +0,0 @@
package org.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.system.domain.bo.CmsContentBo;
import org.ruoyi.system.domain.vo.CmsContentVo;
import org.ruoyi.system.service.ICmsContentService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 内容管理 信息操作处理
*
* @author ruoyi
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/cms/content")
public class CmsContentController extends BaseController {
private final ICmsContentService cmsContentService;
/**
* 获取内容管理列表
*/
@SaCheckPermission("cms:content:list")
@GetMapping("/list")
public TableDataInfo<CmsContentVo> list(CmsContentBo bo, PageQuery pageQuery) {
return cmsContentService.selectPageCmsContentList(bo, pageQuery);
}
/**
* 根据内容编号获取详细信息
*/
@SaCheckPermission("cms:content:query")
@GetMapping(value = "/{contentId}")
public R<CmsContentVo> getInfo(@PathVariable Long contentId) {
return R.ok(cmsContentService.selectCmsContentById(contentId));
}
/**
* 新增内容
*/
@SaCheckPermission("cms:content:add")
@Log(title = "内容管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody CmsContentBo bo) {
return toAjax(cmsContentService.insertCmsContent(bo));
}
/**
* 修改内容
*/
@SaCheckPermission("cms:content:edit")
@Log(title = "内容管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody CmsContentBo bo) {
return toAjax(cmsContentService.updateCmsContent(bo));
}
/**
* 删除内容
*/
@SaCheckPermission("cms:content:remove")
@Log(title = "内容管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{contentIds}")
public R<Void> remove(@PathVariable Long[] contentIds) {
return toAjax(cmsContentService.deleteCmsContentByIds(contentIds));
}
}

View File

@@ -0,0 +1,101 @@
package org.ruoyi.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.system.domain.bo.SysUrlBo;
import org.ruoyi.system.domain.vo.SysUrlShortcutVo;
import org.ruoyi.system.domain.vo.SysUrlVo;
import org.ruoyi.system.service.ISysUrlService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* URL 管理
*
* @author ruoyi
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/url")
public class SysUrlController extends BaseController {
private final ISysUrlService urlService;
/**
* 分页查询 URL 列表(支持名称、状态筛选)
*/
@SaCheckPermission("system:url:list")
@GetMapping("/list")
public TableDataInfo<SysUrlVo> list(SysUrlBo bo, PageQuery pageQuery) {
return urlService.selectPageUrlList(bo, pageQuery);
}
/**
* 获取 Copilot 新会话快捷入口(当前租户已启用链接)
* 允许 coding:harness:use 或 system:url:list 权限
*/
@SaCheckPermission(value = {"system:url:list", "coding:harness:use"}, mode = SaMode.OR)
@GetMapping("/shortcuts")
public R<List<SysUrlShortcutVo>> shortcuts() {
return R.ok(urlService.selectShortcutList());
}
/**
* 根据 ID 获取 URL 详细信息
*
* @param urlId 链接ID
*/
@SaCheckPermission("system:url:query")
@GetMapping(value = "/{urlId}")
public R<SysUrlVo> getInfo(@NotNull(message = "链接ID不能为空") @PathVariable Long urlId) {
return R.ok(urlService.selectUrlById(urlId));
}
/**
* 新增 URL
*/
@SaCheckPermission("system:url:add")
@Log(title = "URL管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysUrlBo bo) {
return toAjax(urlService.insertUrl(bo));
}
/**
* 修改 URL
*/
@SaCheckPermission("system:url:edit")
@Log(title = "URL管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysUrlBo bo) {
return toAjax(urlService.updateUrl(bo));
}
/**
* 删除 URL(支持批量)
*
* @param urlIds 链接ID串
*/
@SaCheckPermission("system:url:remove")
@Log(title = "URL管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{urlIds}")
public R<Void> remove(@PathVariable Long[] urlIds) {
return toAjax(urlService.deleteUrlByIds(urlIds));
}
}

View File

@@ -1,74 +0,0 @@
package org.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import org.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 内容管理表 cms_content
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("cms_content")
public class CmsContent extends TenantEntity {
/**
* 内容ID
*/
@TableId(value = "content_id")
private Long contentId;
/**
* 内容标题
*/
private String title;
/**
* 摘要
*/
private String summary;
/**
* 正文内容
*/
private String content;
/**
* 封面图片
*/
private String coverImage;
/**
* 状态(0草稿 1已发布 2下架)
*/
private String status;
/**
* 发布时间
*/
private Date publishTime;
/**
* 排序顺序
*/
private Integer sortOrder;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private String delFlag;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,55 @@
package org.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.ruoyi.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* URL 管理表 sys_url
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_url")
public class SysUrl extends TenantEntity {
/**
* 链接ID
*/
@TableId(value = "url_id")
private Long urlId;
/**
* 链接名称
*/
private String name;
/**
* HTTP(S) 地址
*/
private String url;
/**
* 链接说明
*/
private String description;
/**
* 排序
*/
private Integer sortOrder;
/**
* 状态(0正常 1停用)
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,80 +0,0 @@
package org.ruoyi.system.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.core.xss.Xss;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import org.ruoyi.system.domain.CmsContent;
import java.util.Date;
/**
* 内容管理业务对象 cms_content
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = CmsContent.class, reverseConvertGenerate = false)
public class CmsContentBo extends BaseEntity {
/**
* 内容ID
*/
private Long contentId;
/**
* 内容标题
*/
@Xss(message = "内容标题不能包含脚本字符")
@NotBlank(message = "内容标题不能为空")
@Size(min = 0, max = 200, message = "内容标题不能超过{max}个字符")
private String title;
/**
* 摘要
*/
@Size(min = 0, max = 500, message = "摘要不能超过{max}个字符")
private String summary;
/**
* 正文内容
*/
private String content;
/**
* 封面图片
*/
@Size(min = 0, max = 255, message = "封面图片路径不能超过{max}个字符")
private String coverImage;
/**
* 状态(0草稿 1已发布 2下架)
*/
private String status;
/**
* 发布时间
*/
private Date publishTime;
/**
* 排序顺序
*/
private Integer sortOrder;
/**
* 备注
*/
@Size(min = 0, max = 500, message = "备注不能超过{max}个字符")
private String remark;
/**
* 创建人名称
*/
private String createByName;
}

View File

@@ -0,0 +1,69 @@
package org.ruoyi.system.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import org.ruoyi.system.domain.SysUrl;
/**
* URL 管理业务对象 sys_url
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysUrl.class, reverseConvertGenerate = false)
public class SysUrlBo extends BaseEntity {
/**
* 链接ID
*/
@NotNull(message = "链接ID不能为空", groups = {EditGroup.class})
private Long urlId;
/**
* 链接名称
*/
@NotBlank(message = "链接名称不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(max = 100, message = "链接名称不能超过{max}个字符", groups = {AddGroup.class, EditGroup.class})
private String name;
/**
* HTTP(S) 地址
*/
@NotBlank(message = "链接地址不能为空", groups = {AddGroup.class, EditGroup.class})
@Size(max = 500, message = "链接地址不能超过{max}个字符", groups = {AddGroup.class, EditGroup.class})
@Pattern(regexp = "^https?://.*", message = "链接地址必须以 http:// 或 https:// 开头", groups = {AddGroup.class, EditGroup.class})
private String url;
/**
* 链接说明
*/
@Size(max = 500, message = "链接说明不能超过{max}个字符", groups = {AddGroup.class, EditGroup.class})
private String description;
/**
* 排序
*/
private Integer sortOrder;
/**
* 状态(0正常 1停用)
*/
@NotBlank(message = "状态不能为空", groups = {AddGroup.class, EditGroup.class})
@Pattern(regexp = "^[01]$", message = "状态只能为 0(正常)或 1(停用)", groups = {AddGroup.class, EditGroup.class})
private String status;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,86 +0,0 @@
package org.ruoyi.system.domain.vo;
import org.ruoyi.common.translation.annotation.Translation;
import org.ruoyi.common.translation.constant.TransConstant;
import org.ruoyi.system.domain.CmsContent;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 内容管理视图对象 cms_content
*
* @author ruoyi
*/
@Data
@AutoMapper(target = CmsContent.class)
public class CmsContentVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 内容ID
*/
private Long contentId;
/**
* 内容标题
*/
private String title;
/**
* 摘要
*/
private String summary;
/**
* 正文内容
*/
private String content;
/**
* 封面图片
*/
private String coverImage;
/**
* 状态(0草稿 1已发布 2下架)
*/
private String status;
/**
* 发布时间
*/
private Date publishTime;
/**
* 排序顺序
*/
private Integer sortOrder;
/**
* 备注
*/
private String remark;
/**
* 创建者
*/
private Long createBy;
/**
* 创建人名称
*/
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
private String createByName;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -0,0 +1,44 @@
package org.ruoyi.system.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* URL 快捷入口视图对象(供 Copilot 新会话展示)
*
* @author ruoyi
*/
@Data
public class SysUrlShortcutVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 链接ID
*/
private Long urlId;
/**
* 链接名称
*/
private String name;
/**
* HTTP(S) 地址
*/
private String url;
/**
* 链接说明
*/
private String description;
/**
* 排序
*/
private Integer sortOrder;
}

View File

@@ -0,0 +1,73 @@
package org.ruoyi.system.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.system.domain.SysUrl;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* URL 管理视图对象 sys_url
*
* @author ruoyi
*/
@Data
@AutoMapper(target = SysUrl.class)
public class SysUrlVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 链接ID
*/
private Long urlId;
/**
* 租户编号
*/
private String tenantId;
/**
* 链接名称
*/
private String name;
/**
* HTTP(S) 地址
*/
private String url;
/**
* 链接说明
*/
private String description;
/**
* 排序
*/
private Integer sortOrder;
/**
* 状态(0正常 1停用)
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@@ -73,7 +73,7 @@ public class UserActionListener implements SaTokenListener {
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
log.info("{}", authenticationEventSummary("login", loginId));
}
/**
@@ -85,7 +85,7 @@ public class UserActionListener implements SaTokenListener {
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
log.info("{}", authenticationEventSummary("logout", loginId));
}
/**
@@ -97,7 +97,7 @@ public class UserActionListener implements SaTokenListener {
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
log.info("{}", authenticationEventSummary("kickout", loginId));
}
/**
@@ -109,7 +109,7 @@ public class UserActionListener implements SaTokenListener {
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
log.info("{}", authenticationEventSummary("replaced", loginId));
}
/**
@@ -160,4 +160,9 @@ public class UserActionListener implements SaTokenListener {
@Override
public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
}
/** The logging boundary cannot accept a token, making accidental formatting impossible. */
static String authenticationEventSummary(String event, Object loginId) {
return "authentication event=" + event + ", userId=" + loginId;
}
}

View File

@@ -1,14 +0,0 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.system.domain.CmsContent;
import org.ruoyi.system.domain.vo.CmsContentVo;
/**
* 内容管理表 数据层
*
* @author ruoyi
*/
public interface CmsContentMapper extends BaseMapperPlus<CmsContent, CmsContentVo> {
}

View File

@@ -0,0 +1,14 @@
package org.ruoyi.system.mapper;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.system.domain.SysUrl;
import org.ruoyi.system.domain.vo.SysUrlVo;
/**
* URL 管理表 数据层
*
* @author ruoyi
*/
public interface SysUrlMapper extends BaseMapperPlus<SysUrl, SysUrlVo> {
}

View File

@@ -1,51 +0,0 @@
package org.ruoyi.system.service;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.system.domain.bo.CmsContentBo;
import org.ruoyi.system.domain.vo.CmsContentVo;
import java.util.List;
/**
* 内容管理 服务层
*
* @author ruoyi
*/
public interface ICmsContentService {
/**
* 分页查询内容管理列表
*/
TableDataInfo<CmsContentVo> selectPageCmsContentList(CmsContentBo bo, PageQuery pageQuery);
/**
* 查询内容信息
*/
CmsContentVo selectCmsContentById(Long contentId);
/**
* 查询内容列表
*/
List<CmsContentVo> selectCmsContentList(CmsContentBo bo);
/**
* 新增内容
*/
int insertCmsContent(CmsContentBo bo);
/**
* 修改内容
*/
int updateCmsContent(CmsContentBo bo);
/**
* 删除内容信息
*/
int deleteCmsContentById(Long contentId);
/**
* 批量删除内容信息
*/
int deleteCmsContentByIds(Long[] contentIds);
}

View File

@@ -0,0 +1,66 @@
package org.ruoyi.system.service;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.system.domain.bo.SysUrlBo;
import org.ruoyi.system.domain.vo.SysUrlShortcutVo;
import org.ruoyi.system.domain.vo.SysUrlVo;
import java.util.List;
/**
* URL 管理 服务层
*
* @author ruoyi
*/
public interface ISysUrlService {
/**
* 分页查询 URL 列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return URL 分页列表
*/
TableDataInfo<SysUrlVo> selectPageUrlList(SysUrlBo bo, PageQuery pageQuery);
/**
* 根据 ID 查询 URL 详情
*
* @param urlId 链接ID
* @return URL 信息
*/
SysUrlVo selectUrlById(Long urlId);
/**
* 新增 URL
*
* @param bo URL 信息
* @return 结果
*/
int insertUrl(SysUrlBo bo);
/**
* 修改 URL
*
* @param bo URL 信息
* @return 结果
*/
int updateUrl(SysUrlBo bo);
/**
* 批量删除 URL
*
* @param urlIds 需要删除的链接ID
* @return 结果
*/
int deleteUrlByIds(Long[] urlIds);
/**
* 查询当前租户已启用的快捷入口列表(按排序升序)
*
* @return 快捷入口列表
*/
List<SysUrlShortcutVo> selectShortcutList();
}

View File

@@ -1,81 +0,0 @@
package org.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.system.domain.CmsContent;
import org.ruoyi.system.domain.bo.CmsContentBo;
import org.ruoyi.system.domain.vo.CmsContentVo;
import org.ruoyi.system.mapper.CmsContentMapper;
import org.ruoyi.system.service.ICmsContentService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* 内容管理 服务层实现
*
* @author ruoyi
*/
@RequiredArgsConstructor
@Service
public class CmsContentServiceImpl implements ICmsContentService {
private final CmsContentMapper baseMapper;
@Override
public TableDataInfo<CmsContentVo> selectPageCmsContentList(CmsContentBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<CmsContent> lqw = buildQueryWrapper(bo);
Page<CmsContentVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(page);
}
@Override
public CmsContentVo selectCmsContentById(Long contentId) {
return baseMapper.selectVoById(contentId);
}
@Override
public List<CmsContentVo> selectCmsContentList(CmsContentBo bo) {
LambdaQueryWrapper<CmsContent> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<CmsContent> buildQueryWrapper(CmsContentBo bo) {
LambdaQueryWrapper<CmsContent> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getTitle()), CmsContent::getTitle, bo.getTitle());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CmsContent::getStatus, bo.getStatus());
lqw.eq(bo.getPublishTime() != null, CmsContent::getPublishTime, bo.getPublishTime());
lqw.orderByAsc(CmsContent::getSortOrder);
lqw.orderByDesc(CmsContent::getCreateTime);
return lqw;
}
@Override
public int insertCmsContent(CmsContentBo bo) {
CmsContent content = MapstructUtils.convert(bo, CmsContent.class);
return baseMapper.insert(content);
}
@Override
public int updateCmsContent(CmsContentBo bo) {
CmsContent content = MapstructUtils.convert(bo, CmsContent.class);
return baseMapper.updateById(content);
}
@Override
public int deleteCmsContentById(Long contentId) {
return baseMapper.deleteById(contentId);
}
@Override
public int deleteCmsContentByIds(Long[] contentIds) {
return baseMapper.deleteByIds(Arrays.asList(contentIds));
}
}

View File

@@ -0,0 +1,175 @@
package org.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.exception.ServiceException;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.system.domain.SysUrl;
import org.ruoyi.system.domain.bo.SysUrlBo;
import org.ruoyi.system.domain.vo.SysUrlShortcutVo;
import org.ruoyi.system.domain.vo.SysUrlVo;
import org.ruoyi.system.mapper.SysUrlMapper;
import org.ruoyi.system.service.ISysUrlService;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
/**
* URL 管理 服务层实现
*
* @author ruoyi
*/
@RequiredArgsConstructor
@Service
public class SysUrlServiceImpl implements ISysUrlService {
/**
* 状态:正常
*/
private static final String STATUS_NORMAL = "0";
private final SysUrlMapper baseMapper;
/**
* 分页查询 URL 列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return URL 分页列表
*/
@Override
public TableDataInfo<SysUrlVo> selectPageUrlList(SysUrlBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysUrl> lqw = buildQueryWrapper(bo);
Page<SysUrlVo> page = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(page);
}
/**
* 根据 ID 查询 URL 详情
*
* @param urlId 链接ID
* @return URL 信息
*/
@Override
public SysUrlVo selectUrlById(Long urlId) {
SysUrlVo vo = baseMapper.selectVoById(urlId);
if (vo == null) {
throw new ServiceException("链接不存在或已被删除");
}
return vo;
}
/**
* 新增 URL
*
* @param bo URL 信息
* @return 结果
*/
@Override
public int insertUrl(SysUrlBo bo) {
validateUrl(bo.getUrl());
SysUrl url = MapstructUtils.convert(bo, SysUrl.class);
if (url.getSortOrder() == null) {
url.setSortOrder(0);
}
if (StringUtils.isBlank(url.getStatus())) {
url.setStatus(STATUS_NORMAL);
}
return baseMapper.insert(url);
}
/**
* 修改 URL
*
* @param bo URL 信息
* @return 结果
*/
@Override
public int updateUrl(SysUrlBo bo) {
validateUrl(bo.getUrl());
if (bo.getUrlId() == null || baseMapper.selectById(bo.getUrlId()) == null) {
throw new ServiceException("链接不存在或已被删除");
}
SysUrl url = MapstructUtils.convert(bo, SysUrl.class);
return baseMapper.updateById(url);
}
/**
* 批量删除 URL
*
* @param urlIds 需要删除的链接ID
* @return 结果
*/
@Override
public int deleteUrlByIds(Long[] urlIds) {
return baseMapper.deleteByIds(Arrays.asList(urlIds));
}
/**
* 查询当前租户已启用的快捷入口列表(按排序升序)
*
* @return 快捷入口列表
*/
@Override
public List<SysUrlShortcutVo> selectShortcutList() {
LambdaQueryWrapper<SysUrl> lqw = Wrappers.<SysUrl>lambdaQuery()
.eq(SysUrl::getStatus, STATUS_NORMAL)
.orderByAsc(SysUrl::getSortOrder)
.orderByAsc(SysUrl::getUrlId);
return baseMapper.selectVoList(lqw).stream()
.map(this::toShortcut)
.toList();
}
private LambdaQueryWrapper<SysUrl> buildQueryWrapper(SysUrlBo bo) {
LambdaQueryWrapper<SysUrl> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getName()), SysUrl::getName, bo.getName());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysUrl::getStatus, bo.getStatus());
lqw.orderByAsc(SysUrl::getSortOrder);
lqw.orderByAsc(SysUrl::getUrlId);
return lqw;
}
private SysUrlShortcutVo toShortcut(SysUrlVo vo) {
SysUrlShortcutVo shortcut = new SysUrlShortcutVo();
shortcut.setUrlId(vo.getUrlId());
shortcut.setName(vo.getName());
shortcut.setUrl(vo.getUrl());
shortcut.setDescription(vo.getDescription());
shortcut.setSortOrder(vo.getSortOrder());
return shortcut;
}
/**
* 校验 URL 地址是否合法:仅允许 http/https 协议,且 host 必须存在且有效
*
* @param url 待校验的 URL
*/
private void validateUrl(String url) {
if (StringUtils.isBlank(url)) {
throw new ServiceException("链接地址不能为空");
}
URI uri;
try {
uri = new URI(url.trim());
} catch (URISyntaxException e) {
throw new ServiceException("链接地址格式不正确");
}
String scheme = uri.getScheme();
if (scheme == null || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {
throw new ServiceException("链接地址必须以 http:// 或 https:// 开头");
}
String host = uri.getHost();
if (StringUtils.isBlank(host)) {
throw new ServiceException("链接地址的主机名无效");
}
}
}

View File

@@ -1,7 +0,0 @@
<?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="org.ruoyi.system.mapper.CmsContentMapper">
</mapper>