一、功能完善
1.需求分析
基于前面两篇文章,成功将一个系统构建完成;虽然就效果而言已经完成,不过就功能来说,还有欠缺,好比一栋楼目前只有骨架而没有实现功能去填补。
2.具体要求
后端中只实现了用户、权限角色、功能许可的逻辑业务,所有首先,应当完善前端中的一个需求——系统公告notice;同时再提供一个访问系统的共有功能——图书管理;最后就是数据库建表。
二、具体实现
1.系统公告notice
(1)实体类
需求分析
对于一个展示在首页的系统公告,需要标题title、内容content,以及发布时间time,同时为其设置一个数据库的主键id。
源码
package com.example.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
@Data
@TableName("notice")
public class Notice extends Model<Notice> {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 发布时间
*/
private String time;
}
(2)mapper层
package com.example.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.system.entity.Notice;
public interface NoticeMapper extends BaseMapper<Notice> {
}
(3)service层
package com.example.system.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.system.entity.Notice;
import com.example.system.mapper.NoticeMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class NoticeService extends ServiceImpl<NoticeMapper, Notice> {
@Resource
private NoticeMapper noticeMapper;
}
(4)controller层
package com.example.system.controller;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.system.common.handle.Result;
import com.example.system.entity.Notice;
import com.example.system.service.NoticeService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/api/notice")
public class NoticeController {
@Resource
private NoticeService noticeService;
/**
* 保存
*
* @param notice
* @return
*/
@PostMapping
public Result<?> save(@RequestBody Notice notice) {
notice.setTime(DateUtil.formatDateTime(new Date()));
return Result.success(noticeService.save(notice));
}
/**
* 更新
*
* @param notice
* @return
*/
@PutMapping
public Result<?> update(@RequestBody Notice notice) {
return Result.success(noticeService.updateById(notice));
}
/**
* 删除
*
* @param id
* @return
*/
@DeleteMapping("/{id}")
public Result<?> delete(@PathVariable Long id) {
noticeService.removeById(id);
return Result.success();
}
/**
* 根据id查询
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<Notice> findById(@PathVariable Long id) {
return Result.success(noticeService.getById(id));
}
/**
* 查询所有
*
* @return
*/
@GetMapping
public Result<List<Notice>> findAll() {
return Result.success(noticeService.list());
}
/**
* 分页
*
* @param name
* @param pageNum
* @param pageSize
* @return
*/
@GetMapping("/page")
public Result<IPage<Notice>> findPage(@RequestParam(required = false, defaultValue = "") String name,
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize) {
LambdaQueryWrapper<Notice> queryWrapper = Wrappers.<Notice>lambdaQuery().like(Notice::getTitle, name);
return Result.success(noticeService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
}
(5)前端界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta HTTP-EQUIV="pragma" CONTENT="no-cache">
<meta HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
<meta HTTP-EQUIV="expires" CONTENT="0">
<title>公告管理</title>
<link rel="stylesheet" href="../../css/element.css">
<link rel="stylesheet" href="../../css/base.css">
</head>
<body>
<!-- 整体区域 -->
<div id="wrapper" v-cloak>
<!-- 分级关系,并在“首页”绑定事件回到首页 -->
<div style="padding-bottom: 10px">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item @click.native="parent.changeIndex('index')">首页</el-breadcrumb-item>
<el-breadcrumb-item>公告管理</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 分割线 -->
<div style="height: 1px; margin: 10px 0; background-color: white"></div>
<!-- 搜素栏及新增按钮 -->
<el-input v-model="search" style="width: 20%;" suffix-icon="el-icon-search" placeholder="请输入名称按回车搜索"
@keyup.enter.native="loadTable"></el-input>
<el-button @click="add" type="primary" style="margin: 10px 0">新增</el-button>
<!-- 列表 -->
<el-table :data="tableData" border style="width: 100%">
<!-- 表头 -->
<el-table-column prop="id" label="ID" width="50"></el-table-column>
<el-table-column prop="title" label="标题"></el-table-column>
<el-table-column prop="content" label="内容"></el-table-column>
<el-table-column prop="time" label="发布时间"></el-table-column>
<!-- 编辑、删除操作 -->
<el-table-column fixed="right" label="操作" width="150">
<template slot-scope="scope">
<el-button type="primary" @click="edit(scope.row)" icon="el-icon-edit" circle></el-button>
<el-popconfirm @onConfirm="del(scope.row.id)" title="确定删除?">
<el-button type="danger" icon="el-icon-delete" circle slot="reference"></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div style="background-color: white">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[5, 10, 20, 40]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<!-- 新增子组件 -->
<el-dialog title="公告信息" :visible.sync="dialogFormVisible" width="40%" :close-on-click-modal="false">
<!-- 信息输入,后端提供绑定发布时间属性 -->
<el-form :model="entity">
<el-form-item label="标题" label-width="120px">
<el-input v-model="entity.title" autocomplete="off" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="内容" label-width="120px">
<el-input type="textarea" v-model="entity.content" :rows="5" autocomplete="off" style="width: 80%"></el-input>
</el-form-item>
</el-form>
<!-- 与否按钮 -->
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
<script src="../../js/jquery.min.js"></script>
<script src="../../js/vue.min.js"></script>
<script src="../../js/element.js"></script>
<script src="../../js/tinymce/tinymce.min.js"></script>
<script>
let urlBase = "/api/notice/";
new Vue({
el: "#wrapper",
data: {
user: {},
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
dialogFormVisible: false,
entity: {},
isCollapse: false,
search: '',
},
// 获取当前用户并加载
created() {
this.user = sessionStorage.getItem("user") ? JSON.parse(sessionStorage.getItem("user")) : {};
this.loadTable();
},
methods: {
handleCollapse() {
this.isCollapse = !this.isCollapse;
},
// 退出
logout() {
$.get("/api/user/logout");
sessionStorage.removeItem("user");
location.href = "/page/end/login.html";
},
// 加载列表
loadTable() {
$.get(urlBase + "page?pageNum=" + this.pageNum + "&pageSize=" + this.pageSize + "&name=" + this.search).then(res => {
this.tableData = res.data.records;
this.total = res.data.total;
})
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.loadTable();
},
handleCurrentChange(pageNum) {
this.pageNum = pageNum;
this.loadTable();
},
add() {
this.entity = {};
this.dialogFormVisible = true;
},
save() {
let type = this.entity.id ? "PUT" : "POST";
$.ajax({
url: urlBase,
type: type,
contentType: "application/json",
data: JSON.stringify(this.entity)
}).then(res => {
if (res.code === '0') {
this.$message({
message: "保存成功",
type: "success"
});
this.loadTable();
} else {
this.$message({
message: res.msg,
type: "error"
})
}
this.dialogFormVisible = false;
})
},
edit(obj) {
this.entity = JSON.parse(JSON.stringify(obj));
this.dialogFormVisible = true;
},
del(id) {
$.ajax({
url: urlBase + id,
type: "delete"
}).then(res => {
if (res.code === '0') {
this.$message({
message: "删除成功",
type: "success"
})
this.loadTable();
} else {
this.$message({
message: res.msg,
type: "error"
})
}
})
}
}
})
</script>
</body>
</html>
2.图书管理
(1)实体类
需求分析
一本图书,应当具有其名字name、价格price,以及数据库主键id。
源码
package com.example.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("book")
public class Book extends Model<Book> {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 名称
*/
private String name;
/**
* 价格
*/
private BigDecimal price;
}
(2)mapper层
package com.example.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.system.entity.Book;
public interface BookMapper extends BaseMapper<Book> {
}
(3)service层
package com.example.system.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.system.entity.Book;
import com.example.system.mapper.BookMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class BookService extends ServiceImpl<BookMapper, Book> {
@Resource
private BookMapper bookMapper;
}
(4)controller层
package com.example.system.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.system.common.handle.Result;
import com.example.system.entity.Book;
import com.example.system.entity.User;
import com.example.system.exception.CustomException;
import com.example.system.service.BookService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/api/book")
public class BookController {
@Resource
private BookService bookService;
@Resource
private HttpServletRequest request;
// 需要登录
public User getUser() {
User user = (User) request.getSession().getAttribute("user");
if (user == null) {
throw new CustomException("-1", "请登录");
}
return user;
}
/**
* 新增
*
* @param book
* @return
*/
@PostMapping
public Result<?> save(@RequestBody Book book) {
return Result.success(bookService.save(book));
}
/**
* 更新
*
* @param book
* @return
*/
@PutMapping
public Result<?> update(@RequestBody Book book) {
return Result.success(bookService.updateById(book));
}
/**
* 删除
*
* @param id
* @return
*/
@DeleteMapping("/{id}")
public Result<?> delete(@PathVariable Long id) {
bookService.removeById(id);
return Result.success();
}
/**
* 根据id查询
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<?> findById(@PathVariable Long id) {
return Result.success(bookService.getById(id));
}
/**
* 查询所有
*
* @return
*/
@GetMapping
public Result<?> findAll() {
return Result.success(bookService.list());
}
/**
* 分页
*
* @param name
* @param pageNum
* @param pageSize
* @return
*/
@GetMapping("/page")
public Result<?> findPage(@RequestParam(required = false, defaultValue = "") String name,
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize) {
LambdaQueryWrapper<Book> query = Wrappers.<Book>lambdaQuery().orderByDesc(Book::getId);
if (StrUtil.isNotBlank(name)) {
query.like(Book::getName, name);
}
return Result.success(bookService.page(new Page<>(pageNum, pageSize), query));
}
}
(5)前端界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta HTTP-EQUIV="pragma" CONTENT="no-cache">
<meta HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
<meta HTTP-EQUIV="expires" CONTENT="0">
<title>图书管理</title>
<link rel="stylesheet" href="../../css/element.css">
<link rel="stylesheet" href="../../css/base.css">
</head>
<body>
<!-- 整体区域 -->
<div id="wrapper" v-cloak>
<!-- 分级关系 -->
<div style="padding-bottom: 10px">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item @click.native="parent.changeIndex('index')">首页</el-breadcrumb-item>
<el-breadcrumb-item>图书管理</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 分割线 -->
<div style="height: 1px; margin: 10px 0; background-color: white"></div>
<!-- 搜索栏及新增按钮 -->
<el-input size='small' v-model="search" style="width: 20%;" suffix-icon="el-icon-search"
placeholder="请输入名称按回车搜索"
@keyup.enter.native="loadTable"></el-input>
<el-button size='small' @click="add" type="primary" style="margin: 10px 0">新增</el-button>
<!-- 列表 -->
<el-table :data="tableData" border stripe style="width: 100%">
<!-- 表头 -->
<el-table-column prop="id" label="ID" width="100"></el-table-column>
<el-table-column prop="name" label="图书名字"></el-table-column>
<el-table-column prop="price" label="图书价格"></el-table-column>
<!-- 编辑、删除按钮 -->
<el-table-column fixed="right" label="操作" width="200">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" circle size='small' @click="edit(scope.row)"></el-button>
<el-popconfirm @onConfirm="del(scope.row.id)" title="确定删除?">
<el-button size='small' type="danger" icon="el-icon-delete" circle slot="reference" style="margin-left: 10px"></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div style="background-color: white">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[5, 10, 20, 40]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<!-- 新增子组件 -->
<el-dialog title="图书信息" :visible.sync="dialogFormVisible" width="40%" :close-on-click-modal="false">
<el-form :model="entity">
<el-form-item label="" label-width="120px">图书名字 <el-input v-model="entity.name" autocomplete="off" style="width: 80%"></el-input></el-form-item>
<el-form-item label="" label-width="120px">图书价格 <el-input v-model="entity.price" autocomplete="off" style="width: 80%"></el-input></el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
<script src="../../js/jquery.min.js"></script>
<script src="../../js/vue.min.js"></script>
<script src="../../js/element.js"></script>
<script src="../../js/tinymce/tinymce.min.js"></script>
<script>
let urlBase = "/api/book/";
new Vue({
el: "#wrapper",
data: {
options: [],
fileList: [],
user: {},
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
dialogFormVisible: false,
entity: {},
isCollapse: false,
search: '',
},
// 获取当前用户并加载
created() {
this.user = sessionStorage.getItem("user") ? JSON.parse(sessionStorage.getItem("user")) : {};
this.loadTable();
},
methods: {
handleCollapse() {
this.isCollapse = !this.isCollapse;
},
// 退出
logout() {
$.get("/api/user/logout");
sessionStorage.removeItem("user");
location.href = "/page/end/login.html";
},
// 加载列表
loadTable() {
$.get(urlBase + "page?pageNum=" + this.pageNum + "&pageSize=" + this.pageSize + "&name=" + this.search).then(res => {
this.tableData = res.data.records;
this.total = res.data.total;
})
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.loadTable();
},
handleCurrentChange(pageNum) {
this.pageNum = pageNum;
this.loadTable();
},
add() {
this.entity = {};
this.fileList = [];
this.dialogFormVisible = true;
},
save() {
let type = this.entity.id ? "PUT" : "POST";
$.ajax({
url: urlBase,
type: type,
contentType: "application/json",
data: JSON.stringify(this.entity)
}).then(res => {
if (res.code === '0') {
this.$message({
message: "保存成功",
type: "success"
});
} else {
this.$message({
message: res.msg,
type: "error"
})
}
this.loadTable();
this.dialogFormVisible = false;
})
},
edit(obj) {
this.entity = JSON.parse(JSON.stringify(obj));
this.fileList = [];
this.dialogFormVisible = true;
},
del(id) {
$.ajax({
url: urlBase + id,
type: "delete"
}).then(res => {
if (res.code === '0') {
this.$message({
message: "删除成功",
type: "success"
})
this.loadTable();
} else {
this.$message({
message: res.msg,
type: "error"
})
}
})
}
}
})
</script>
</body>
</html>
3.数据库建表database.sql
create database system_database;
use system_database;
drop table if exists `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '密码',
`nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '昵称',
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像',
`role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '地址',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
INSERT INTO `user` VALUES (1, 'admin', 'admin', '管理员', '3225647306@qq.com', '17373467103', '1622537239707', '[1]', '湖南', 22);
SET FOREIGN_KEY_CHECKS = 1;
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
`permission` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限列表',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
INSERT INTO `role` VALUES (1, '超级管理员', '所有权限', '[1,2,3,4,5]');
INSERT INTO `role` VALUES (2, '普通用户', '部分权限', '[5]');
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单路径',
`icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 's-data' COMMENT '图标',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限菜单表' ROW_FORMAT = DYNAMIC;
INSERT INTO `permission` VALUES (1, '用户管理', '用户管理', 'user', 'user-solid');
INSERT INTO `permission` VALUES (2, '角色管理', '角色管理', 'role', 's-cooperation');
INSERT INTO `permission` VALUES (3, '权限管理', '权限管理', 'permission', 'menu');
INSERT INTO `permission` VALUES (4, '公告管理', '公告管理', 'notice', 'data-board');
INSERT INTO `permission` VALUES (5, '图书管理', '图书管理', 'book', 's-data');
DROP TABLE IF EXISTS `notice`;
CREATE TABLE `notice` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '内容',
`time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发布时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '序号',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名称',
`price` decimal(10, 2) NULL DEFAULT NULL COMMENT '价格',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `book` VALUES (1, '白夜行', 23.59);