微头条项目开发笔记|BUG修复 + 新闻详情页面完整开发记录
微头条项目开发笔记|BUG修复 + 新闻详情页面完整开发记录
前言
本次课完成两大块内容:修复两个线上功能性BUG(越权删除提示错误、发布新闻用户发布计数不增长);全新开发新闻详情页面,解决详情页作者名称不展示问题,完整梳理排错思路与前后端配套修改方案。
一、课次35:线上BUG定位与修复
Bug1:删除他人新闻仍弹出「删除成功」提示(前端判断逻辑缺失)
问题根源
后端删除接口已经做权限校验:非新闻发布者删除时,返回code≠200、message="只能删除自己的新闻"标准错误结果,但前端删除接口调用后未判断返回码,无论后端成功/失败都直接弹出成功提示,造成功能逻辑错乱。
排错思路
- 后端接口统一返回
Result封装结构,业务错误不会抛出异常,仅修改code与提示文案; - 后端逻辑无漏洞,问题锁定在前端删除请求的回调判断;
- 前端接收后端响应后,增加
res.code === 200分支判断,区分成功、失败场景分别弹窗。
修复要点
前端删除方法新增返回码判断:
const homeDeleteNews = async (id) => {
try {
const res = await deleteNews(id)
if(res.code===200){
ElMessage.success(res.message)
loadNews() // 刷新列表
}else{
ElMessage.error(res.message)
}
} catch {
ElMessage.error('请求失败')
}
}
修改完成后,删除不属于自己的新闻时,正常弹出「只能删除自己的新闻」提示,越权操作拦截生效。
Bug2:发布新闻后用户表news_count发布数量不自动+1
问题根源
原始发布接口仅完成新闻插入操作,缺少同步更新用户发布计数的逻辑,用户发布文章数不会累加。
后端代码改造(NewsController#addNews)
- 注入
UserService用于操作用户表; - 新闻插入成功后,使用
LambdaUpdateWrapper执行SQL自增,更新对应用户news_count = news_count + 1; - 双重判断:新闻保存、用户计数更新全部成功,才返回发布成功;任一失败则提示发布失败;
- 添加打印日志输出当前登录用户ID,方便调试校验token解析的用户身份。
@Autowired
private UserService userService;
@PostMapping("/add")
public Result<?> addNews(@RequestBody NewsAddDTO dto) {
// 1. 插入新闻
Integer userId = UserContext.getCurrentUserId();
System.out.println("当前用户ID:" + userId);
if (userId == null) return Result.error("请先登录");
News news = new News();
news.setUserId(userId);
news.setTitle(dto.getTitle());
news.setContent(dto.getContent());
news.setViewCount(0);
boolean saved = newsService.save(news);
// 2. 更新用户的 news_count + 1
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, userId)
.setSql("news_count = news_count + 1");
boolean updated = userService.update(wrapper);
if (saved && updated) {
return Result.success("发布成功");
} else {
return Result.error("发布失败");
}
}
前端配套问题排查
原始前端发布代码写死userId:1,怀疑身份传递异常;通过后端打印日志验证:用户ID由JWT拦截器解析存入UserContext,前端无需手动传递userId,写死ID仅为本地测试冗余代码,不影响线上逻辑。
验证方案
使用id≠1的测试账号登录发布新闻,查看后端控制台打印正确userId,刷新数据库user表,对应用户news_count数值自动+1,BUG修复完成。
二、课次37:新闻详情页面完整开发 + 作者名不展示BUG修复
开发目标
- 根据路由传参新闻id,请求后端
/news/{id}接口渲染新闻详情; - 展示标题、作者、发布时间、浏览量、正文;
- 权限控制:仅新闻发布者可见「编辑、删除」按钮;
- 新增返回首页、编辑、删除交互弹窗。
步骤1:新建Detail.vue详情页面完整代码
页面使用Vue3 script setup语法,集成路由参数读取、页面加载请求、登录用户鉴权、删除弹窗确认逻辑:
<!-- src/views/Detail.vue -->
<template>
<div class="detail-container">
<el-card v-loading="loading">
<template v-if="news">
<h2>{{ news.title }}</h2>
<div class="info">
<span>作者:{{ news.username || '匿名' }}</span>
<span>发布时间:{{ news.publishTime }}</span>
<span>浏览量:{{ news.viewCount }}</span>
</div>
<div class="content">{{ news.content }}</div>
<div class="actions">
<el-button @click="$router.back()">返回首页</el-button>
<el-button type="primary" @click="goToEdit" v-if="isOwner">编辑</el-button>
<el-button type="danger" @click="handleDelete" v-if="isOwner">删除</el-button>
</div>
</template>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getNewsDetail, deleteNews } from '../api/news'
import { useUserStore } from '../stores/user'
import { ElMessage, ElMessageBox } from 'element-plus'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const news = ref(null)
const loading = ref(false)
const isOwner = computed(() => {
return news.value && userStore.username && news.value.username === userStore.username
})
const loadDetail = async () => {
loading.value = true
try {
const res = await getNewsDetail(route.params.id)
if (res.code === 200) {
news.value = res.data
} else {
ElMessage.error(res.message)
}
} finally {
loading.value = false
}
}
const goToEdit = () => {
router.push(`/edit/${news.value.id}`)
}
const handleDelete = async () => {
try {
await ElMessageBox.confirm('确定删除该新闻吗?', '提示', { type: 'warning' })
const res = await deleteNews(news.value.id)
if (res.code === 200) {
ElMessage.success('删除成功')
router.push('/home')
} else {
ElMessage.error(res.message)
}
} catch {}
}
onMounted(loadDetail)
</script>
<style scoped>
.detail-container { max-width: 800px; margin: 40px auto; }
.info { color: #909399; font-size: 14px; margin: 16px 0; }
.info span { margin-right: 20px; }
.content { font-size: 16px; line-height: 1.8; margin: 20px 0; }
.actions { margin-top: 20px; }
</style>
核心功能说明:
route.params.id获取路由传递的新闻主键;computed计算属性isOwner对比登录用户名与新闻作者,控制编辑/删除按钮显示;- 删除操作增加二次确认弹窗,防止误操作;
- 请求增加loading加载状态、错误提示统一捕获。
页面BUG:详情页无法显示作者名称
问题原因
News实体类仅存储userId数字,无用户名字段;- 原始
getNewsDetail仅返回新闻表数据,未关联查询用户表获取用户名; - 前端模板绑定
authorName,后端无此字段,渲染为空。
后端修复:改造详情接口
修改getNewsDetail方法,查询新闻后根据userId关联查询用户,把用户名存入新闻实体返回给前端:
@GetMapping("/{id}")
public Result<News> getNewsDetail(@PathVariable Integer id) {
News news = newsService.getById(id);
if (news != null) {
// 增加浏览量
news.setViewCount(news.getViewCount() + 1);
newsService.updateById(news);
// 关联用户表查询作者名
User user = userService.getById(news.getUserId());
news.setUserName(user.getUsername());
return Result.success(news);
} else {
return Result.error("新闻不存在");
}
}
配套:News实体新增username属性,提供get/set方法。
前端批量替换字段
使用VSCode全局替换快捷键Ctrl+H,将页面中所有authorName批量替换为username,和后端返回字段保持一致。
验证
重启前后端,点击新闻进入详情页,作者名称正常渲染,编辑、删除按钮仅本人可见,浏览量自动累加,全部功能正常。
今日整体复盘
- BUG排查思路标准化:先区分前后端责任,后端校验正常则定位前端回调判断缺失;打印日志、数据库校验双重验证数据流转;
- 掌握MyBatis-Plus自更新语法
setSql,实现发布计数自动累加,业务数据同步更新; - 完成完整详情页开发,实现路由传参、鉴权按钮、二次确认删除、加载状态等交互;
- 学会实体类多表关联临时赋值方案,无需新建DTO即可携带关联用户信息返回前端;
- 优化前端请求健壮性:所有接口统一判断
code===200,区分业务成功/失败场景提示。


浙公网安备 33010602011771号