饮识分子--中药材前后端分离系统设计

1. 概述

1.1功能

本系统前后端分离,可以实现的功能简述如下
登录:从数据库中查询是否存在该用户,只有后端返回存在时才能进入系统

注册:首先验证是否已存在该用户,存在提示错误,当符合要求后将数据添加到数据库中

主页:一个简单的显示界面,当用户正常登录时,将用户名显示在右上角,即传值的实现

用户管理:分为管理员和普通用户,管理员划分权限,可以实现不同功能。因为逻辑一致,用户的数据比较多,登录验证时,以用户user数据库进行数据验证。可以进行用户的增删改查,批量删除,条件搜索,并且提供数据的上传下载,具体内容在第三部分详细介绍。

中药管理:功能逻辑与用户管理有类似处,依据中药表项进行增加,实现中药材的增删改查,多条件模糊查询,批量的数据删除,也包括中药表格的EXCEL导入导出。

附近药材:集成高德地图API,进行点标记和窗口数据显示,根据实际情况在定位的周围查看最近的地方有哪些中药。

1.2作用

中药管理系统是为了加强中药管理,以便更好地对药品进行监督和管理,使售药机构人员更加高效、准确的完成工作。本系统主要包含系统用户管理模块、药品管理模块、个人信息管理模块、高德地图显示附近中药模块。中药管理系统是以合理、全面、准确的药品编码体系为基础,提供了对中药名称、功效作用、临床应用、化学成分、价格的全面管理,统一的药价管理机制规范了药品的价格。系统能随时提供药库的库存、药品的消耗,还能根据现有库存提供采购计划或应暂停采购的药品清单,以提高资金的利用率,避免不必要的损失,方便快捷的对药基本信息及用户管理员基本信息进行定期的更新和删除等管理。

1.3开发背景

在全球数字化的大背景下,“互联网+中医药”发展势在必行。“互联网+中医药”成为传统中医药在新时期转型发展的共识,加之国家多项利好政策的推动,技术的深化发展,疫情影响等因素,都为传统中医药与数字融合发展提供了可能与先决条件。中药科普大众化及人才培养专业化,有助于改善、解决中医药当前面临的发展问题,全面提速向前。如何利用现代信息技术使企业拥有快速、高效的市场反映能力和高度的效率,已是医药经营企业特别关心的问题。由此建立了一个功能齐备的药库管理系统,实现规范化、自动化,从而达到提高管理的效率。

本系统开发设计思想是实现药品管理的数字化。采用现有软硬件环境,提高系统开发水平和应用效果。满足日常管理的需要,操作过程中能够直观、方便的满足管理的要求。系统采用模块化程序设计方法,既便于系统功能的各种组合,又便于技术维护人员的补充、维护。系统具备数据库维护功能,能够及时根据用户需求进行数据的添加、删除、修改等操作。

2. 开发工具介绍

代码编写使用的是IntelliJ IDEA 2021.3.2,常用的软件,这里就不过多介绍了,主要介绍前端后端及数据库用到的一些软件和工具。

2.1前端

Vue2.0:Vue.js是一款流行的JavaScript前端框架,旨在更好地组织与简化Web开发。Vue所关注的核心是MVC模式中的视图层,同时,它也能方便地获取数据更新,并通过组件内部特定的方法实现视图与模型的交互。

Element:Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。

高德地图API:高德Web服务API向开发者提供HTTP接口,开发者可通过这些接口使用各类型的地理数据服务,返回结果支持JSON和XML格式。Web服务API对所有用户开放,不同类型用户可获取不同的数据访问能力。

2.2 后端

Spring Boot:Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了更容易运行 Spring 应用程序并且减少配置文件。Spring Boot不是什么新的框架,它默认配置了很多框架的使用方式,整合了所有的框架。

Hutool:Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。本系统中主要是用来导入导出表格数据和前后端传数据时用到的。

接口测试——Postman,Swagger

Postman:Postman是一个可扩展的API开发和测试协同平台工具,可以快速集成到CI/CD管道中。旨在简化测试和开发中的API工作流。Postman 有 workspace 的概念,workspace 分 personal 和 team 类型。Personal workspace 只能自己查看的 API,Team workspace 可添加成员和设置成员权限,成员之间可共同管理 API。

Swagger:Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。

**2.3 ** 数据库

