『博客开发日记-后台』之编辑更新文章接口的实现

本文最后更新于 2026年5月29日 上午

编辑更新文章接口的实现


编辑更新文章接口的需求

根据文章id获取文章信息(必须要有id才能进行更新操作)

要可以更新缩略图或文章内容里的图片或者文件,即正确上传oss中的更新操作

支持更新文章基本信息

更新标签关联关系

如果标题发生变化,同步更新文件表里的文件来源

代码实现

下面只展示服务类里的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//编辑/更新文章
@Override
@Transactional
public ResponseResult updatePosts(Long id, UpdatePostsDto updatePostsDto)
{

//检查文章是否存在
Article oldArticle = articleService.getById(id);
if (oldArticle == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "该文章不存在!");
}

//处理文章内容中的图片
String content = updatePostsDto.getContent();
List<String> oldContentImages = new ArrayList<>();
List<String> newContentImages = new ArrayList<>();

//提取旧内容中的图片URL
if (StringUtils.hasText(oldArticle.getContent())) {
oldContentImages = ImageUrlUtils.extractImageUrls(oldArticle.getContent());
}

//处理新内容中的临时图片
if (ImageUrlUtils.containsTempImages(content)) {
content = processContentImagesAndSaveRecords(content, updatePostsDto.getTitle());
updatePostsDto.setContent(content);
}

//提取新内容中的图片URL
if (StringUtils.hasText(content)) {
newContentImages = ImageUrlUtils.extractImageUrls(content);
}

//处理缩略图
String newThumbnail = updatePostsDto.getThumbnail();
String oldThumbnail = oldArticle.getThumbnail();

if (StringUtils.hasText(newThumbnail) && newThumbnail.contains("temp/")) {
UploadFileMetaVo uploadFileMetaVo = ossFileService.moveTempToFormal(newThumbnail);
if (uploadFileMetaVo == null || !StringUtils.hasText(uploadFileMetaVo.getUrl())) {
throw new RuntimeException("缩略图保存失败,无法更新文章记录: " + newThumbnail);
}
newThumbnail = uploadFileMetaVo.getUrl();
updatePostsDto.setThumbnail(newThumbnail);
saveFileRecord(uploadFileMetaVo.getName(),
uploadFileMetaVo.getFilePath(),
uploadFileMetaVo.getUrl(),
uploadFileMetaVo.getSize(),
uploadFileMetaVo.getMimeType(),
"文章《" + updatePostsDto.getTitle() + "》中的缩略图");
}

//更新文章基本信息
Article article = BeanCopyUtils.copyBean(updatePostsDto, Article.class);
article.setId(id);
boolean updated = articleService.updateById(article);
if (!updated) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "更新文章失败");
}

//如果标题发生变化,同步更新文件表里的文件来源
if (StringUtils.hasText(oldArticle.getTitle()) && StringUtils.hasText(updatePostsDto.getTitle())
&& !oldArticle.getTitle().equals(updatePostsDto.getTitle())) {
syncFileSourceByTitle(oldArticle.getTitle(), updatePostsDto.getTitle());
}

//删除不再使用的内容图片
for (String oldImageUrl : oldContentImages) {
if (!newContentImages.contains(oldImageUrl)) {
ossFileService.deleteFileByUrl(oldImageUrl);
deleteFileRecordByUrl(oldImageUrl);
log.info("删除旧的内容图片并同步文件表: {}", oldImageUrl);
}
}

//如果缩略图发生变化,删除旧缩略图
if (StringUtils.hasText(oldThumbnail) &&
StringUtils.hasText(newThumbnail) &&
!oldThumbnail.equals(newThumbnail)) {
ossFileService.deleteFileByUrl(oldThumbnail);
deleteFileRecordByUrl(oldThumbnail);
log.info("删除旧的缩略图并同步文件表: {}", oldThumbnail);
}

//更新标签关联关系
//先删除旧的关联关系
LambdaQueryWrapper<ArticleTag> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(ArticleTag::getArticleId, id);
articleTagMapper.delete(deleteWrapper);

//添加新的关联关系
if (updatePostsDto.getTags() != null && !updatePostsDto.getTags().isEmpty()) {
List<ArticleTag> articleTags = updatePostsDto.getTags().stream()
.map(tagId -> new ArticleTag(article.getId(), tagId))
.collect(Collectors.toList());
articleTagService.saveBatch(articleTags);
}

