哈哈,我又来了,今天还是继续来撸这个《SpringBoot日记本系统》!
分页查询
上一讲,我们已经完成了日记的新增,新增完毕后自动跳转到首页。
首页的日记查询是分页的,用的layUI的layPage插件。
mybatis-plus自带一个分页组件,后来我发现之前用的版本有点低,可能还得配合pageHelper来做分页,于是我提升了版本。(3.4.1版本是可以的,亲测可行!)
<!-- mybatis plus 支持 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
配置分页插件
@Configuration
//mybatis扫包
@MapperScan("com.rabbit.diary.dao")
public class MybatisPlusConfig {
//分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
开始编写查询日记的代码,controller
@RequestMapping("/select")
@SaCheckLogin
public Page<TblSynBlog> select(@RequestBody TblSynBlog blog,
@RequestParam Integer pageIndex, @RequestParam Integer size){
Page<TblSynBlog> pageBean = new Page<>(pageIndex,size);
Page<TblSynBlog> page = blogService.selectPage(pageBean,blog);
return page;
}
我们接收分页参数;pageIndex和size,还有blog对象(这个用于后面根据某些字段来查询,比如blogType,title等,先预留着)
blogService添加selectPage方法
Page<TblSynBlog> selectPage(Page<TblSynBlog> pageBean, TblSynBlog blog);
具体实现
@Override
public Page<TblSynBlog> selectPage(Page<TblSynBlog> pageBean, TblSynBlog blog) {
QueryWrapper<TblSynBlog> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("update_date");
//只允许查看自己发布的日记
queryWrapper.eq("user_id", StpUtil.getLoginId());
queryWrapper.eq("is_delete", "0");
return blogMapper.selectPage(pageBean,queryWrapper);
}
我们规定,用户只能看到自己发布的日记,所以添加user_id的匹配,is_delete是逻辑删除的字段,我们只查询未删除的。还有就是,要根据最后更新日期倒叙排序。
前端代码
list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<c:set var="basePath" value="${pageContext.request.contextPath}"></c:set>
<div id="dlist" class="layui-card dbox" style="border-right: 2px solid #eaeaea;">
<div class="layui-card-header"><b><i class="layui-icon layui-icon-list" style="color: #000;"></i></b>日记列表</div>
<div class="layui-card-body">
<ul class="dlist">
<li v-for="item in datalist"><a :href="'${basePath}/diary/' + item.id + '.html'">{{item.title}}</a></li>
</ul>
<div id="pageCode"></div>
</div>
</div>
<script>
var dlist = new Vue({
el:"#dlist",
data:{
datalist:[],
pageIndex:1,
size:10,
total:0
},
methods:{
queryBlogs(){
axios.post('${basePath}/blog/select?pageIndex='+this.pageIndex+'&size=' + this.size,{}).then(r =>{
if(r.data.code != '0000'){
layer.msg(r.data.message,{icon:2});
return;
}
this.datalist = r.data.data.records;
this.total = r.data.data.total;
laypage.render({
elem: 'pageCode' //注意,这里的 pageCode 是 ID,不用加 # 号
,count: dlist.total //数据总数,从服务端得到
,limit:dlist.size
,curr:r.data.data.current //必须加,不然死循环
,jump: function(obj,first) {
let curr = obj.curr; //当前页码
dlist.pageIndex = curr;//更新data的页码
console.log(curr)
if(!first) dlist.queryBlogs();//重新加载
}
});
});
}
},
created(){
this.queryBlogs();
}
});
</script>
配合Vue,我们每次用axios查询后都要重新渲染layPage,注意,一定要添加curr(当前页),不然会死循环的。
效果:
日记查看
每一篇日记都被一个a标签包裹着
<li v-for="item in datalist"><a :href="'${basePath}/diary/' + item.id + '.html'">{{item.title}}</a></li>
点击后会跳转到 /diary/{id}.html ,这是PageController里面的一个方法。
@RequestMapping("diary/{id}.html")
@SaCheckLogin
public ModelAndView getDiary(@PathVariable Long id, ModelAndView mav){
TblSynBlog tblSynBlog = blogService.selectOne(id);
mav.addObject("blog",tblSynBlog);
mav.setViewName("detail");
return mav;
}
我们根据id调用selectOne方法,去获取具体的日记对象,添加到视图ModelAndView,返回。
方法实现:
@Override
public TblSynBlog selectOne(Long id) {
/**
* 根据id和用户ID查询日志
*/
QueryWrapper<TblSynBlog> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id",id);
queryWrapper.eq("user_id", StpUtil.getLoginId());
return blogMapper.selectOne(queryWrapper);
}
效果:
detail.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<c:set var="basePath" value="${pageContext.request.contextPath}"></c:set>
<html>
<head>
<title>日记本详情页</title>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<script src="${basePath}/layui/layui.all.js" charset="utf-8"></script>
<script src="${basePath}/js/vue.js"></script>
<script src="${basePath}/js/axios.min.js"></script>
<link rel="stylesheet" href="${basePath}/layui/css/layui.css" media="all">
<style>
.title {margin: 20px;text-align: center;}
.title2{text-align: center;color: #666;}
.content {text-indent: 2em;margin-top: 50px;}
.tools{margin-top: 100px;text-align: right;}
</style>
</head>
<body>
<div id="app" class="layui-container">
<div class="layui-row layui-col-space10">
<div class="layui-row">
<div class="layui-col-md12">
<jsp:include page="common/header.jsp"></jsp:include>
</div>
<div class="layui-col-md9">
<div class="layui-card dbox" style="border-right: 2px solid #eaeaea;height: 90%;padding: 16px;">
<h1 class="title">${blog.title}</h1>
<div class="title2">发布时间:${blog.createDate} 日志类别:${blog.blogType}</div>
<div class="content">${blog.content}</div>
<div class="tools">
<a href="${basePath}/diary/add.html?id=${blog.id}"><button type="button" class="layui-btn layui-btn-normal">修改</button></a>
<button onclick="del()" type="button" class="layui-btn layui-btn-danger">删除</button>
<a href="${basePath}/"><button type="button" class="layui-btn layui-btn-warm">返回</button></a>
</div>
</div>
</div>
<div class="layui-col-md3" style="">
<jsp:include page="common/sider.jsp"></jsp:include>
</div>
</div>
</div>
<jsp:include page="common/footer.jsp"></jsp:include>
</div>
<script>
function del(){
if(confirm('您确定要删除这个日记吗?')){
var index = layer.load(1); //添加laoding,0-2两种方式
axios.post('${basePath}/blog/delete?id=${id}',{}).then(r =>{
layer.close(index); //返回数据关闭loading
if(r.data.code != '0000'){
layer.msg(r.data.message,{icon:2});
return;
}
layer.msg('日记删除成功!',{icon:1});
setTimeout(()=>{location.href="/"},500)
}).catch(error => {
layer.msg(error.response.status,{icon:2});
layer.close(index); //返回数据关闭loading
})
}
}
</script>
</body>
</html>
修改日记
点击修改按钮,会触发a链接跳转
中间经过Controller
@RequestMapping("diary/add.html")
@SaCheckLogin
public ModelAndView addDiary(@RequestParam(required=false) Long id, ModelAndView mav){
mav.setViewName("diary/add");
mav.addObject("id",id);
return mav;
}
意思就是把id传到add.jsp
为什么我不专门写一个修改页面呢?
因为一般在企业中,新增和修改我们都是共用的,只需要根据有没有id,来判断是新增还修改。
在add.jsp中,我们就做了如下判断
form.on('submit(demo1)', function(data){
data = {...data.field ,...{content:layedit.getContent(editIndex)}}
if(data.content.length < 10){
layer.msg('内容至少10个字符!',{icon:2});
return false;
}
var index = layer.load(1); //添加laoding,0-2两种方式
//如果有id,就把id传过去
if("${id}"){
data.id = "${id}";
}
axios.post('${basePath}/blog/add',data).then(r =>{
layer.close(index); //返回数据关闭loading
if(r.data.code != '0000'){
layer.msg(r.data.message,{icon:2});
return;
}
layer.msg('日记记录成功!',{icon:1});
setTimeout(()=>{location.href="/"},500)
});
return false;
});
这是保存日记的逻辑,如果有id传过来,就把id增加到保存的参数中,交给后台去判断是新增还是修改。
当add.jsp被打开,我们需要判断有没有id过来,如果有,就填充日记的数据。
if("${id}"){
/**
* 如果id存在,说明是修改
*/
let index = layer.load(1); //添加laoding,0-2两种方式
axios.post('${basePath}/blog/get?id=${id}',{}).then(r =>{
layer.close(index); //返回数据关闭loading
if(r.data.code != '0000'){
layer.msg(r.data.message,{icon:2});
return;
}
//渲染数据
form.val('blog',r.data.data);
layedit.setContent(editIndex,r.data.data.content)
});
}else{
//默认今天的日期
fillDate(form);
}
效果:
保存日记的接口也需要做改变
/**
* 新增或者修改
* @param blog
* @return
*/
@RequestMapping("/add")
@SaCheckLogin
public Result add(@RequestBody TblSynBlog blog){
//新增
if(StrUtil.isBlankIfStr(blog.getId())){
//拼装BlogBean
blog.setId(redisServiceImpl.getIncr("BlogId")); //redis自增ID
blog.setCreateDate(DateUtil.now());
blog.setUserId(StpUtil.getLoginIdAsLong());
blog.setUpdateDate(DateUtil.now());
blogService.save(blog);
}else{
//修改
TblSynBlog tblSynBlog = blogService.selectOne(blog.getId());
BeanUtil.copyProperties(blog,tblSynBlog);
tblSynBlog.setUpdateDate(DateUtil.now());
blogService.updateById(tblSynBlog);
}
return Result.success();
}
修改的逻辑是,根据id查到数据库中的数据。这边可能会有一个BUG,我们后面再说。
测试一下修改
提交
报错了,MD,看日志
2022-04-16 13:15:46.404 WARN 12852 --- [p-nio-80-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.DataIntegrityViolationException: <EOL><EOL>### Error updating database. Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'title' at row 1<EOL><EOL>### The error may exist in com/rabbit/diary/dao/BlogMapper.java (best guess)<EOL><EOL>### The error may involve com.rabbit.diary.dao.BlogMapper.updateById-Inline<EOL><EOL>### The error occurred while setting parameters<EOL><EOL>### SQL: UPDATE tbl_syn_blog SET title=?, blog_type=?, content=?, update_date=? WHERE id=?<EOL><EOL>### Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'title' at row 1<EOL>; Data truncation: Data too long for column 'title' at row 1; nested exception is com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'title' at row 1]
哦,title字段太短了,那就加长!
alter TABLE tbl_syn_blog MODIFY title VARCHAR(100)
执行以上sql即可。
再来
删除功能
看下删除按钮
<button onclick="del()" type="button" class="layui-btn layui-btn-danger">删除</button>
del方法
function del(){
if(confirm('您确定要删除这个日记吗?')){
var index = layer.load(1); //添加laoding,0-2两种方式
axios.post('${basePath}/blog/delete?id=${id}',{}).then(r =>{
layer.close(index); //返回数据关闭loading
if(r.data.code != '0000'){
layer.msg(r.data.message,{icon:2});
return;
}
layer.msg('日记删除成功!',{icon:1});
setTimeout(()=>{location.href="/"},500)
}).catch(error => {
layer.msg(error.response.status,{icon:2});
layer.close(index); //返回数据关闭loading
})
}
}
调用后台的删除接口,把id传过去
@RequestMapping("/delete")
@SaCheckLogin
public Result delete(@RequestParam Long id ){
blogService.delete(id);
return Result.success();
}
delete方法实现
@Override
public void delete(Long id) {
blogMapper.deleteByIdLogic(id);
}
我们要做的是逻辑删除,不是真的把日记删除,后期可以考虑做个回收站功能。
deleteByIdLogic方法
@Mapper
public interface BlogMapper extends BaseMapper<TblSynBlog> {
@Update("update tbl_syn_blog set is_delete = '1' where id = #{id}")
void deleteByIdLogic(Long id);
}
测试删除
搞定!
让我们来总结一下这一节的几个要点吧:
1.mybatis-plus分页有默认的插件可以使用,但是请确保MP的版本号不能太低。
2.工作中新增和修改一般是共用一套逻辑,根据id是否存在来判断是新增还是修改。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)