Navicat Premium:Navicat是一套数据库管理工具,专为简化数据库的管理及降低系统管理成本而设。Navicat 是以直觉化的图形用户界面而建的,可以安全和简单地创建、组织、访问并共用信息。Navicat Premium 是 Navicat 的产品成员之一,能简单并快速地在各种数据库系统间传输数据,或传输一份指定 SQL 格式及编码的纯文本文件。其他功能包括导入向导、导出向导、查询创建工具、报表创建工具、资料同步、备份、工作计划及更多。

MySQL:常用的数据库管理系统,关系型数据库。

Mybatis-Plus:MyBatis 是一流的持久化框架,支持自定义 SQL,存储过程和高级映射。MyBatis几乎消除了所有 JDBC 代码和手动设置参数和检索的结果。MyBatis 可以使用简单的 XML 或的注释配置和映射基元,映射接口到数据库记录。MyBatis-Plus是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变。封装了对单表的CRUD操作,代码生成、自动分页、逻辑删除、自动填充等功能一应俱全。

3. 设计过程

3.1概要设计

为了保证系统能够长期、安全、稳定、可靠、高效的运行,中药管理系统应该满足以下的性能需求:

1、系统处理的准确性和及时性

系统处理的准确性和及时性是系统的必要性能。在系统设计和开发过程中,要充分考虑系统当前和将来可能承受的工作量,使系统的处理能力和响应时间能够满足对信息处理的需求。

2、系统的开放性和系统的可扩充性

中药管理系统在开发过程中,应该充分考虑以后的可扩充性。例如用户查询的需求也会不断的更新和完善。所有这些,都要求系统提供足够的手段进行功能的调整和扩充。而要实现这一点,应通过系统的开放性来完成,既系统应是一个开放系统,只要符合一定的规范,可以简单的加入和减少系统的模块,配置系统的硬件。通过软件的修补、替换完成系统的升级和更新换代。

3、系统的易用性和易维护性

中药管理系统是直接面对使用人员的,而使用人员往往对计算机并不非常熟悉。这就要求系统能够提供良好的用户接口,易用的人机交互界面。要实现这一点,就要求系统应该尽量使用用户熟悉的术语和中文信息的界面;针对用户可能出现的使用问题,要提供足够的在线帮助,缩短用户对系统熟悉的过程。

4、系统的标准性

系统在设计开发使用过程中都要涉及到很多计算机硬件、软件。所有这些都要符合主流国际、国家和行业标准。

5、系统的先进性

目前计算系统的技术发展相当快,做为中药管理系统工程,在系统的生命周期尽量做到系统的先进,充分完成企业信息处理的要求而不至于落后。这一方面通过系统的开放性和可扩充性,不断改善系统的功能完成。另一方面,在系统设计和开发的过程中,应在考虑成本的基础上尽量采用当前主流并先进且有良好发展前途的产品。

数据库设计:

数据库设计有几个范式,一般我们要做到的是第三范式,即数据表中没有冗余字段以及同一个表中的字段没有函数依赖关系。冗余字段即在一个表中已经保存过的信息,在另一个表中就不应该存在,如果需要的话,可以通过表间的关联来得到,函数依赖性就是一个表中的字段间不应该有计算关系,如一个表中有单价字段、数量字段,就不应该有一个总金额字段。如果程序运行过程中需要总金额,可以实时计算。不过在一些较常用的表中,我们可以适当地保留冗余字段,这样,在程序运行过程中可以减少由于表间互相关联而使用速度降低等问题。这就是所谓的第四范式。数据表设计时,最好不要使用用户输入的信息作为主键,每一个数据表自己定义一个主键,添加信息是由程序自动添加,这样就可以减少数据更新时产生的错误。表与表相关联的外键最好是由程序自动生成的主键,这样数据库就比较规范了。另外,数据表设计时一般都应该有一些标志字段,标志字段可以定义成CHAR型。在最初设计时,可能我们没有考虑到的一些情况,在程序后来的开发中,可以通过设计标志字段为不同的值来解决,这样就避免了修改数据库结构。

数据库初期设计时要谨慎,把所有可能的情况都考虑进去,即使当时没有用到,也要

