feat(app-article, app-article-category, sys-post): 添加文章和分类拖拽排序功能

- 在 AppArticleController 和 AppArticleCategoryController 中新增排序接口
- 在 AppArticleService 和 AppArticleCategoryService 中添加排序方法
- 在 SysPostController 和 SysPostService 中实现岗位排序功能
- 所有排序功能支持按传入 ID 顺序调整,保留未选中记录位置

(cherry picked from commit 0ba00ddcaba9a042bf082c10230042ba0ecf84d2)
This commit is contained in:
PIGCLOUD committed 2026-11-03 14:54:51 +08:00
1 parent d927a5b984
commit a177f3073a
9 files changed
+301 -93

No files matched your search

@@ -51,6 +51,7 @@ public class AppArticleCategoryController {
LambdaQueryWrapper<AppArticleCategoryEntity> wrapper = Wrappers.lambdaQuery();
wrapper.like(StrUtil.isNotBlank(appArticleCategory.getName()), AppArticleCategoryEntity::getName,
appArticleCategory.getName());
wrapper.orderByAsc(AppArticleCategoryEntity::getSort, AppArticleCategoryEntity::getId);
return R.ok(appArticleCategoryService.page(page, wrapper));
}
@@ -74,7 +75,8 @@ public class AppArticleCategoryController {
@Inner(value = false)
@GetMapping("/list")
public R list() {
return R.ok(appArticleCategoryService.list());
return R.ok(appArticleCategoryService.list(Wrappers.<AppArticleCategoryEntity>lambdaQuery()
.orderByAsc(AppArticleCategoryEntity::getSort, AppArticleCategoryEntity::getId)));
}
/**
@@ -103,6 +105,24 @@ public class AppArticleCategoryController {
return R.ok(appArticleCategoryService.updateById(appArticleCategory));
}
/**
* 按传入 ID 顺序调整文章分类,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的文章分类 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @return 排序保存成功的响应;校验或更新失败时抛出异常,由统一异常处理器转换响应
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
@Operation(summary = "拖拽排序", description = "按当前列表顺序保存排序")
@SysLog("文章分类排序")
@PutMapping("/sort")
@HasPermission("app_appArticleCategory_edit")
public R sort(@RequestBody List<Long> ids) {
appArticleCategoryService.sort(ids);
return R.ok();
}
/**
* 通过id删除文章分类表
* @param ids id列表
@@ -36,13 +36,12 @@ public class AppArticleController {
private final AppArticleService appArticleService;
/**
* 分页查询文章资讯。
* <p>
* 返回文章列表时会关联分类名称,供 App 资讯列表和后台管理复用。
*
* 分页查询文章资讯。
* <p>
* 返回文章列表时会关联分类名称,供 App 资讯列表和后台管理复用。
* @param page 分页对象
* @param appArticle 文章资讯
* @return 文章分页结果
* @return 文章分页结果
*/
@Operation(summary = "分页查询", description = "分页查询")
@Inner(value = false)
@@ -52,13 +51,12 @@ public class AppArticleController {
}
/**
* 查询文章详情。
* <p>
* 会递增访问次数;如果传入 userId,则额外返回该用户是否收藏当前文章。
*
* @param id 文章 ID
* @param userId 用户 ID,可为空
* @return 文章详情
* 查询文章详情。
* <p>
* 会递增访问次数;如果传入 userId,则额外返回该用户是否收藏当前文章。
* @param id 文章 ID
* @param userId 用户 ID,可为空
* @return 文章详情
*/
@Inner(value = false)
@Operation(summary = "通过id查询", description = "通过id查询")
@@ -93,6 +91,24 @@ public class AppArticleController {
return R.ok(appArticleService.updateById(appArticle));
}
/**
* 按传入 ID 顺序调整文章,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的文章 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @return 排序保存成功的响应;校验或更新失败时抛出异常,由统一异常处理器转换响应
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
@Operation(summary = "拖拽排序", description = "按当前列表顺序保存排序")
@SysLog("文章资讯排序")
@PutMapping("/sort")
@HasPermission("app_appArticle_edit")
public R sort(@RequestBody List<Long> ids) {
appArticleService.sort(ids);
return R.ok();
}
/**
* 通过id删除文章资讯
* @param ids id列表
@@ -3,9 +3,21 @@ package com.pig4cloud.pigx.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pig4cloud.pigx.app.api.entity.AppArticleCategoryEntity;
import java.util.List;
/**
* 文章分类基础服务。
*/
public interface AppArticleCategoryService extends IService<AppArticleCategoryEntity> {
/**
* 按传入 ID 顺序调整文章分类,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的文章分类 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
void sort(List<Long> ids);
}
@@ -4,8 +4,23 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pig4cloud.pigx.app.api.entity.AppArticleEntity;
import java.util.List;
/**
* 文章查询与排序服务,供管理端和 App 文章接口调用。
*/
public interface AppArticleService extends IService<AppArticleEntity> {
/**
* 按传入 ID 顺序调整文章,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的文章 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
void sort(List<Long> ids);
/**
* 获取文章并使阅读数+1
* @param id id
@@ -1,10 +1,18 @@
package com.pig4cloud.pigx.app.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.app.api.entity.AppArticleCategoryEntity;
import com.pig4cloud.pigx.app.mapper.AppArticleCategoryMapper;
import com.pig4cloud.pigx.app.service.AppArticleCategoryService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 文章分类表
@@ -16,4 +24,37 @@ import org.springframework.stereotype.Service;
public class AppArticleCategoryServiceImpl extends ServiceImpl<AppArticleCategoryMapper, AppArticleCategoryEntity>
implements AppArticleCategoryService {
/**
* 按传入 ID 顺序调整文章分类,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的文章分类 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void sort(List<Long> ids) {
Assert.notEmpty(ids, "排序记录不能为空");
Set<Long> selected = new HashSet<>(ids);
Assert.isTrue(!selected.contains(null) && selected.size() == ids.size(), "排序记录无效");
// 锁定统一顺序,避免重复排序值和分页边界导致拖动后顺序丢失。
List<AppArticleCategoryEntity> records = list(Wrappers.<AppArticleCategoryEntity>lambdaQuery()
.orderByAsc(AppArticleCategoryEntity::getSort, AppArticleCategoryEntity::getId)
.last("FOR UPDATE"));
long matched = records.stream().filter(row -> selected.contains(row.getId())).count();
Assert.isTrue(matched == ids.size(), "记录已变更,请刷新后重试");
List<AppArticleCategoryEntity> updates = new ArrayList<>();
int next = 0;
for (int i = 0; i < records.size(); i++) {
AppArticleCategoryEntity current = records.get(i);
Long id = selected.contains(current.getId()) ? ids.get(next++) : current.getId();
AppArticleCategoryEntity update = new AppArticleCategoryEntity();
update.setId(id);
update.setSort(i + 1);
updates.add(update);
}
Assert.state(updateBatchById(updates), "排序保存失败");
}
}
@@ -13,6 +13,13 @@ import com.pig4cloud.pigx.app.mapper.AppArticleMapper;
import com.pig4cloud.pigx.app.service.AppArticleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Objects;
@@ -32,19 +39,17 @@ public class AppArticleServiceImpl extends ServiceImpl<AppArticleMapper, AppArti
private final AppArticleCollectMapper collectMapper;
/**
* 获取文章详情,并尝试将阅读数加 1。
* <p>
* 如果传入用户 ID,会额外查询该用户是否已收藏当前文章。
* 阅读数更新使用当前 visit 值作为条件,降低并发覆盖风险。
*
* @param id 文章 ID
* @param userId 当前用户 ID,可为空
* @return 文章详情
* 获取文章详情,并尝试将阅读数加 1。
* <p>
* 如果传入用户 ID,会额外查询该用户是否已收藏当前文章。 阅读数更新使用当前 visit 值作为条件,降低并发覆盖风险。
* @param id 文章 ID
* @param userId 当前用户 ID,可为空
* @return 文章详情
*/
@Override
public AppArticleEntity getArticleAndIncrById(Long id, Long userId) {
AppArticleEntity appArticleEntity = baseMapper.selectById(id);
// 登录用户访问详情时,补充当前用户是否已收藏该文章。
// 登录用户访问详情时,补充当前用户是否已收藏该文章。
if (Objects.nonNull(userId)) {
boolean exists = collectMapper.exists(Wrappers.<AppArticleCollectEntity>lambdaQuery()//
.eq(AppArticleCollectEntity::getArticleId, appArticleEntity.getId())//
@@ -54,7 +59,7 @@ public class AppArticleServiceImpl extends ServiceImpl<AppArticleMapper, AppArti
Integer nowVisit = appArticleEntity.getVisit();
appArticleEntity.setVisit(nowVisit + 1);
// 使用旧 visit 值作为更新条件,避免并发访问时直接覆盖最新阅读数。
// 使用旧 visit 值作为更新条件,避免并发访问时直接覆盖最新阅读数。
baseMapper.update(appArticleEntity,
Wrappers.<AppArticleEntity>lambdaQuery()
.eq(AppArticleEntity::getId, id)
@@ -63,11 +68,10 @@ public class AppArticleServiceImpl extends ServiceImpl<AppArticleMapper, AppArti
}
/**
* 分页查询文章列表,并关联分类名称。
*
* 分页查询文章列表,并关联分类名称。
* @param page 分页参数
* @param appArticle 文章查询条件
* @return 文章分页结果,包含分类名称 cname
* @return 文章分页结果,包含分类名称 cname
*/
@Override
public Page pageAndCname(Page page, AppArticleEntity appArticle) {
@@ -77,8 +81,42 @@ public class AppArticleServiceImpl extends ServiceImpl<AppArticleMapper, AppArti
.leftJoin(AppArticleCategoryEntity.class, AppArticleCategoryEntity::getId, AppArticleEntity::getCid)
.like(StrUtil.isNotBlank(appArticle.getAuthor()), AppArticleEntity::getAuthor, appArticle.getAuthor())
.like(StrUtil.isNotBlank(appArticle.getTitle()), AppArticleEntity::getTitle, appArticle.getTitle())
.eq(Objects.nonNull(appArticle.getCid()), AppArticleEntity::getCid, appArticle.getCid());
.eq(Objects.nonNull(appArticle.getCid()), AppArticleEntity::getCid, appArticle.getCid())
.orderByAsc(AppArticleEntity::getSort, AppArticleEntity::getId);
return this.page(page, wrapper);
}
/**
* 按传入 ID 顺序调整文章,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的文章 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void sort(List<Long> ids) {
Assert.notEmpty(ids, "排序记录不能为空");
Set<Long> selected = new HashSet<>(ids);
Assert.isTrue(!selected.contains(null) && selected.size() == ids.size(), "排序记录无效");
// 锁定统一顺序,避免重复排序值和分页边界导致拖动后顺序丢失。
List<AppArticleEntity> records = list(Wrappers.<AppArticleEntity>lambdaQuery()
.orderByAsc(AppArticleEntity::getSort, AppArticleEntity::getId)
.last("FOR UPDATE"));
long matched = records.stream().filter(row -> selected.contains(row.getId())).count();
Assert.isTrue(matched == ids.size(), "记录已变更,请刷新后重试");
List<AppArticleEntity> updates = new ArrayList<>();
int next = 0;
for (int i = 0; i < records.size(); i++) {
AppArticleEntity current = records.get(i);
Long id = selected.contains(current.getId()) ? ids.get(next++) : current.getId();
AppArticleEntity update = new AppArticleEntity();
update.setId(id);
update.setSort(i + 1);
updates.add(update);
}
Assert.state(updateBatchById(updates), "排序保存失败");
}
}
@@ -61,7 +61,8 @@ public class SysPostController {
*/
@GetMapping("/list")
public R<List<SysPost>> listPosts() {
return R.ok(sysPostService.list(Wrappers.emptyWrapper()));
return R.ok(sysPostService
.list(Wrappers.<SysPost>lambdaQuery().orderByAsc(SysPost::getPostSort, SysPost::getPostId)));
}
/**
@@ -74,8 +75,10 @@ public class SysPostController {
@GetMapping("/page")
@HasPermission("sys_post_view")
public R getSysPostPage(@ParameterObject Page page, @ParameterObject SysPost sysPost) {
return R.ok(sysPostService.page(page, Wrappers.<SysPost>lambdaQuery()
.like(StrUtil.isNotBlank(sysPost.getPostName()), SysPost::getPostName, sysPost.getPostName())));
return R.ok(sysPostService.page(page,
Wrappers.<SysPost>lambdaQuery()
.like(StrUtil.isNotBlank(sysPost.getPostName()), SysPost::getPostName, sysPost.getPostName())
.orderByAsc(SysPost::getPostSort, SysPost::getPostId)));
}
/**
@@ -128,6 +131,24 @@ public class SysPostController {
return R.ok(sysPostService.updateById(sysPost));
}
/**
* 按传入 ID 顺序调整岗位,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的岗位 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @return 排序保存成功的响应;校验或更新失败时抛出异常,由统一异常处理器转换响应
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
@Operation(summary = "岗位拖拽排序", description = "按当前列表顺序保存岗位排序")
@SysLog("岗位排序")
@PutMapping("/sort")
@HasPermission("sys_post_edit")
public R sort(@RequestBody List<Long> ids) {
sysPostService.sort(ids);
return R.ok();
}
/**
* 通过id删除岗位信息表
* @param ids id 列表
@@ -33,6 +33,16 @@ import java.util.List;
*/
public interface SysPostService extends IService<SysPost> {
/**
* 按传入 ID 顺序调整岗位,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的岗位 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
void sort(List<Long> ids);
/**
* 导出excel 表格
* @return
@@ -42,8 +52,8 @@ public interface SysPostService extends IService<SysPost> {
/**
* 导入岗位
* @param excelVOList 岗位列表
* @param bindingResult 错误信息列表
* @return ok fail
* @param bindingResult 通用校验结果,其 target 持有错误信息列表
* @return 全部导入成功返回 ok;存在校验失败时返回携带错误信息列表的 failed
*/
R importPost(List<PostExcelVO> excelVOList, BindingResult bindingResult);
@@ -30,8 +30,11 @@ import com.pig4cloud.pigx.common.core.util.MsgUtils;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.excel.vo.ErrorMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -45,72 +48,104 @@ import java.util.Set;
@Service
public class SysPostServiceImpl extends ServiceImpl<SysPostMapper, SysPost> implements SysPostService {
/**
* 导入岗位
*
* @param excelVOList 岗位列表
* @param bindingResult 错误信息列表
* @return ok fail
*/
@Override
public R importPost(List<PostExcelVO> excelVOList, BindingResult bindingResult) {
// 通用校验获取失败的数据
List<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();
/**
* 导入岗位
* @param excelVOList 岗位列表
* @param bindingResult 通用校验结果,其 target 持有错误信息列表
* @return 全部导入成功返回 ok;存在校验失败时返回携带错误信息列表的 failed
*/
@Override
public R importPost(List<PostExcelVO> excelVOList, BindingResult bindingResult) {
// 通用校验获取失败的数据
List<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();
// 个性化校验逻辑
List<SysPost> postList = this.list();
// 个性化校验逻辑
List<SysPost> postList = this.list();
// 执行数据插入操作 组装 PostDto
for (PostExcelVO excel : excelVOList) {
Set<String> errorMsg = new HashSet<>();
// 检验岗位名称或者岗位编码是否存在
boolean existPost = postList.stream()
.anyMatch(post -> excel.getPostName().equals(post.getPostName())
|| excel.getPostCode().equals(post.getPostCode()));
// 执行数据插入操作 组装 PostDto
for (PostExcelVO excel : excelVOList) {
Set<String> errorMsg = new HashSet<>();
// 检验岗位名称或者岗位编码是否存在
boolean existPost = postList.stream()
.anyMatch(post -> excel.getPostName().equals(post.getPostName())
|| excel.getPostCode().equals(post.getPostCode()));
if (existPost) {
errorMsg.add(MsgUtils.getMessage(UpmsErrorCodes.SYS_POST_NAMEORCODE_EXISTING, excel.getPostName(),
excel.getPostCode()));
}
if (existPost) {
errorMsg.add(MsgUtils.getMessage(UpmsErrorCodes.SYS_POST_NAMEORCODE_EXISTING, excel.getPostName(),
excel.getPostCode()));
}
// 数据合法情况
if (CollUtil.isEmpty(errorMsg)) {
insertExcelPost(excel);
} else {
// 数据不合法
errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg));
}
}
if (CollUtil.isNotEmpty(errorMessageList)) {
return R.failed(errorMessageList);
}
return R.ok();
}
// 数据合法情况
if (CollUtil.isEmpty(errorMsg)) {
insertExcelPost(excel);
}
else {
// 数据不合法
errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg));
}
}
if (CollUtil.isNotEmpty(errorMessageList)) {
return R.failed(errorMessageList);
}
return R.ok();
}
/**
* 导出excel 表格
*
* @return
*/
@Override
public List<PostExcelVO> listPost(SysPost query, Long[] ids) {
List<SysPost> postList = this
.list(Wrappers.lambdaQuery(query).in(ArrayUtil.isNotEmpty(ids), SysPost::getPostId, CollUtil.toList(ids)));
// 转换成execl 对象输出
return postList.stream().map(post -> {
PostExcelVO postExcelVO = new PostExcelVO();
BeanUtil.copyProperties(post, postExcelVO);
return postExcelVO;
}).toList();
}
/**
* 导出excel 表格
* @return
*/
@Override
public List<PostExcelVO> listPost(SysPost query, Long[] ids) {
List<SysPost> postList = this
.list(Wrappers.lambdaQuery(query).in(ArrayUtil.isNotEmpty(ids), SysPost::getPostId, CollUtil.toList(ids)));
// 转换成execl 对象输出
return postList.stream().map(post -> {
PostExcelVO postExcelVO = new PostExcelVO();
BeanUtil.copyProperties(post, postExcelVO);
return postExcelVO;
}).toList();
}
/**
* 插入excel Post
*/
private void insertExcelPost(PostExcelVO excel) {
SysPost sysPost = new SysPost();
BeanUtil.copyProperties(excel, sysPost);
this.save(sysPost);
}
/**
* 插入excel Post
*/
private void insertExcelPost(PostExcelVO excel) {
SysPost sysPost = new SysPost();
BeanUtil.copyProperties(excel, sysPost);
this.save(sysPost);
}
/**
* 按传入 ID 顺序调整岗位,保留未选中记录在排序序列中的位置。
* <p>
* 以当前租户内未删除记录的排序值、主键升序为基准,仅重排选中记录占据的位置, 再将全部记录的排序值从 1 起连续重编号。
* @param ids 按目标顺序排列的岗位 ID;不可为 null 或空,不可包含 null、重复或不可用的 ID
* @throws IllegalArgumentException ID 列表为空、包含 null 或重复值,或指定记录已删除、不属于当前租户 或不存在
* @throws IllegalStateException 批量更新返回失败
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void sort(List<Long> ids) {
Assert.notEmpty(ids, "排序记录不能为空");
Set<Long> selected = new HashSet<>(ids);
Assert.isTrue(!selected.contains(null) && selected.size() == ids.size(), "排序记录无效");
// 锁定统一顺序,避免重复排序值和分页边界导致拖动后顺序丢失。
List<SysPost> records = list(Wrappers.<SysPost>lambdaQuery()
.orderByAsc(SysPost::getPostSort, SysPost::getPostId)
.last("FOR UPDATE"));
long matched = records.stream().filter(row -> selected.contains(row.getPostId())).count();
Assert.isTrue(matched == ids.size(), "记录已变更,请刷新后重试");
List<SysPost> updates = new ArrayList<>();
int next = 0;
for (int i = 0; i < records.size(); i++) {
SysPost current = records.get(i);
Long id = selected.contains(current.getPostId()) ? ids.get(next++) : current.getPostId();
SysPost update = new SysPost();
update.setPostId(id);
update.setPostSort(i + 1);
updates.add(update);
}
Assert.state(updateBatchById(updates), "排序保存失败");
}
}