哈哈,我又来了,今天还是继续来撸这个《SpringBoot日记本系统》!
这一节,我们把日记分类管理的增删改查搞起来。
创建类型表:
/* Navicat MySQL Data Transfer Source Server : mysql Source Server Version : 50531 Source Host : localhost:3306 Source Database : diary Target Server Type : MYSQL Target Server Version : 50531 File Encoding : 65001 Date: 2022-04-17 13:52:56 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for sys_blog_type -- ---------------------------- DROP TABLE IF EXISTS `sys_blog_type`; CREATE TABLE `sys_blog_type` ( `id` int(11) NOT NULL AUTO_INCREMENT, `type_name` varchar(20) DEFAULT NULL, `create_date` varchar(20) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
为了方便起见,这次我们用MP的自动生成工具,添加依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.0</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version> </dependency>
编写启动类: AutoGeneratorConfig
package com.rabbit.diary.util; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.Scanner; public class AutoGeneratorConfig { /** * <p> * 读取控制台内容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static <FileOutConfig> void main(String[] args) { String projectPath = System.getProperty("user.dir"); // 全局配置 GlobalConfig gc = new GlobalConfig.Builder() .author("剽悍一小兔") .outputDir(projectPath + "/src/main/java") .openDir(false).build(); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig.Builder("jdbc:mysql://localhost:3306/diary?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","").build(); // 代码生成器 AutoGenerator mpg = new AutoGenerator(dsc); mpg.global(gc); // 包配置 PackageConfig pc = new PackageConfig.Builder() .controller("web") .entity("bean") .mapper("dao") .service("service") .serviceImpl("service.impl") .parent("com.rabbit.diary").build(); mpg.packageInfo(pc); // 5、策略配置 StrategyConfig strategy = new StrategyConfig.Builder() .addInclude(scanner("数据表名")) .addTablePrefix("") .entityBuilder() // 数据库表映射到实体的命名策略,驼峰命名法 .naming(NamingStrategy.underline_to_camel) //数据库表字段映射到实体的命名策略 .columnNaming(NamingStrategy.underline_to_camel) // lombok 模型 @Accessors(chain = true) setter链式操作 .enableLombok() .controllerBuilder() //restful api风格控制器 .enableRestStyle() //url中驼峰转连字符 .enableHyphenStyle().build(); mpg.strategy(strategy); mpg.execute(); } }
作用就是根据表名,自动生成各种文件。我没有用自定义模板,因为项目比较简单,为了最大的灵活性,感觉没必要。
运行
13:51:28.591 [main] DEBUG com.baomidou.mybatisplus.generator.AutoGenerator - ==========================准备生成文件...========================== 13:51:28.607 [main] WARN com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine - Velocity 1.x is outdated, please upgrade to 2.x or later. 13:51:29.404 [main] DEBUG com.baomidou.mybatisplus.generator.config.querys.MySqlQuery - 执行SQL:show table status WHERE 1=1 AND NAME IN ('sys_blog_type') 13:51:29.466 [main] DEBUG com.baomidou.mybatisplus.generator.config.querys.MySqlQuery - 返回记录数:1,耗时(ms):58 13:51:29.513 [main] DEBUG com.baomidou.mybatisplus.generator.config.querys.MySqlQuery - 执行SQL:show full fields from `sys_blog_type` 13:51:29.529 [main] DEBUG com.baomidou.mybatisplus.generator.config.querys.MySqlQuery - 返回记录数:4,耗时(ms):28 13:51:29.779 [main] DEBUG com.baomidou.mybatisplus.generator.AutoGenerator - ==========================文件生成完成!!!==========================
我们先把后台写好,类别这种东西,没必要分页了,直接来个搜全部的方法。
@RestController @RequestMapping("/sys-blog-type") @SaCheckLogin public class SysBlogTypeController { @Autowired ISysBlogTypeService sysBlogTypeService; @RequestMapping("/selectAll") public List<SysBlogType> selectAll(){ return sysBlogTypeService.selectAll(); } }
我在类上加了SaCheckLogin,表示跟类别有关的所有方法都需要登录。(PS:生成的@RequestMapping("//sys-blog-type")不知道为啥有两个 / ,改成一个/的)
实现类:
@Service public class SysBlogTypeServiceImpl extends ServiceImpl<SysBlogTypeMapper, SysBlogType> implements ISysBlogTypeService { @Autowired SysBlogTypeMapper sysBlogTypeMapper; @Override public List<SysBlogType> selectAll() { QueryWrapper<SysBlogType> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", StpUtil.getLoginId()); return sysBlogTypeMapper.selectList(queryWrapper); } }
SysBlogTypeMapper 生成出来没有加@Mapper,我们给他加上。
@Mapper public interface SysBlogTypeMapper extends BaseMapper<SysBlogType> { }
接下来,我们继续写和日记类别有关的后台接口。
新增或者修改
/** * 新增或者修改 * @return */ @RequestMapping("/add") @SaCheckLogin public Result add(@RequestBody SysBlogType sysBlogType){ //新增 if(StrUtil.isBlankIfStr(sysBlogType.getId())){ //拼装BlogBean sysBlogType.setCreateDate(DateUtil.now()); sysBlogType.setUserId(StpUtil.getLoginIdAsInt()); sysBlogTypeService.save(sysBlogType); }else{ //修改 SysBlogType sysBlogType2 = sysBlogTypeService.selectOne(sysBlogType.getId()); BeanUtil.copyProperties(sysBlogType,sysBlogType2); sysBlogTypeService.updateById(sysBlogType2); } return Result.success(); }
和日记的保存差不多,也是根据是否有id来判断新增还是修改。
sysBlogTypeService.save实现:
@Override public List<SysBlogType> selectAll() { QueryWrapper<SysBlogType> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", StpUtil.getLoginId()); return sysBlogTypeMapper.selectList(queryWrapper); }
根据当前登录用户,查询其所拥有的所有的类型。
前端代码:
getData(){ var index = layer.load(1); //添加laoding,0-2两种方式 axios.post('${basePath}/sys-blog-type/selectAll',{}).then(r =>{ layer.close(index); //返回数据关闭loading if(r.data.code != '0000'){ layer.msg(r.data.message,{icon:2}); return; } console.log(r.data.data); this.listData = r.data.data; }).catch(error => { layer.msg(error.response.status,{icon:2}); layer.close(index); //返回数据关闭loading }) },
用的是vue,渲染代码如下
<tr v-for="item in listData"> <td>{{item.id}}</td> <td>{{item.typeName}}</td> <td>{{item.createDate}}</td> <td> <a :href="'${basePath}/diary/blogType/add.html?id='+item.id"><button type="button" class="layui-btn layui-btn-normal layui-btn-sm">修改</button></a> <button @click="del(item.id)" type="button" class="layui-btn layui-btn-danger layui-btn-sm">删除</button> </td> </tr>
因为代码实在太多,不适合全部贴出来,所以我只贴核心的。大家可以到时候下载源码,进行比对和运行。
界面效果:
新增功能,根据新增按钮跳转到新增页面。
提交后:
修改和删除功能和日记模块差不多,只是删除的时候,我们需要加一些特定的逻辑。
@RequestMapping("/delete") @SaCheckLogin public Result delete(@RequestParam Integer id ){ //检查是否是自己创建类型 SysBlogType sysBlogType = sysBlogTypeService.selectOne(id); Integer userId = sysBlogType.getUserId(); if(!userId.equals(StpUtil.getLoginIdAsInt()) ){ throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("您无法删除他人的日记类型!")); } //检查是否有该类型的日记,如果有,则不允许删除 Integer sysBlogTypeId = sysBlogType.getId(); Page<TblSynBlog> pageBean = new Page<>(1,10); TblSynBlog blog = new TblSynBlog(); blog.setBlogType(String.valueOf(sysBlogTypeId)); Page<TblSynBlog> page = blogService.selectPage(pageBean,blog); if(page.getTotal() > 0){ throw new BizException(ExceptionCodeEnum.ERROR_PARAM.setDesc("该类别下还有"+page.getTotal()+"篇日记,不允许删除!")); } sysBlogTypeService.delete(id); return Result.success(); }
大概意思呢,就是用户只能删除自己的日记类型,而且如果已经有了该类型的日记,则不允许删除。 哦对了,我们还需要做一下日记分类的菜单选中效果。
layui.use(['jquery', 'layer'], function(){ let $ = layui.$; let pathName = window.location.pathname; switch (pathName) { case "/diary/add.html": $('.layui-nav-item').eq(2).addClass("layui-this").siblings().removeClass("layui-this") break; case "/diary/blogType.html": $('.layui-nav-item').eq(3).addClass("layui-this").siblings().removeClass("layui-this") break; } });
右侧模块的日记分类,之前是写死的,现在改成动态的。
这个模块是分出去的,我们采用vue的方式,给这个模块最外层的div加一个id,然后用Vue去控制。
<div class="layui-row" id="sider"> ... </div>
下面去新建一个Vue对象。
new Vue({ el:'#sider', data:{ typelist:[] }, methods:{ getTypelist(){ var index = layer.load(1); //添加laoding,0-2两种方式 axios.post('${basePath}/sys-blog-type/selectAll',{}).then(r =>{ layer.close(index); //返回数据关闭loading if(r.data.code != '0000'){ layer.msg(r.data.message,{icon:2}); return; } this.typelist = r.data.data; }).catch(error => { layer.msg(error.response.status,{icon:2}); layer.close(index); //返回数据关闭loading }) } }, created(){ this.getTypelist(); } });
渲染li元素
<ul class="tlist"> <li v-for="item in typelist"> <a href="">{{item.typeName}}</a></li> </ul>
效果:
我刚才又添加了一笔数据,证明是可行的。
下一个问题,当我点击某一个日记类别应该发生什么呢?
我想,应该就是跳转到首页,然后首页展示的就是该类别的数据吧!
改变后的li元素
<li v-for="item in typelist"> <a :href="'${basePath}?typeId='+item.id">{{item.typeName}}</a></li>
地址栏是 http://localhost/?typeId=1
有点丑。额,先这样吧,不管了。
这边我们做一个改动,把layPage的引入代码放到list.jsp。
layui.use('laypage', function(){ var laypage = layui.laypage; window.laypage = laypage; //绑定到windows对象上,方便vue去获取 });
然后呢,我们要根据是否有typeId传过来,显示不同的内容。
查询日记的axios改成
axios.post('${basePath}/blog/select?pageIndex='+this.pageIndex+'&size=' + this.size,{ typeId:'${typeId}' })
刷新页面,看到参数已经传过去了。
后台关键代码
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"); if(StrUtil.isNotEmpty(blog.getBlogType())){ queryWrapper.eq("blog_type",blog.getBlogType()); } return blogMapper.selectPage(pageBean,queryWrapper); }
很简单吧,就看下type传过来没有,有的话就把条件加上,搞定。
前端再加一个小提示,只要没有日记,就显示。
computed:{ empty(){ return this.total == 0 } },
html:
<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> {{empty?'该类型还没有日记哦,快去写一篇吧!':''}} <div id="pageCode"></div> </div> </div>
效果:
好啦,这一讲就到此为止了,总结一下,这一节的内容主要是日记类型的增删改查,还有MP自动生成代码的方法。