将它留在数据库中作为备用字段以便将来扩充。程序一旦开始编码,就应该尽量避免再修改数据库。因为如果数据库结构一旦改变,所有与修改的数据表相关的业务都有可能受到影响,而某些影响还很难看到,这样就容易形成一个恶性循环。错误越改越多,越改越乱,最终导致程序的失败。PB 的数据窗口与其他语言的数据控件不一样,它的很多东西是预编译的。即使你一个模块已经调试无误,但只要数据库结构改动。相应的模块就要重新修改,否则一定会出问题。

数据字典如下:

1.用户信息

数据项 id
含义说明 用户ID
类型 int
数据项 name
含义说明 用户姓名
类型 varchar
长度 30
数据项 sex
含义说明 用户性别
类型 varchar
长度 2
数据项 age
含义说明 用户年龄
类型 int
数据项 password
含义说明 用户密码
类型 varchar
长度 255
数据项 connection
含义说明 用户联系方式
类型 varchar
长度 255

2.药品信息

数据项 id
含义说明 中药编号
类型 int
数据项 name
含义说明 中药名
类型 varchar
长度 255
数据项 application
含义说明 功效作用
类型 varchar
长度 255
数据项 clinical
含义说明 临床应用
类型 varchar
长度 255
数据项 composition
含义说明 化学成分
类型 varchar
长度 255
数据项 price
含义说明 价格
类型 double

3.管理员信息

数据项 id
含义说明 管理员ID
类型 int
数据项 name
含义说明 管理员姓名
类型 varchar
长度 30
数据项 password
含义说明 管理员密码
类型 varchar
长度 30
数据项 level
含义说明 管理员权限
类型 varchar
长度 10

3.2详细设计分析

系统功能实现分为初始登录注册页面

3.2.1登录:后端关联数据库,判断是否符合要求,符合才能正确进入页面,前端进行一个输入框内容的简单校验。同时将用户信息存储到浏览器,以在后面页面可以使用,类似session技术,但是在Vue中实现特别简单,localStorage.setItem("user", JSON.stringify(res.data))

即可将数据上传

user:localStorage.getItem("user")?JSON.parse(localStorage.getItem("user")) : {}

即可获取上一页传来的数据

后端接口控制代码:

1
2
3
4
5
6
7
8
9
10
@PostMapping("/login")
public Result login(@RequestBody UserDTO userDTO) {
String name = userDTO.getName();
String password = userDTO.getPassword();
if (StrUtil.isBlank(name) || StrUtil.isBlank(password)) {
return Result.error(Constants.CODE_400,"参数错误");
}
UserDTO dto = userService.login(userDTO);
return Result.success(dto);
}

判断是否与数据库一致:

1
2
3
4
5
6
7
8
9
10
11
private static final Log LOG = Log.get();

public UserDTO login(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one != null) {
BeanUtil.copyProperties(one, userDTO, true);
return userDTO;
} else {
throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
}
}

3.2.2注册:注册页面只设置了三个参数,用户名,密码,确认密码。前端仅做输入框的值是否为空,内容格式是否符合要求的判断,而后端判断此次注册的用户名密码是否在数据库中已存在,不允许存在两个相同用户名的账号,否则登录时就有返回两个值,无法判断是否正确,后期信息在个人主页中完善即可。

1
2
@PostMapping("/register")
public User register(@RequestBody UserDTO userDTO) { return userService.register(userDTO); }

判断是否存在该用户:

1
2
3
4
5
6
7
8
9
10
11
public User register(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one == null) {
one = new User();
BeanUtil.copyProperties(userDTO, one, true);
save(one); // 把 copy完之后的用户对象存储到数据库
} else {
throw new ServiceException(Constants.CODE_600, "用户已存在");
}
return one;
}

主页包含个人信息,退出系统,用户管理,管理员管理,中药管理,附近药材功能模块。

3.2.3个人信息:这里的信息对应登录者的信息,直接从数据库中导入,因为是登录成功后显示页,所以不做是否存在的判断,直接查询数据即可。

后端接口代码实现:

1
2
3
4
5
6
@GetMapping("/name/{name}")
public Result findOne(@PathVariable String name){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name",name);
return Result.success(userService.getOne(queryWrapper));
}

数据库查询该账号数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
private User getUserInfo(UserDTO userDTO) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", userDTO.getName());
queryWrapper.eq("password", userDTO.getPassword());
User one;
try {
one = getOne(queryWrapper); // 从数据库查询用户信息
} catch (Exception e) {
LOG.error(e);
throw new ServiceException(Constants.CODE_500, "系统错误");
}
return one;
}

