👍 碰到问题欢迎留言或私信我
目录
项目代码逻辑并不复杂,但基本简单实现了一个管理系统必备的几个功能,如果有所需要可以进行完善优化,有问题欢迎私信我~
一。技术栈
前端:
- HTML + CSS + JavaScript 三件套
- vue
- Element组件库
- axios
后端
- Java 编程语言
- Spring + SpringMVC + SpringBoot 框架
- MyBatis + MyBatis Plus 数据访问框架
- MySQL 数据库
二。项目图片展示
三。项目代码
首先给大家看一下整体包结构再详述流程
1.数据库创建表格
首先在数据库创建对应的表单。
create table tbl_book
(
id int auto_increment
primary key,
type varchar(20) null,
name varchar(50) null,
description varchar(255) null
)
charset = utf8;
2.初始化springboot
初始化springboot工程,选择下面三个依赖
因为需要使用MyBatis-plus,,但由于springboot这目前还没有收录,需要自己去maven repository找对应坐标如下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
3.连接数据库,配置参数
将application.properties文件改成application.yml,并在其中连接数据库并做相关配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?serveTimeZone=CST
username: root
password: root
mybatis-plus:
global-config:
db-config:
#去掉表格前缀
table-prefix: tbl_
#控制台打印运行日志
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 80
4.创建实体类
创建实体类与数据库表形成映射,由于使用了lombok所以简化了手工书写get和set方法
package com.gaunzhi.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
/**
* @author 观止
*/
@Data
public class Book {
@TableId(type = IdType.AUTO)
private Integer id;
private String type;
private String name;
private String description;
}
5.数据层
接下来是dao层方法。由于使用了MyBatis-plus框架,这里只需要继承类和加上注解让spring容器可以识别即可。如果需要扩展功能可以再在其中定义一下方法。
package com.gaunzhi.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gaunzhi.domain.Book;
import org.apache.ibatis.annotations.*;
/**
* 数据层接口
* @author 观止
*/
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
6.业务层逻辑
接下来是业务层接口和实现类,这边也可以使用MyBatis-plus框架,但由于目前使用的还不熟练就采用的是自己定义的方法和实现。
6.1业务层接口
package com.gaunzhi.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gaunzhi.domain.Book;
import java.util.List;
/**
* 业务层接口
* @author 观止study
*/
public interface BookService {
/**
* 保存
*return 成功
*/
boolean save(Book book);
/**
* 按id删除
*
* @param id
* @return
*/
boolean delete(Integer id);
/**
* 修改
*
* @param book
* @return
*/
boolean update(Book book);
/**
* 按id查询
*
* @param id
* @return
*/
Book getById(Integer id);
/**
* 查询全部
*
* @return
*/
List<Book> selectAll();
/**
* 按条件查询
*/
IPage<Book> getPage(int currentPage, int pageSize, Book book);
/**
* 分页查询
*/
IPage<Book> getPage(int currentPage, int pageSize);
}
6.2业务层实现类
package com.gaunzhi.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gaunzhi.dao.BookDao;
import com.gaunzhi.domain.Book;
import com.gaunzhi.service.BookService;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @author 观止
*/
@Service
public class BookServiceImpl implements BookService {
@Resource
private BookDao bookDao;
/**
* 保存图书
*
* @param book
* @return
*/
@Override
public boolean save(Book book) {
return bookDao.insert(book) > 0;
}
/**
* 删除图书
*
* @param id
* @return
*/
@Override
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
/**
* 修改图书
*
* @param book
* @return
*/
@Override
public boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
/**
* 根据id查询图书
*
* @param id
* @return
*/
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
/**
* 获得全部图书
*
* @return
*/
@Override
public List<Book> selectAll() {
return bookDao.selectList(null);
}
/**
* 分页获取图书
*
* @param currentPage 当前页码
* @param pageSize 单页条数
* @return
*/
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage, pageSize);
bookDao.selectPage(page, null);
return page;
}
/**
* 分页查询图书
*
* @param currentPage 当前页码
* @param pageSize 单页条数
* @param book 实体类对象
* @return
*/
@Override
public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
IPage page = new Page(currentPage, pageSize);
LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());
queryWrapper.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());
queryWrapper.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());
bookDao.selectPage(page, queryWrapper);
return page;
}
}
7.分页功能配置类
由于需要实现分页功能,因此需要配置一个拦截器,否则无法实现相应功能
package com.gaunzhi.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MP相关配置
* @author 观止study
*/
@Configuration
public class MPConfig {
/**
* 拦截器
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//创建拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加用来实现分页功能拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
8.表现层逻辑
接下来是表现层定义供前端调用的一系列方法,采用了restful风格
package com.gaunzhi.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gaunzhi.domain.Book;
import com.gaunzhi.service.BookService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 表现层方法
* @author 观止study
*/
@RestController
@RequestMapping("/books")
public class BookController {
@Resource
private BookService bookService;
//增
@PostMapping
public Result save(@RequestBody Book book) {
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
//删
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
//改
@PutMapping
public Result update(@RequestBody Book book) {
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
//查
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code, book, msg);
}
//查
@GetMapping
public Result selectAll() {
List<Book> books = bookService.selectAll();
Integer code = books != null ? Code.GET_OK : Code.GET_ERR;
String msg = books != null ? "" : "数据查询失败,请重试!";
return new Result(code, books, msg);
}
//分页
@GetMapping("/{currentPage}/{pageSize}")
public Result getSinglePage(@PathVariable Integer currentPage, @PathVariable Integer pageSize,Book book) {
IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
//如果当前页码值大于总页码值,重新查询
if (currentPage > page.getPages()) {
page = bookService.getPage((int) page.getPages(), pageSize,book);
}
List<Book> books = page.getRecords();
Integer code = books != null ? Code.GET_OK : Code.GET_ERR;
String msg = books != null ? "" : "数据查询失败,请重试!";
//需要传入page对象而不是具体数据books,因为前端还要从中获取size,current,size等数据
return new Result(code, page, msg);
}
}
9.数据传输协议实现
由于我们返回值的类型各种各样,不利于前端处理,因此选择用统一格式封装结果集,并补充一些提示信息,也就是指实现表现层与前端数据传输数据协议。
9.1定义统一返回格式
package com.gaunzhi.controller;
import lombok.Data;
/**
* 表现层与前端数据传输数据协议实现
* @author 观止
*/
@Data
public class Result {
private Object data;
private Integer code;
private String msg;
public Result() {
}
public Result(Integer code, Object data) {
this.data = data;
this.code = code;
}
public Result(Integer code, Object data, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
}
9.2定义一些标记码
package com.gaunzhi.controller;
/**
* 定义所有标记码值
* @author 观止
*/
public class Code {
/**
* 业务操作结果反馈
*/
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
/**
* 异常类型标识
*/
public static final Integer SYSTEM_ERR = 50001;
public static final Integer BUSINESS_ERR = 50002;
public static final Integer SYSTEM_UNKNOW_ERR = 59999;
}
10.统一处理异常
嗯,现在做到了程序正常执行时返回统一格式,但程序运行过程中难免会出现一些难以预料的异常,所以我们还需要定义异常类,统一处理抛出的异常
10.1统一处理程序抛出的异常
package com.gaunzhi.controller;
import com.gaunzhi.exception.BusinessException;
import com.gaunzhi.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 统一处理程序抛出的异常
* @author 观止
*/
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex) {
//记录日志
//发消息给运维人员
//发邮件给开发人员,ex对象发给开发人员
return new Result(ex.getCode(), null, ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex) {
return new Result(ex.getCode(), null, ex.getMessage());
}
@ExceptionHandler(Exception.class)
public Result doException(Exception exception) {
//记录日志
//发消息给运维人员
//发邮件给开发人员,ex对象发给开发人员
exception.printStackTrace();
return new Result(Code.SYSTEM_UNKNOW_ERR, null,"系统繁忙,请稍后再试");
}
}
10.2处理业务异常
package com.gaunzhi.exception;
/**
*处理业务异常
* @author 观止
*/
public class BusinessException extends RuntimeException {
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
10.3处理系统异常
package com.gaunzhi.exception;
/**
* 处理系统异常
* @author 观止
*/
public class SystemException extends RuntimeException {
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public SystemException(Integer code, String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}
做到这里我们的后端程序已经大功告成了,接下来是一些前端代码
-------------------------------------------------------前后端分割线--------------------------------------------------------
11.前后端交互
这里仅保留网页主要界面代码(books.html)在最下方一块,写明了前端通过axios与后端交互的方法,图中部分插件请自行到网上下载或者私信找我
<!DOCTYPE html>
<html>
<head>
<!-- 页面meta -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>基于SpringBoot整合SSM案例</title>
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
<!-- 引入样式 -->
<link rel="stylesheet" href="../plugins/elementui/index.css">
<link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="hold-transition">
<div id="app">
<div class="content-header">
<h1>图书管理</h1>
</div>
<div class="app-container">
<div class="box">
<div class="filter-container">
<el-input placeholder="图书类别" v-model="pagination.type" style="width: 200px;"
class="filter-item"></el-input>
<el-input placeholder="图书名称" v-model="pagination.name" style="width: 200px;"
class="filter-item"></el-input>
<el-input placeholder="图书描述" v-model="pagination.description" style="width: 200px;"
class="filter-item"></el-input>
<el-button @click="getAll()" class="dalfBut">查询</el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
<el-button type="success" class="butT2" @click="getAllNotCondition()">重置</el-button>
</div>
<el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
<el-table-column type="index" align="center" label="序号"></el-table-column>
<el-table-column prop="type" label="图书类别" align="center"></el-table-column>
<el-table-column prop="name" label="图书名称" align="center"></el-table-column>
<el-table-column prop="description" label="描述" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<div class="pagination-container">
<el-pagination
class="pagiantion"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
layout="total, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
<!-- 新增标签弹层 -->
<div class="add-form">
<el-dialog title="新增图书" :visible.sync="dialogFormVisible">
<el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right"
label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel()">取消</el-button>
<el-button type="primary" @click="handleAdd()">确定</el-button>
</div>
</el-dialog>
</div>
<!-- 编辑标签弹层 -->
<div class="add-form">
<el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">
<el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right"
label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel()">取消</el-button>
<el-button type="primary" @click="handleEdit()">确定</el-button>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</body>
<!-- 引入组件库 -->
<script src="../js/vue.js"></script>
<script src="../plugins/elementui/index.js"></script>
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script src="../js/axios-0.18.0.js"></script>
<script>
var vue = new Vue({
el: '#app',
data: {
dataList: [],//当前页要展示的列表数据
dialogFormVisible: false,//添加表单是否可见
dialogFormVisible4Edit: false,//编辑表单是否可见
formData: {},//表单数据
rules: {//校验规则
type: [{required: true, message: '图书类别为必填项', trigger: 'blur'}],
name: [{required: true, message: '图书名称为必填项', trigger: 'blur'}]
},
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize: 10,//每页显示的记录数
total: 0,//总记录数
type: "",
name: "",
description: ""
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
this.getAll();
},
methods: {
//列表
// getAll() {
// axios.get("/books").then((res) => {
// this.dataList = res.data.data;
// })
// },
//条件查询
//分页查询
getAll() {
let param = "?query";
param += "&type=" + this.pagination.type;
param += "&name=" + this.pagination.name;
param += "&description=" + this.pagination.description;
axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize+param).then((res) => {
this.dataList = res.data.data.records;
this.pagination.currentPage = res.data.data.current;
this.pagination.pagesize = res.data.data.size;
this.pagination.total = res.data.data.total;
})
},
getAllNotCondition() {
axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize).then((res) => {
this.dataList = res.data.data.records;
this.pagination.currentPage = res.data.data.current;
this.pagination.pagesize = res.data.data.size;
this.pagination.total = res.data.data.total;
})
this.pagination.type = "";
this.pagination.name = "";
this.pagination.description = "";
},
//切换页码
handleCurrentChange(currentPage) {
this.pagination.currentPage = currentPage;
this.getAll();
},
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
//重置表单
resetForm() {
this.formData = {};
},
//添加
handleAdd() {
axios.post("/books", this.formData).then((res) => {
console.log(res.data);
if (res.data.code == 20011) {
this.dialogFormVisible = false;
this.$message.success("添加成功");
} else if (res.data.code == 20010) {
this.$message.error("添加失败");
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
this.getAll();
});
},
//取消
cancel() {
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("当前操作取消");
},
// 删除
handleDelete(row) {
//1.弹出提示框
this.$confirm("此操作永久删除数据,是否继续?", "提示", {
type: 'info'
}).then(() => {
//做删除业务
axios.delete("/books/" + row.id).then((res) => {
if (res.data.code == 20021) {
this.$message.success("删除成功");
} else {
this.$message.error("删除失败");
}
}).finally(() => {
this.getAll();
});
}).catch(() => {
//取消删除
this.$message.info("取消删除操作");
});
},
//弹出编辑窗口
handleUpdate(row) {
console.log(row);
axios.get("/books/" + row.id).then((res) => {
if (res.data.code == 20041) {
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;
} else {
this.$message.error(res.data.msg);
}
});
},
//修改
handleEdit() {
axios.put("/books", this.formData).then((res) => {
console.log(res.data);
if (res.data.code == 20031) {
this.dialogFormVisible4Edit = false;
this.$message.success("修改成功");
} else if (res.data.code == 20030) {
this.$message.error("修改失败");
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
this.getAll();
});
},
}
})
</script>
</html>