return ResponseResult.okResult();
}
//处理文章内容中的图片并将其写入文件表
private String processContentImagesAndSaveRecords(String content, String postTitle)
{
List<String> tempImageUrls = ImageUrlUtils.extractTempImageUrls(content);
String result = content;

// 因为同一篇文章里可能重复引用同一张临时图片,所一这里先去重,避免重复转正和重复删除临时文件
List<String> distinctTempImageUrls = tempImageUrls.stream().distinct().collect(Collectors.toList());

for (String tempUrl : distinctTempImageUrls) {
UploadFileMetaVo uploadFileMetaVo = ossFileService.moveTempToFormal(tempUrl);

//判断图片是否保存(从oss中的 临时 > 正式)成功
if (uploadFileMetaVo == null || !StringUtils.hasText(uploadFileMetaVo.getUrl())) {
throw new RuntimeException("图片保存失败,无法保存文章内容中的图片记录: " + tempUrl);
}

result = ImageUrlUtils.replaceImageUrl(result, tempUrl, uploadFileMetaVo.getUrl());
if (StringUtils.hasText(uploadFileMetaVo.getFilePath())) {
String fileSource = "文章《" + postTitle + "》中的内容图片";
saveFileRecord(uploadFileMetaVo.getName(),
uploadFileMetaVo.getFilePath(),
uploadFileMetaVo.getUrl(),
uploadFileMetaVo.getSize(),
uploadFileMetaVo.getMimeType(),
fileSource);
}
}

return result;
}

//将元数据写入文件表
private void saveFileRecord(String fileName, String filePath, String url, Long size, String mimeType, String fileSource)
{
SysFile sysFile = new SysFile();
sysFile.setName(fileName);
sysFile.setFilePath(filePath);
sysFile.setUrl(url);
sysFile.setSize(size);
sysFile.setMimeType(mimeType);
sysFile.setFileSource(fileSource);
sysFileService.save(sysFile);
}

//标题变更时同步更新文件来源
private void syncFileSourceByTitle(String oldTitle, String newTitle)
{
String oldPostPrefix = "文章《" + oldTitle + "》";
String newPostPrefix = "文章《" + newTitle + "》";

List<SysFile> fileList = sysFileService.list(new LambdaQueryWrapper<SysFile>()
.like(SysFile::getFileSource, oldPostPrefix));

if (fileList.isEmpty()) {
return;
}

for (SysFile sysFile : fileList) {
String fileSource = sysFile.getFileSource();
if (StringUtils.hasText(fileSource)) {
sysFile.setFileSource(fileSource.replace(oldPostPrefix, newPostPrefix));
}
}

sysFileService.updateBatchById(fileList);
}

//处理删除文件
private void deleteFileRecordByUrl(String url)
{
if (!StringUtils.hasText(url)) {
return;
}

LambdaQueryWrapper<SysFile> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysFile::getUrl, url);
sysFileService.remove(queryWrapper);
}

新建 UpdatePostsDto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 更新文章请求DTO
*/
@Data
@ApiModel(description = "更新文章请求对象")
public class UpdatePostsDto {

@ApiModelProperty(value = "文章ID", required = true, example = "1")
private Long id;

@ApiModelProperty(value = "文章标题", required = true, example = "文章标题")
private String title;

@ApiModelProperty(value = "文章内容(HTML格式)", required = true)
private String content;

@ApiModelProperty(value = "文章摘要", example = "文章摘要")
private String summary;

@ApiModelProperty(value = "缩略图URL", example = "https://example.com/image.jpg")
private String thumbnail;

@ApiModelProperty(value = "分类ID", required = true, example = "1")
private Long categoryId;

@ApiModelProperty(value = "是否置顶(0-否,1-是)", example = "0")
private String isTop;

@ApiModelProperty(value = "状态(0-已发布,1-草稿)", example = "0")
private String status;

@ApiModelProperty(value = "是否允许评论(0-否,1-是)", example = "1")
private String isComment;

@ApiModelProperty(value = "标签ID列表")
private List<Long> tags;
}



PS:该系列只做为作者学习开发项目做的笔记用

不一定符合读者来学习,仅供参考


预告

后续会记录博客的开发过程

每次学习会做一份笔记来进行发表

“一花一世界,一叶一菩提”


版权所有 © 2026 云梦泽
欢迎访问我的个人网站:https://hgt12.github.io/


『博客开发日记-后台』之编辑更新文章接口的实现
http://example.com/2026/04/13/『博客开发日记-后台』之编辑更新文章接口的实现/
作者
云梦泽
发布于
2026年4月13日
更新于
2026年5月29日
许可协议