3.2.4退出系统:简单的返回登录页即可,前端实现。

1
2
3
<el-dropdown-item>
<router-link to="/login" style="text-decoration: none">退出系统</router-link>
</el-dropdown-item>

3.2.5用户管理:包含多条件查询,新增数据,批量删除,Excel导入导出功能,分页查询,并且数据实时更新在该页中。
分页查询:基于MyBatis-plus实现,倒序查询方便测试新增是否正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//分页查询:mybatis-plus
@GetMapping("/page")
public IPage<User> findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(defaultValue = "") String name,
@RequestParam(defaultValue = "-1") Integer age,
@RequestParam(defaultValue = "") String password) {
IPage<User> page = new Page<>(pageNum, pageSize);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (!"".equals(name)) {
queryWrapper.like("name", name);
}
if (age!=-1) {
queryWrapper.like("age", age);
}
if (!"".equals(password)) {
queryWrapper.like("password", password);
}
//倒序
queryWrapper.orderByDesc("id");
return userService.page(page, queryWrapper);
}

数据更新时的接口实现:如果有id,就是对数据库内容的更新,如果没有id就是新增数据。原本使用Mybatis实现,较为复杂,后续删除批量删除查询也麻烦。

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserService extends ServiceImpl<UserMapper,User> {
public boolean saveUser(User user)
{
if(user.getId()==null) {
return save(user);// puls 提供的
}
else {
return updateById(user);
}
}

调整成Mybatis-plus 这样查询,插入,更新,多条件查询只需要用自带的即可。 注释部分都不需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package edu.njucm.javaks.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import edu.njucm.javaks.entity.User;
import org.apache.ibatis.annotations.*;

import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// @Select("SELECT * FROM user")
// List<User> findAll();
// @Insert("INSERT into user(name,sex,age,password) VALUES (#{name}, #{sex}, #{age}, #{password})")
// int insert(User user);
// int update(User user);
// @Delete("delete from user where id = #{id}")
// Integer deleteById(@Param("id") Integer id);
//
// @Select("SELECT * FROM user where name like #{name} limit #{pageNum},#{pageSize}")
// List<User> selectPage(Integer pageNum,Integer pageSize,String name);
//
// @Select("SELECT count(*) from user where name like concat('%', #{name}, '%')")
// Integer selectTotal(String name);
}

Control接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Autowired
private UserService userService;

@GetMapping
public List<User> findAll() {
return userService.list();
}

@PostMapping
public boolean save(@RequestBody User user) {
return userService.saveUser(user);
}

@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return userService.removeById(id);
}

//批量删除
@PostMapping("/del/batch")
public boolean deleteBatch(@RequestBody List<Integer> ids) { // [1,2,3]
return userService.removeByIds(ids);
}

3.2.6Excel导入导出:由Hutool工具实现

  1. 从数据库查询出所有的数据

  2. 通过工具类创建writer 写出到磁盘路径

  3. 在内存操作,写出到浏览器

  4. 一次性写出list内的对象到excel,使用默认样式,强制输出标题

  5. 设置浏览器响应的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   /**
* 导出接口
*/
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception {
// 从数据库查询出所有的数据
List<User> list = userService.list();
// 通过工具类创建writer 写出到磁盘路径
// ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath + "/用户信息.xlsx");
// 在内存操作,写出到浏览器
ExcelWriter writer = ExcelUtil.getWriter(true);
//自定义标题别名
writer.addHeaderAlias("id", "ID号");
writer.addHeaderAlias("name", "姓名");
writer.addHeaderAlias("sex", "性别");
writer.addHeaderAlias("age", "年龄");
writer.addHeaderAlias("password", "密码");
writer.addHeaderAlias("connection", "联系方式");

// 一次性写出list内的对象到excel,使用默认样式,强制输出标题
writer.write(list, true);

// 设置浏览器响应的格式
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
String fileName = URLEncoder.encode("用户信息", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");

ServletOutputStream out = response.getOutputStream();
writer.flush(out, true);
out.close();
writer.close();
}

导入就是javabean的方式读取Excel内的对象,实现相对简单

1
2
3
4
5
6
7
8
9
10
11
12
/**
* excel 导入
*/
@PostMapping("/import")
public Boolean imp(MultipartFile file) throws Exception {
InputStream inputStream = file.getInputStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
//javabean的方式读取Excel内的对象,但是要求表头必须是英文,跟javabean的属性要对应起来
List<User> list = reader.readAll(User.class);
userService.saveBatch(list);
return true;
}

3.2.7附近中药:可以查询附近的中药点及该处有哪些中药,使用高德Web服务API实现,查询到经纬度后进行点标记和单击窗口显示内容即可。

文本定义:

1
2
3
4
var content1 = [
"<div style='font-size: 14px; color: red; width:120px; height: 25px'>南京中医药大学</div>",
"<div style='font-size: 12px; color: blue;'>灵芝,当归,芦根</div>"
]

显示地图:

1
2
3
4
5
mounted() {
var map = new AMap.Map("container", {
zoom: 13,
center: [118.796624,32.059344]
})

窗口初始化:

1
2
3
4
var infoWindow1 = new AMap.InfoWindow({
anchor: 'top-right',
content: content1.join("<br>")
});

点击事情设置:

1
2
3
var clickHandler1 = function (e) {
infoWindow1.open(map,[118.945804,32.102836]);
}

初始化点标记,并加入地图

1
2
3
4
5
6
var marker1 = new AMap.Marker({
position: new AMap.LngLat(118.945804,32.102836),
title:'南京中医药大学'
})
marker1.on('click',clickHandler1);
map.add(marker1);

3.2.8自定义全局异常处理:这样报错提示时可以看出来是哪步有问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import lombok.Getter;

/**
* 自定义异常
*/
@Getter
public class ServiceException extends RuntimeException {
private String code;

public ServiceException(String code, String msg) {
super(msg);
this.code = code;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import edu.njucm.javaks.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {

/**
* 如果抛出的的是ServiceException,则调用该方法
* @param se 业务异常
* @return Result
*/
@ExceptionHandler(ServiceException.class)
@ResponseBody
public Result handle(ServiceException se){
return Result.error(se.getCode(), se.getMessage());
}

}

3.2.9接口测试:选用Swagger和Postman,前期Swagger用的比较多。

之后虽然配置的Swagger比较方便,但是Postman功能比较全面,所以用Postman来做后端接口测试。

比如:上传文件测试,可以直接在Postman里实现,选择Post,file即可,能直接看到返回值,然后在数据库中验证即可,确保无误后再传入前端。

3.2.10前端网页:配置路由,封装导航栏和顶部栏,包含的版块作为子路由,这样通过点击标签时只更换需要的那部分内容,不影响其他部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const routes = [
{
path: '/',
name: 'Manage',
component: () => import(/* webpackChunkName: "about" */ '../views/Manage.vue'),
redirect: "/home",
children : [
{path: 'user',name: 'User',component: ()=>import('../views/User.vue')},
{path: 'home',name: 'Home',component: ()=>import('../views/Home.vue')},
{path: 'medicine',name: 'Medicine',component: ()=>import('../views/Medicine.vue')},
{path: 'admin',name: 'admin',component: ()=>import('../views/Admin.vue')},
{path: 'person',name: 'person',component: ()=>import('../views/Person.vue')},
{path: 'map',name: 'map',component: ()=>import('../views/map.vue')},
]
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue'),
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue'),
}
]

3.2.11数据库:

4. 调试

4.1运行环境配置

4.1.1系统配置 配置跨域资源共享(CORS)支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

// 当前跨域请求最大有效时长。这里默认1天
private static final long MAX_AGE = 24 * 60 * 60;

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://localhost:8080"); // 1 设置访问源地址
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
corsConfiguration.setMaxAge(MAX_AGE);
source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}

4.1.2 Mybatis配置 配置MyBatis-Plus插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("edu.njucm.javaks.mapper")

public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

**4.1.3 Swagger配置 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
@EnableOpenApi
public class SwaggerConfig {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@Bean
public Docket restApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("标准接口")
.apiInfo(apiInfo("Spring Boot中使用Swagger2构建RESTful APIs", "1.0"))
.useDefaultResponseMessages(true)
.forCodeGeneration(false)
.select()
.apis(RequestHandlerSelectors.basePackage("edu.njucm.javaks.controller"))
.paths(PathSelectors.any())
.build();
}

/**
* 创建该API的基本信息
* 访问地址:http://ip:port/swagger-ui.html
*
* @return
*/
private ApiInfo apiInfo(String title, String version) {
return new ApiInfoBuilder()
.title(title)
.description("饮识分子: localhost:8080")
.termsOfServiceUrl("localhost:8080")
.version(version)
.build();
}
}

4.1.4 异常处理参数:

1
2
3
4
5
6
7
8
9
public interface Constants {

String CODE_200 = "200"; //成功
String CODE_401 = "401"; // 权限不足
String CODE_400 = "400"; // 参数错误
String CODE_500 = "500"; // 系统错误
String CODE_600 = "600"; // 其他业务异常

}

4.1.5 结果集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 接口统一返回包装类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {

private String code;
private String msg;
private Object data;

public static Result success() {
return new Result(Constants.CODE_200, "", null);
}

public static Result success(Object data) {
return new Result(Constants.CODE_200, "", data);
}

public static Result error(String code, String msg) {
return new Result(code, msg, null);
}

public static Result error() {
return new Result(Constants.CODE_500, "系统错误", null);
}

}

4.1.6 pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>edu.njucm</groupId>
<artifactId>javaks</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>javaks</name>
<description>javaks</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.1</version>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-generator</artifactId>-->
<!-- <version>3.5.1</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.velocity</groupId>-->
<!-- <artifactId>velocity</artifactId>-->
<!-- <version>1.7</version>-->
<!-- </dependency>-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

<!-- 数据库-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>


</project>

4.1.7 路由配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/Manage.vue'

Vue.use(VueRouter)

const routes = [
{
path: '/',
name: 'Manage',
component: () => import(/* webpackChunkName: "about" */ '../views/Manage.vue'),
redirect: "/home",
children : [
{path: 'user',name: 'User',component: ()=>import('../views/User.vue')},
{path: 'home',name: 'Home',component: ()=>import('../views/Home.vue')},
{path: 'medicine',name: 'Medicine',component: ()=>import('../views/Medicine.vue')},
{path: 'admin',name: 'admin',component: ()=>import('../views/Admin.vue')},
{path: 'person',name: 'person',component: ()=>import('../views/Person.vue')},
{path: 'map',name: 'map',component: ()=>import('../views/map.vue')},
]
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue'),
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue'),
}
]

const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})

export default router

4.1.8 Request和reponse拦截器配置(后端接口8082):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import axios from 'axios'

const request = axios.create({
baseURL: 'http://localhost:8082',
timeout: 5000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';

// config.headers['token'] = user.token; // 设置请求头
return config
}, error => {
return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)

export default request

4.1.9 gloable.css(格式配置文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
html.body.div{/*边框设为0*/
margin: 0;
padding: 0;
}
html.body{
height: 100%;
}

.ml-5 {
margin-left: 5px;
}
.mr-5 {
margin-right: 5px;
}
.pd-10 {
padding: 10px 0;
}

4.1.10 Index.html引入高德地图

1
2
3
  </body>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=、、、、、、、、、、"></script>
</html>

4.2调试

4.2.1登录

实现:登录验证逻辑连接数据库,用户名对应name,密码对应password

只有当用户名密码输入正确时才能正确进入,同时将数据传到主页里使用。

异常处理:

  1. 为空时提示(前端验证)

  2. 账号密码不正确时,提示用户名密码错误

  3. 简单的字符长度要求(前端验证)

4.2.2注册

异常处理:

1.密码输入不正确

  1. 数据库中已存在该账号的用户

成功注册

数据库中更新

4.2.3主页

用王羲之的账号登录,系统会将用户名的值传递到主页右上角。

这里的实现相比课本中学到的简单一些。只需要将数据封装一下两个页面post,request接口很简单就能使用。{{user.name}}

1
2
3
4
5
data() {
return {
user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
}
},

进入主页后,右上角由登录传值获取,头像是从网页上直接获取的,原本设想是同步不同头像,但是逻辑和名字是一样的,这是的图片采用src:地址下载,就没有单独设置,实际系统完善中可以在数据库再加入一个头像属性。

4.2.4个人信息

调出数据库的内容显示,并可以进行内容的更新。

4.2.5用户管理
新增数据

修改数据

删除数据

多条件模糊查询

批量删除[新增了五个脏数据]

导出数据

导入数据

分页查询功能:显示10/20条数据

4.2.6附近药材:

南京中医药大学点:

泰康鼓楼医院点:

南京中医药大学汉中门校区点:

5. 设计总结

5.1体会

这次系统的完成中,使用SpringBoot后端+Vue前端完成。在完成过程中学习到的东西非常多,新技术带来的是更方便更美观的界面设计,但是同样带来的是要涉及的东西多。下面我对完成的过程做一个总结:从最开始前端界面完善用到了Element,这个网站是我完成整个设计的基础,它提供了各种模块的示例代码,如何实现,实现后的功能如何,这从另一方面也体现到了框架的好处,搭建好整个网页框架后,就开始了SpringBoot对接数据库,相比于传统的,简便了很多,比如所有参数的getter,setter函数,只要一个@Data就能实现,十分方便。之后学习到了集成Mybatis实现数据查询,但是很快就用Mybatis-plus,升级版重新写了之前的代码,升级版本身内置就包含了之前完善的许多代码,在实际使用测试代码时非常重要,它可以将页面请求的数据直接打印在IDEA控制台中,方便寻找问题。另外,在实现后端时,运用到了软件Postman以及Swagger框架,前者是最开始涉及的,特点是软件界面简洁实用方便,而Swagger框架是配置在代码里的,优点是直观,可以将接口直接显示出来,Postman需要手动输入。还有一点收获是很重要的:在实际操作中,有很多不确定的bug,调试就显得特别重要,网页中用f12来找后端送过来的数据哪里出错,也可以发现页面实现的bug。总的来讲,此次大项目的整体完成,让我对前后端分离实现有了一个较大的认识,实现中碰到的问题很多,有的时候一改就是几个小时,但是完整的解决整个程序后,掌握的知识及技术非常多。

相比于课堂上常用一些工具,使用SpringBoot+Vue和集成的一些软件工具,可以更快更高效的制作一个前后端分离的页面,并且使用自带的Element包,图标格式等一切都很美观,并且需要什么就可以去学哪些,上手很快。很多东西都是看起来复杂,但是实际写起来会发现特别简单。

对前后端分离有了深刻体会,在后端的数据处理时,首先要保证代码运行的正确,然后再到前端去使用,这也是前后端分离的一个优点,在确保后端可以使用的时候再用。

对比书本中的表格下载来看,Hutool工具进行Excel处理,element里上传文件,可以很快就实现Excel表格的上传和导入数据到数据库。本次系统的设计中,最大的感悟就是官方文档太好用了,不管是Spring,Vue,MyBatis,Element,Hutool,高德API使用教程,都是非常详细的告诉使用者这里如何使用,包括例子,还有版本更新中的改进,哪里变了使用时有没有改变,有很多版本问题就是在这里解决的。

5.2改进

因为之前对Spring基本是没有涉及过,所以使用SpringBoot实现系统中,在遇到像@PathVariable``@RequestPara``@Autowired这些注解时,只是知道了这里要这样用,具体这些注解是什么意思,如何使用,内置了哪些代码,都没有进行深入了解,这点我觉得是欠缺的,但是工程比较庞大,整个Spring的学习也是一个大工程,需要在后续做一个完整的学习,在这次实现中,发现Spring的使用十分有效,很有必要深入研究。

系统完成过程中有两处大修改:
1.结果集的设定,因为前端发送过来的数据很多很杂,数据格式也不一样,所以在完善时,单独写了一个封装结果集的Result。

2.异常报错自定义:使用时会出现系统错误,用户名账号错误,参数错误等等,此时自定以一个异常处理,不同情况返回不同code值,这在前面截图中有,十分方便,很容易知道哪里出错。

3.Mybaits升级成Mybaits-plus

简化了很多代码:

4.注解十分方便 @Data@TOString可以替代很多代码。@Data注解是Lombok库提供的一个注解,自动为类生成getter、setter、equals、hashCode和toString等方法,减少了重复的样板代码。

6. 系统分工

系统由本人独自完成,基础功能是之前实现的一个简易系统,只有数据库的增删改查。在本系统中:完善了个人信息绑定功能,登录注册页面,对表单的导入导出Excel的实现,集成高德地图显示附近中药,网页之间传值显示昵称,并且对之前用Mybaits增删改查的实现改进成Mybatis Plus实现,将主页拆分用子路由显示,统一返回的结果集,以及全局异常处理设置。


饮识分子--中药材前后端分离系统设计
http://example.com/2024/03/06/饮识分子-中药材前后端分离系统设计/
作者
zhanghao
发布于
2024年3月6日
许可协议