写在前面

这篇文章记录学校课程,web全栈项目中我负责的部分,以后端为主(因为我后端的知识遗忘太多了..)

重要文件解读

常见注解

  1. @Resource:
    这是一个 Java 注解,用于依赖注入。它类似于 Spring 的 @Autowired,但更通用,支持更多的注入方式。
  2. @Service
    标记这是一个
    Spring 服务组件。Spring 会自动扫描并管理这个类的实例
  3. @Override
    注解在 Java 中用于明确表示一个方法重写了父类或实现了接口中的某个方法。
    在服务层impl类中常用
    使用 @Override 注解有几个重要的好处:

提高代码可读性: 清晰地表明该方法是重写自父类或接口的方法。
编译时检查: 如果父类或接口中不存在对应的方法,编译器会报错,从而防止拼写错误或其他误操作。
维护方便: 当父类或接口发生变化时,IDE 可以提示哪些方法需要更新。
4. @RestController
是@Controller 和 @ResponseBody 的组合,简化了WESTful Web服务的开发

整体架构逻辑关系

https://lxblog.com/qianwen/share?shareId=07210c85-20fe-4b6b-8091-7a06745de5b0

前端发送请求到控制器,控制器调用服务层,服务层调用serviceimpl来实现服务器接口的方法,然后进入dao数据访问层,在MyBatis 映射文件 Mapper.xml里面定义了sql语句,然后进行数据库数据操作。实体类entity表示数据库表的接口,vo视图对象用于传输数据的对象,包含更多展示的或关联的信息
假设我们正在构建一个简单的分类管理系统,其中涉及以下几个主要组件:

实体类 (Entity): 表示数据库表的结构。
视图对象 (VO): 用于传输数据的对象。
数据访问层 (DAO): 负责与数据库进行交互。
服务层 (Service): 处理业务逻辑。
控制层 (Controller): 处理客户端请求。
MyBatis 映射文件: 定义 SQL 语句。

以下是各个文件之间的交互关系:

前端 (index.html):
用户通过浏览器发送 HTTP 请求到后端。
使用 Fetch API 发送 GET 请求到 /classifyInfo 端点。
接收并解析后端返回的 JSON 数据,显示分类信息及其+子分类信息。
控制器 (ClassifyInfoController.java):
处理前端发送的 HTTP 请求。
根据请求路径和方法调用相应的服务层方法。
返回处理结果给前端。
服务层 (ClassifyInfoServiceImpl.java):
实现具体的业务逻辑。
调用数据访问层方法操作数据库。
将处理结果返回给控制器。
数据访问层 (ClassifyInfoDao.java):
定义与数据库交互的方法。
通过 MyBatis 映射文件执行具体的 SQL 语句。
MyBatis 映射文件 (ClassifyInfoMapper.xml):
定义具体的 SQL 语句。
与数据访问层接口中的方法对应。
实体类 (ClassifyInfo.java):
表示数据库表的结构。
用于存储分类信息的基本数据。
视图对象 (ClassifyInfoVo.java):
用于传输数据的对象。
包含更多的展示信息或关联信息。

ajax axios

https://lxblog.com/qianwen/share?shareId=99c467a7-56f9-4604-afdc-1d870d291744

服务层,控制层,dao层重点理解

MyBatis映射文件

映射文件(如 NxSystemFileInfoDao.xml)通常位于 src/main/resources/mapper/ 目录下)定义了具体的 SQL 查询语句,与 DAO 接口中的方法一一对应

控制层

  1. 作用: 处理 HTTP 请求和响应。
    将业务逻辑交给 Service 层处理
    返回业务结果
  2. 特点:
    处理 HTTP 请求:接收客户端的请求并返回响应。
    路由和参数解析:根据 URL 和请求参数调用相应的方法。
    调用 Service 层:将具体的业务逻辑交给 Service 层处理。
    返回结果:将 Service 层的结果封装成 HTTP 响应返回给客户端。

服务层

  1. 作用:处理业务逻辑:实现具体的业务功能。
    调用 DAO 层:与数据库交互,执行 CRUD 操作
    返回响应
  2. 特点
    业务逻辑处理:实现应用程序的核心业务逻辑。
    事务管理:管理数据库事务,确保数据的一致性。
    权限控制:检查用户是否有权执行某些操作。
    数据校验:验证输入数据的有效性和完整性。
    调用 DAO 层:与数据库交互,执行 CRUD 操作。
    抽象接口:提供清晰的业务接口,便于测试和维护

dao层

  1. 作用:与数据库交互:执行 SQL 操作,如查询、插入、更新和删除。
    提供基础 CRUD 操作:通过继承通用 Mapper 接口简化操作
    返回数据结果
  2. 特点
    与数据库交互:执行 SQL 操作,如查询、插入、更新和删除。
    CRUD 操作:提供基础的增删改查方法。
    自定义查询:可以根据需要编写自定义的查询方法。
    隔离数据库细节:隐藏底层数据库的具体实现细节,使得业务逻辑层不直接依赖数据库。
    提高可维护性:集中管理数据库操作,减少重复代码

三者关系

  1. 控制层调用服务层:
    Controller 接收请求后,调用 Service 层的方法处理业务逻辑。
  2. 服务层调用 DAO 层:
    Service 层在处理业务逻辑时,调用 DAO 层的方法与数据库交互。
  3. 数据流:
    请求 -> Controller -> Service -> DAO -> 数据库
    数据库 -> DAO -> Service -> Controller -> 响应

目录解读

各目录及文件的作用

src/main/java/com.example/
controller/:存放所有的控制器类,这些类负责处理HTTP请求,调用服务层方法,并返回响应。
例如:MenuController.java, EchartsController.java, ClassifyInfoController.java 等。
service/:存放服务层接口,这些接口定义了业务逻辑的方法。
例如:AdminInfoService.java, UserInfoService.java 等。
service/impl/:存放服务层接口的具体实现类。
例如:AdminInfoServiceImpl.java, UserInfoServiceImpl.java 等。
dao/ or repository/:存放数据访问对象(DAO)或仓库(Repository)接口,这些接口定义了与数据库交互的方法。
例如:AdminInfoDao.java, UserInfoDao.java 等。
entity/:存放实体类,这些类通常对应数据库中的表,用于封装数据。
例如:AdminInfo.java, UserInfo.java 等。
config/:存放配置类,这些类用于配置Spring框架的各种组件。
例如:WebConfig.java, DatabaseConfig.java 等。
common/:存放通用工具类、常量类等。
例如:Result.java, Utils.java 等。
Application.java:项目的启动类,通常包含Spring Boot应用的入口点。
例如:@SpringBootApplication 注解的类。
src/main/resources/
application.properties:配置文件,用于配置应用的各种参数,如数据库连接、服务器端口等。
例如:spring.datasource.url=jdbc:mysql://localhost:3306/mydb 等。
static/:存放静态资源文件,如CSS、JavaScript、图片等。
例如:style.css, script.js, logo.png 等。
templates/:存放模板文件,通常用于Thymeleaf或其他模板引擎。
例如:index.html, login.html 等。
src/test/java/com.example/
TestClasses.java:存放测试类,这些类用于编写单元测试和集成测试。
例如:MenuControllerTest.java, UserServiceTest.java 等。

yml文件解读

application.yml 文件详解
server 配置
yaml
深色版本
server:
port: 8080
connection-timeout: 60000
port: 8080
指定应用运行的端口号,默认是8080。
connection-timeout: 60000
设置连接超时时间,单位是毫秒。这里设置为60000毫秒,即60秒。
spring 配置
yaml
深色版本
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/deliciousFoods?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
thymeleaf:
cache: false #关闭缓存
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
指定数据库驱动类名,这里是MySQL的驱动。
username: root
数据库用户名。
password: 123456
数据库密码。
url: jdbc:mysql://localhost:3306/deliciousFoods?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
数据库连接URL,包括数据库地址、端口、数据库名以及一些连接参数。
useUnicode=true: 使用Unicode字符集。
characterEncoding=utf-8: 设置字符编码为UTF-8。
allowMultiQueries=true: 允许执行多条SQL语句。
useSSL=false: 不使用SSL连接。
serverTimezone=GMT%2b8: 设置服务器时区为东八区(北京时间)。
thymeleaf:
cache: false
关闭Thymeleaf模板缓存,方便开发时实时看到模板变化。
servlet:
multipart:
max-file-size: 100MB
设置单个文件上传的最大大小为100MB。
max-request-size: 100MB
设置整个请求的最大大小为100MB。
mybatis 配置
yaml
深色版本
mybatis:
mapper-locations: classpath:mapper/.xml
type-aliases-package: com.example.entity
mapper-locations: classpath:mapper/
.xml
指定MyBatis的Mapper XML文件的位置,这里设置为classpath下的mapper目录中的所有.xml文件。
type-aliases-package: com.example.entity
指定实体类所在的包路径,MyBatis会自动扫描该包下的类并为其生成别名。
pagehelper 配置
yaml
深色版本
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
helper-dialect: mysql
指定分页插件使用的数据库方言,这里是MySQL。
reasonable: true
是否开启合理化模式,当总页数小于等于0时,返回第一页或最后一页。
support-methods-arguments: true
是否支持通过方法参数传递分页参数。
params: count=countSql
分页参数的名称,这里设置为countSql。
authority 配置
yaml
深色版本
authority:
info: ‘[{“models”:[{“modelId”:1,”operation”:[1,2,3,4]},{“modelId”:1000001,”operation”:[1,2,3,4]},{“modelId”:1000002,”operation”:[1,2,3,4]},{“modelId”:2,”operation”:[1,2,3,4]},{“modelId”:3,”operation”:[1,2,3,4]},{“modelId”:4,”operation”:[1,2,3,4]},{“modelId”:5,”operation”:[1,2,3,4]},{“modelId”:6,”operation”:[1,2,3,4]},{“modelId”:7,”operation”:[1,2,3,4]},{“modelId”:8,”operation”:[1,2,3,4]},{“modelId”:9,”operation”:[1,2,3,4]},{“modelId”:10,”operation”:[1,2,3,4]}],”level”:1,”modelId”:1,”name”:”管理员”},{“models”:[{“modelId”:5,”operation”:[4,1,2,3]},{“modelId”:9,”operation”:[4,1,2,3]}],”level”:2,”modelId”:2,”name”:”用户”}]’
info:
这是一个JSON字符串,定义了不同角色的权限信息。
每个角色包含models(模型)、level(权限级别)、modelId(模型ID)和name(角色名称)。
models是一个列表,每个元素包含modelId和operation(操作权限)。
logging 配置
yaml
深色版本
logging:
file: log/my.log
file: log/my.log
指定日志文件的路径和名称,日志将被写入到log/my.log文件中。
总结
server: 配置应用的端口和连接超时时间。
spring: 配置数据源、Thymeleaf模板引擎和文件上传限制。
mybatis: 配置MyBatis的Mapper XML文件位置和实体类包路径。
pagehelper: 配置分页插件的相关参数。
authority: 配置角色权限信息。
logging: 配置日志文件的路径和名称。

MyBatis 和 MyBatis-Plus

MyBatis
定义
MyBatis 是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 消除了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis 可以通过简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects)映射成数据库中的记录。

主要特点
轻量级:MyBatis 是一个轻量级的框架,不会占用太多的系统资源。
灵活:支持XML和注解两种方式配置SQL语句,可以根据项目需求选择合适的方式。
强大的映射功能:支持一对一、一对多、多对多等复杂关系的映射。
动态SQL:提供强大的动态SQL生成功能,可以根据条件动态生成SQL语句。
缓存机制:支持一级缓存和二级缓存,可以显著提高查询性能。
事务管理:支持事务管理,确保数据的一致性和完整性。
使用场景
当你需要对SQL进行精细控制时,MyBatis 是一个很好的选择。
适用于中小型项目,特别是那些需要高度定制化SQL的项目。
适合与Spring框架集成,形成完整的Web应用开发框架。
MyBatis-Plus
定义
MyBatis-Plus(简称MP)是MyBatis的增强工具,旨在简化开发、提高效率。它在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis-Plus 提供了许多开箱即用的功能,如CRUD操作、乐观锁、逻辑删除、分页等,使得开发者可以更加专注于业务逻辑的实现。

主要特点
无侵入:MyBatis-Plus 在MyBatis的基础上进行了扩展,完全兼容MyBatis的所有特性。
性能监控:内置性能分析插件,可以监控SQL执行时间,帮助优化性能。
内置分页插件:提供了一套强大的分页插件,支持多种数据库。
代码生成器:内置代码生成器,可以快速生成实体类、Mapper接口、Mapper XML文件等。
CRUD操作:提供了丰富的CRUD操作,无需编写SQL语句即可完成基本的增删改查操作。
乐观锁:支持乐观锁机制,防止并发更新数据时出现的问题。
逻辑删除:支持逻辑删除,可以在数据库中保留数据的同时标记为已删除。
全局配置:支持全局配置,可以统一管理一些常用的配置项。
使用场景
当你需要快速开发一个项目,且项目中包含大量的CRUD操作时,MyBatis-Plus 是一个非常好的选择。
适用于大型项目,特别是那些需要高效开发、减少重复代码的项目。
适合与Spring Boot框架集成,可以快速搭建企业级应用。
对比
特性 MyBatis MyBatis-Plus
灵活性 高 中
学习曲线 中 低
内置功能 基础 丰富(如分页、代码生成等)
性能监控 无 内置
代码生成 无 内置
CRUD操作 手动编写SQL 内置
乐观锁 无 支持
逻辑删除 无 支持

普通查询和分页查询

分页查询
https://lxblog.com/qianwen/share?shareId=b8a915f8-46de-492f-8a3e-b42b3a9d068b

普通查询
定义: 普通查询是指一次性从数据库中检索所有符合条件的数据记录。
特点:
一次性加载: 所有符合条件的数据都会被一次性加载到内存中。
适用场景: 数据量较小的情况,或者不需要对数据进行分页展示的场景。
优点: 实现简单,易于理解。
缺点: 当数据量较大时,可能导致性能问题(如内存不足、响应时间长)。

1
public List<NxSystemFileInfo> findAll();

在这个方法中,findAll() 方法会返回所有 NxSystemFileInfo 记录的列表。
2. 分页查询
定义: 分页查询是指将大量数据分成多个小部分(称为“页”),每次只加载其中的一部分数据。
特点
按需加载: 只加载当前需要显示的那一部分数据,减少内存占用。
适用场景: 大数据量的情况,需要在前端进行分页展示的场景。
优点: 提高性能,减少服务器压力,提高用户体验。
缺点: 实现相对复杂一些,需要额外的参数来控制分页逻辑。
示例:

1
public PageInfo<NxSystemFileInfo> findPage(String name, Integer pageNum, Integer pageSize);

在这个方法中,findPage() 方法接受三个参数:
name: 文件名关键字,用于过滤数据。
pageNum: 当前页码。
pageSize: 每页显示的记录数。
方法返回一个

1
PageInfo<NxSystemFileInfo> 

对象,该对象包含了分页信息(如总记录数、总页数、当前页数据等)。

  1. 分页查询的优势
    3.1 性能优化
    减少内存占用: 只加载当前页的数据,避免一次性加载大量数据导致内存溢出。
    快速响应: 加载较少的数据可以显著减少响应时间,提升用户体验。
    3.2 用户体验
    易用性: 用户可以通过翻页浏览大量数据,而无需等待所有数据加载完成。
    可读性: 分页显示使得数据更加清晰有序,便于用户查找和管理。
    3.3 减少带宽消耗
    高效传输: 只传输当前页的数据,减少了不必要的网络流量。

数据库

数据库表解读

管理员信息表 (admin_info):
包含管理员的基本信息,如姓名、密码、昵称、性别、年龄、生日、手机号、地址、编号、邮箱、身份证、权限等级、账户等。
level 字段表示管理员的权限等级,默认值为1。
公告信息表 (advertiser_info):
包含公告的名称、内容、发布时间等信息。
菜谱大类信息表 (classify_info):
包含菜谱大类的名称和描述。
收藏信息表 (collect_info):
记录用户的收藏信息,包括收藏的名称、时间、菜谱ID、笔记ID、用户ID、用户等级等。
笔记评论信息表 (comment_notes_info):
记录笔记的评论信息,包括评论内容、评论时间、评论人、关联的模块ID等。
食材信息表 (foods_material_info):
包含食材的名称、描述、文件ID、文件名、上传人、用户等级、用户ID等。
菜谱信息表 (foods_menu_info):
包含菜谱的名称、描述、菜谱标签、菜谱小类ID、文件ID、文件名、上传人、用户等级、用户ID等。
趣味答题信息表 (message_info):
包含趣味答题的名称、回答内容、回答时间等信息。
饮食资讯信息表 (news_info):
包含饮食资讯的名称和内容。
笔记信息表 (notes_info):
包含笔记的名称、上传时间、用户ID、内容、审核状态等。
文件信息表 (nx_system_file_info):
包含文件的原始文件名和存储文件名。
笔记点赞信息表 (praise_info):
记录笔记的点赞信息,包括名称、时间、笔记ID、菜谱ID、用户ID、用户等级等。
菜谱小类信息表 (sub_classify_info):
包含菜谱小类的名称、描述、菜谱大类ID等。
用户信息表 (user_info):
包含用户的基本信息,如姓名、密码、昵称、性别、年龄、生日、手机号、地址、邮箱、身份证、权限等级等。
level 字段表示用户的权限等级,默认值为2

控制器文及其相关文件

负责 辅助信息 相关的控制器,包含:
MenuController (2)
EchartsController (2)
ClassifyInfoController (8)
NxSystemFileController (6)
AdvertiserInfoController (8)
SubClassifyInfoController (8)

管理页面菜单动态渲染模块

功能描述

根据用户不同身份加载后端管理系统菜单栏
功能描述:在用户在首页中点击“进入后台系统”时候,根据用户的普通身份或管理员身份加载对应的后台管理系统页面,为不同类型的用户提供对应的功能展示。
代码描述:在用户要进入管理系统时,前端向后端发送请求获取用户类型信息。
后端首先获取当前用户的信息,管理员用户,权限级别为1(level = 1)(未登录的用户在首页不会看到“进入后台系统”的标签,所以不用担心这里会出问题);如果是普通用户,那么权限级别为2(level = 2)。然后根据用户权限级别(level=1为管理员,2为普通用户)生成不同的菜单项信息到后端要返回前端的数据数组中。接着通过 getJsonObject()方法将数据数组添加到要返回给前端的对象中,最后返回一个包含菜单项信息的 JSONObject。
后端返回的用户信息解构后赋值到authority中,然后根据authority的数据进行v-if判断菜单栏是否渲染。

控制器类 MenuController.java

这是一个用于生成菜单界面的控制器,它基于用户的不同权限级别显示不同的菜单项,并且还提供了一个获取总记录数的API。
getMenu 方法:根据用户权限级别生成不同的菜单项,并返回JSON格式的菜单数据。
getJsonObject 方法:辅助方法,用于生成单个菜单项的JSON对象。

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
@RestController//负责处理前端请求,并返回导航菜单数据
public class MenuController {

@Resource//这是一个依赖注入注解,用于自动装配Bean。这里用于注入各种业务逻辑服务实现类。
private AdminInfoServiceimpl adminInfoService;
@Resource
private UserInfoServiceimpl userInfoService;
@Resource
private ClassifyInfoServiceimpl classifyInfoService;
@Resource
private SubClassifyInfoServiceimpl subClassifyInfoService;
@Resource
private CollectInfoServiceimpl collectInfoService;
@Resource
private PraiseInfoServiceimpl praiseInfoService;
@Resource
private NewsInfoServiceimpl newsInfoService;
@Resource
private AdvertiserInfoServiceimpl advertiserInfoService;
@Resource
private MessageInfoServiceimpl messageInfoService;


@GetMapping(value = "/getMenu", produces="application/json;charset=UTF-8")
public String getMenu(HttpServletRequest request) {
//HTTP请求中获取用户会话信息,检查是否有登录用户
Account account = (Account) request.getSession().getAttribute("user");

Integer level;
if (account == null) {
level = 1;
} else {
level = account.getLevel();
}
// 创建一个JSON对象用于存储导航菜单项
JSONObject obj = new JSONObject();
obj.putOpt("code", 0);
obj.putOpt("msg", "");
JSONArray dataArray = new JSONArray();
// 添加系统首页菜单项
dataArray.add(getJsonObject("/", "系统首页", "layui-icon-home", "/"));
// 创建信息管理菜单项
JSONObject tableObj = new JSONObject();
tableObj.putOpt("title", "信息管理");
tableObj.putOpt("icon", "layui-icon-table");
// 根据用户权限级别决定是否添加信息管理菜单项
if (1 == level) {
JSONArray array = new JSONArray();
//getJsonObject方法创建每个子菜单项,并将其添加到array中
array.add(getJsonObject("adminInfo", "管理员信息", "layui-icon-table", "adminInfo"));
array.add(getJsonObject("userInfo", "用户信息", "layui-icon-table", "userInfo"));
array.add(getJsonObject("classifyInfo", "菜谱大类信息", "layui-icon-table", "classifyInfo"));
array.add(getJsonObject("subClassifyInfo", "菜谱小类信息", "layui-icon-table", "subClassifyInfo"));
array.add(getJsonObject("foodsMenuInfo", "菜谱信息", "layui-icon-table", "foodsMenuInfo"));
array.add(getJsonObject("foodsMaterialInfo", "食材信息", "layui-icon-table", "foodsMaterialInfo"));
array.add(getJsonObject("collectInfo", "收藏信息", "layui-icon-table", "collectInfo"));
array.add(getJsonObject("praiseInfo", "笔记点赞信息", "layui-icon-table", "praiseInfo"));
array.add(getJsonObject("notesInfo", "笔记信息", "layui-icon-table", "notesInfo"));
array.add(getJsonObject("newsInfo", "饮食资讯信息", "layui-icon-table", "newsInfo"));
array.add(getJsonObject("advertiserInfo", "公告信息", "layui-icon-table", "advertiserInfo"));
array.add(getJsonObject("messageInfo", "趣味答题信息", "layui-icon-table", "messageInfo"));
array.add(getJsonObject("accountAdminInfo", "个人信息", "layui-icon-user", "accountAdminInfo"));
tableObj.putOpt("list", array);
//将array设置为tableObj的list属性,以便在前端展示这些子菜单项
}
if (2 == level) {
JSONArray array = new JSONArray();
array.add(getJsonObject("foodsMenuInfo", "菜谱信息", "layui-icon-table", "foodsMenuInfo"));
array.add(getJsonObject("notesInfo", "笔记信息", "layui-icon-table", "notesInfo"));
array.add(getJsonObject("accountUserInfo", "个人信息", "layui-icon-user", "accountUserInfo"));
tableObj.putOpt("list", array);
}


dataArray.add(tableObj);
dataArray.add(getJsonObject("notesInfoComment", "笔记评论", "layui-icon-group", "notesInfoComment"));

dataArray.add(getJsonObject("updatePassword", "修改密码", "layui-icon-password", "updatePassword"));
dataArray.add(getJsonObject("login", "退出登录", "layui-icon-logout", "login"));

obj.putOpt("data", dataArray);
return obj.toString();
}

private JSONObject getJsonObject(String name, String title, String icon, String jump) {
JSONObject object = new JSONObject();
object.putOpt("name", name);
object.putOpt("title", title);
object.putOpt("icon", icon);
object.putOpt("jump", jump);
return object;
}

前端代码

获取用户的权限信息的get请求

1
2
3
4
5
6
7
8
9
10
11
12
created: function () {
axios.get("/authority").then(res => {
/*如果res.data.code等于'0',表示请求成功。
将res.data.data赋值给this.authority,
更新Vue实例中的authority数据属性*/
if (res.data.code === '0') {
this.authority = res.data.data;
} else {
msg('error', res.data.msg);
}
});

html动态渲染部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<li>
<a href="#"><i class="fa fa-yelp "></i>信息展示 <span class="fa arrow"></span></a>
<ul class="nav nav-second-level collapse in">
<li v-if="authority.indexOf(1) != -1">
<a href="adminInfo.html"><i class="fa fa-table"></i>管理员信息</a>
</li>
<li v-if="authority.indexOf(2) != -1">
<a href="userInfo.html"><i class="fa fa-table"></i>用户信息</a>
</li>
<li v-if="authority.indexOf(3) != -1">
<a href="classifyInfo.html"><i class="fa fa-table"></i>菜谱大类信息</a>
</li>
<li v-if="authority.indexOf(4) != -1">
<a href="subClassifyInfo.html"><i class="fa fa-table"></i>菜谱小类信息</a>
</li>
<li v-if="authority.indexOf(5) != -1">
<a href="foodsMenuInfo.html"><i class="fa fa-table"></i>菜谱信息</a>
</li>
</ul>

classifyInfo.html

1
2
3
4
5
6
7
8
9
10
       <div class="header-right">
<a href="javascript:void(0)" class="btn btn-info" title="跳转到个人信息" @click="personalPage">
<i class="fa fa-user fa-2x"></i>
</a>
</div>
<!--
使用javascript:void(0)防止页面跳转
javascript:void(0) 是一个常用的技巧,用于在 HTML 中创建一个不会导致页面跳转的链接。具体解释如下:
javascript: - 这是一个伪协议,告诉浏览器后面的内容是一段 JavaScript 代码。
vo-->

Echarts图标生成模块

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="panel panel-info">
<div class="panel-heading">
数据图表
<select style="margin-left: 10px" @change="drawLine"
v-model="echartsModel">
<option v-for="item in echartsModelOptions" :key="item.value" :value="item.value">{{item.label}}</option>
</select>
<select style="float:right" @change="selectEchartsType" v-model="echartsType">
<option value="pie">饼图</option>
<option value="bar">柱状图</option>
</select>
</div>
<div class="panel-body">
<!-- 为ECharts准备一个具备大小(宽高)的盒子 -->
<div id="main" style="width: 100%;height:400px;"></div>
</div>
</div>
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
methods: {
//发起HTTP请求获取数据并绘制图标发送GET请求,初始化ECharts实例并设置图表选项
drawLine() {
axios.get('/echarts/get/' + this.echartsModel).then(res => {
// 基于准备好的dom,初始化echarts实例
this.echartsShowArr = res.data.data;
if (this.echartsShowArr.length) {
let myChart = echarts.init(document.getElementById('main'));
let option = this.getEchartsType(this.echartsShowArr, this.echartsType);
myChart.setOption(option, true);
}
});
},
//切换echarts图表类型
//当用户选择不同的图表类型时,更新图表显示
selectEchartsType() {
let myChart = echarts.init(document.getElementById('main'));
let option = this.getEchartsType(this.echartsShowArr, this.echartsType);
myChart.setOption(option, true);
},
//根据类型获取echarts配置
//遍历echarts数据数组,根据指定的图表类型,从数据数组中找到匹配的图表配置并返回
getEchartsType(echartsArr, type) {
for (let item of echartsArr) {
if (item.series[0].type === type) {
return item;
}
}
},

控制器EchartsController.java

获取ECharts数据:
getEchartsData方法接收一个路径参数modelName,根据不同的模型名称调用相应的服务方法获取数据。
对于每个模型,统计性别分布或分类数量,并调用getPieData和getBarData方法生成饼图和柱状图数据。
最后将生成的图表数据列表封装在Result对象中返回。
获取选项列表:
getOptions方法返回一个包含所有可用模型名称及其标签的列表,用于前端选择。
生成图表数据:
getPieData方法生成饼图数据,包括标题、系列数据、图例等。
getBarData方法生成柱状图数据,包括标题、X轴数据、Y轴数据、图例等。

ClassifyInfo菜谱大类分类模块

功能思想

1) 菜谱分类逻辑
在后端管理系统中,菜单栏有“菜谱大类信息”和“菜谱小类信息”两栏。管理员用户可以对菜谱分类进行管理。菜谱分类包括菜谱大类和菜谱小类,菜谱小类是菜谱大类的详细分类。这样分类在数据增加后可以对美食进行详细的划分,让数据更有条理,给用户更好的使用体验。
2) 菜谱的增删查改
在菜谱大类信息和菜谱小类信息中,都可以实现对对应分类的添加、搜索、编辑、删除的操作。
3) 能供用excel表格批量导入菜谱分类
在菜谱大类信息和菜谱小类信息亮栏中,提供excel模板下载的功能,下载模板后通过在模板上编写要添加的分类信息和对应的描述,再将写好后的模板excel表格文件进行批量导入,可以实现一次性批量增加多条分类信息。

前端js模块

js代码

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<script>
function msg(type, msg) {
Vue.prototype.$message({
type: type, // success(成功)、warning(警告), error(错误)
message: msg,
duration: 2000,
offset: 100,
center: true
})
}

new Vue({
el: '#wrapper',
data: {

authority: [],
permission: [],
storeUser: JSON.parse(localStorage.getItem("user")),
name: '',
user: {},

objs: [],
pageInfo: {},
preActive: true,
nextActive: true,
entity: {
sex: '男'
}
},
created: function () {

this.loadTable(1);//加载第一页的表格数据
//获取用户信息
this.user = JSON.parse(localStorage.getItem('user'));
//获取权限信息
axios.get("/permission/3").then(res => {
if (res.data.code === '0') {
this.permission = res.data.data;
} else {
msg('error', res.data.msg);
}
});
//获取权限组信息
axios.get("/authority").then(res => {
if (res.data.code === '0') {
this.authority = res.data.data;
} else {
msg('error', res.data.msg);
}
});


},
methods: {

loadTable(pageNum) {
let name = this.name === '' ? "all" : this.name;
axios.get("/classifyInfo/page/" + name + "?pageNum=" + pageNum).then(res => {
if (res.data.code === '0') {
this.objs = res.data.data.list;
this.pageInfo = res.data.data;
this.preActive = !(this.pageInfo.hasPreviousPage);
this.nextActive = !(this.pageInfo.hasNextPage);
} else {
msg('error', res.data.msg);
}
});
},
init(o) {
if (this.permission.indexOf(3) === -1) {
msg('warning', '你没有权限操作');
return;
}
this.entity = JSON.parse(JSON.stringify(o));
$('#mod').modal('show');
},
add() {
if (this.permission.indexOf(1) === -1) {
msg('warning', '你没有权限操作');
return;
}
this.entity = {sex:'男'};
$('#mod').modal('show');
},
update() {
if (checkValid(this.entity)) {
if (!this.entity.id) {
axios.post(
"/classifyInfo", this.entity
).then(res => {
if (res.data.code === '0') {
msg('success', '添加成功');
$('#mod').modal('hide');
this.loadTable(1);
} else {
msg('error', res.data.msg);
}
})
} else {
axios.put(
"/classifyInfo", this.entity
).then(res => {
if (res.data.code === '0') {
msg('success', '更新成功');
$('#mod').modal('hide');
this.loadTable(1);
} else {
msg('error', res.data.msg);
}
})
}
}
},
del(id) {
if (this.permission.indexOf(2) === -1) {
msg('warning', '你没有权限操作');
return;
}
let _this = this;
this.$confirm('确认删除?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
cancelButtonClass: 'btn-custom-cancel',
type: 'warning'
}).then(function(){
axios.delete("/classifyInfo/" + id).then(res => {
if (res.data.code === '0') {
msg('success', '删除成功');
_this.loadTable(1);
} else {
msg('error', res.data.msg);
}
});
}).catch(function() {
console.log("取消成功!");
});
},
downModel() {
location.href = '/classifyInfo/getExcelModel';
},
clickUpload: function (e) {
if (this.permission.indexOf(1) === -1) {
msg('warning', '你没有权限操作');
e.preventDefault();
}
},
upload: function () {
let files = this.$refs.file.files;
for (let i = 0; i < files.length; i++) {
let formData = new FormData();
formData.append('file', files[i]);
axios.post('/classifyInfo/upload', formData, {
'Content-Type': 'multipart/form-data'
}).then(res => {
if (res.data.code === '0') {
msg('success', '批量导入成功');
this.loadTable(1);
} else {
msg('error', res.data.msg);
}
})
}
},
logout() {
logout();
}
}
})
</script>

实体类 entity

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
package com.example.entity;

import javax.persistence.*;
@Entity
@Table(name = "classify_info")
//将 Java 类映射到数据库中的 classify_info表
public class ClassifyInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "name")
private String name;
@Column(name = "descroiption")
private String descroiption;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return descroiption;
}
public void setDescription(String descroiption) {
this.descroiption = descroiption;
}

public void setId(Long id) {
this.id = id;
}
public Long getId() {
return this.id;
}

}

控制层 ClassifyInfoController.java

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

@RestController
@RequestMapping(value = "/classifyInfo")
//注解表示该控制器处理所有以 /classifyInfo 开头的HTTP请求。
public class ClassifyInfoController {
@Resource
private ClassifyInfoServiceimpl classifyInfoService;
@PostMapping
public Result<ClassifyInfo> add(@RequestBody ClassifyInfoVo classifyInfo) {
classifyInfoService.add(classifyInfo);
return Result.success(classifyInfo);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Long id) {
classifyInfoService.delete(id);
return Result.success();
}
@PutMapping
public Result update(@RequestBody ClassifyInfoVo classifyInfo) {
classifyInfoService.update(classifyInfo);
return Result.success();
}
@GetMapping("/{id}")
public Result<ClassifyInfo> detail(@PathVariable Long id) {
ClassifyInfo classifyInfo = classifyInfoService.findById(id);
return Result.success(classifyInfo);
}
@GetMapping
public Result<List<ClassifyInfoVo>> all() {
return Result.success(classifyInfoService.findAll());
}
@GetMapping("/page/{name}")
public Result<PageInfo<ClassifyInfoVo>> page(@PathVariable String name,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize,
HttpServletRequest request) {
return Result.success(classifyInfoService.findPage(name, pageNum, pageSize, request));
}

dao ClassifyInfoDao.java

1
2
@Repository
public interface ClassifyInfoDao extends Mapper<ClassifyInfo> {}

作用: 定义了一个数据访问层接口。
说明:
@Repository 注解表示该接口是一个数据访问层组件,Spring会自动扫描并管理这个接口。
extends Mapper 表示 ClassifyInfoDao 继承了 Mapper 接口,从而获得了基本的CRUD(创建、读取、更新、删除)操作能力。
Mapper 接口是TkMapper库提供的一个通用Mapper接口,包含了常用的数据库操作方法,如 select, insert, update, delete 等。
自定义方法 (findByName)

1
List<ClassifyInfoVo> findByName(@Param("name") String name);

作用: 定义了一个自定义查询方法,根据名称查找分类信息。
说明:
List: 返回类型为 ClassifyInfoVo 对象的列表。
findByName: 方法名称,表示根据名称查找分类信息。
@Param(“name”) String name: 参数注解,指定参数名称为 name。在MyBatis的XML映射文件中可以通过 #{name} 来引用这个参数。
这个方法需要在对应的XML映射文件中实现具体的SQL查询逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.dao;

import com.example.entity.ClassifyInfo;
import com.example.vo.ClassifyInfoVo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;

import java.util.List;

@Repository
public interface ClassifyInfoDao extends Mapper<ClassifyInfo> {
List<ClassifyInfoVo> findByName(@Param("name") String name);

}

vo ClassifyInfoVo.java

使用场景
服务层到表现层的数据传输:
在服务层中,你可以创建 ClassifyInfoVo 对象,并将其填充数据后返回给表现层(如控制器)。
这种方式可以确保表现层接收到的数据不仅包含分类的基本信息,还包含与其相关的子分类信息。
JSON 序列化:
当将 ClassifyInfoVo 对象序列化为 JSON 格式时,subList 字段也会被包含在内。
这对于需要展示分类及其子分类的应用程序非常有用。使用场景
服务层到表现层的数据传输:
在服务层中,你可以创建 ClassifyInfoVo 对象,并将其填充数据后返回给表现层(如控制器)。
这种方式可以确保表现层接收到的数据不仅包含分类的基本信息,还包含与其相关的子分类信息。
JSON 序列化:
当将 ClassifyInfoVo 对象序列化为 JSON 格式时,subList 字段也会被包含在内。
这对于需要展示分类及其子分类的应用程序非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.vo;

import com.example.entity.ClassifyInfo;

import java.util.List;

public class ClassifyInfoVo extends ClassifyInfo {
//subList用于存储子分类信息,有助于在返回给客户端的数据中包含更多的相关信息
List<SubClassifyInfoVo> subList;

public List<SubClassifyInfoVo> getSubList() {
return subList;
}

public void setSubList(List<SubClassifyInfoVo> subList) {
this.subList = subList;
}
}

服务层 ClassifyInfoService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.service;

import com.example.entity.ClassifyInfo;
import com.example.vo.ClassifyInfoVo;
import com.github.pagehelper.PageInfo;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

public interface ClassifyInfoService {
public ClassifyInfo add(ClassifyInfo classifyInfo);
public void delete(Long id);
public void update(ClassifyInfo classifyInfo);
public ClassifyInfo findById(Long id);
public List<ClassifyInfoVo> findAll();
public PageInfo<ClassifyInfoVo> findPage(String name, Integer pageNum, Integer pageSize, HttpServletRequest request);
public List<ClassifyInfoVo> findAllPage(HttpServletRequest request, String name);
}

impl ClassifyInfoServiceimpl.java

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
package com.example.service.impl;

import com.example.dao.ClassifyInfoDao;
import com.example.dao.SubClassifyInfoDao;
import com.example.service.ClassifyInfoService;
import com.example.vo.SubClassifyInfoVo;
import org.springframework.stereotype.Service;
import com.example.entity.ClassifyInfo;
import com.example.vo.ClassifyInfoVo;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Service
public class ClassifyInfoServiceimpl implements ClassifyInfoService {
@Resource //依赖注入,将 ClassifyInfoDao 和 SubClassifyInfoDao 的实例注入到当前类中
private ClassifyInfoDao classifyInfoDao;
@Resource
private SubClassifyInfoDao subClassifyInfoDao;
@Override
public ClassifyInfo add(ClassifyInfo classifyInfo) {
classifyInfoDao.insertSelective(classifyInfo);
return classifyInfo;
}
@Override
public void delete(Long id) {
classifyInfoDao.deleteByPrimaryKey(id);
}
@Override
public void update(ClassifyInfo classifyInfo) {
classifyInfoDao.updateByPrimaryKeySelective(classifyInfo);
}
@Override
public ClassifyInfo findById(Long id) {
return classifyInfoDao.selectByPrimaryKey(id);
}
@Override
public List<ClassifyInfoVo> findAll() {
List<ClassifyInfoVo> all = classifyInfoDao.findByName("all");
for (ClassifyInfoVo classifyInfoVo : all) {
List<SubClassifyInfoVo> subList = subClassifyInfoDao.findByClassifyId(classifyInfoVo.getId());
classifyInfoVo.setSubList(subList);
}
return all;
}

public PageInfo<ClassifyInfoVo> findPage(String name, Integer pageNum, Integer pageSize, HttpServletRequest request) {
PageHelper.startPage(pageNum, pageSize);
List<ClassifyInfoVo> all = findAllPage(request, name);
return PageInfo.of(all);
}

public List<ClassifyInfoVo> findAllPage(HttpServletRequest request, String name) {
return classifyInfoDao.findByName(name);
}

}

ClassifyInfoMapper.xml

ClassifyInfoMapper.xml部分实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--指定这个映射文件对应的 DAO 接口全限定名。MyBatis使用这个命名空间来找到对应的接口方法-->
<mapper namespace="com.example.dao.ClassifyInfoDao">
<!--.通过传入的name参数进行模糊查询
如果name为null、空字符串或'all',则不进行名称过滤
查询结果按照文件信息的ID进行排序-->
<select id="findByName" resultType="com.example.vo.ClassifyInfoVo">
select `classify_info`.* from `classify_info`
<!-- where 1 = 1 用于避免动态条件拼接时的语法错误,用于简化动态条件拼接。
<if> 元素: 动态 SQL 条件,只有在 name 参数不为空且不是 'all' 时才会添加到 SQL 语句中
-->
where 1 = 1
<if test="name != null and name != '' and name != 'all'">
and `classify_info`.`name` like concat('%', #{name}, '%')
</if>
order by `classify_info`.id
</select>
</mapper>

属性和方法理解

数据属性 (data)
authority:
存储权限组信息。
permission:
存储用户的权限信息。
storeUser:
从localStorage获取当前登录用户的信息。
name:
搜索关键字,用于过滤分类信息。
user:
当前登录用户的信息。
objs:
表格数据列表,存储从服务器获取的分类信息。
pageInfo:
分页信息,包含总页数、当前页码、是否有上一页和下一页等。
preActive 和 nextActive:
控制分页按钮的激活状态。当没有上一页时,preActive为false;当没有下一页时,nextActive为false。
entity:
当前操作的对象,初始值为{sex: ‘男’}。用于存储要添加或更新的分类信息。
生命周期钩子 (created)
作用:
在Vue实例创建完成后立即调用。
具体操作:
调用loadTable(1)加载第一页的数据。
获取当前登录用户的信息并存储在user变量中。
发送GET请求获取权限信息并存储在permission变量中。
发送GET请求获取权限组信息并存储在authority变量中。
方法 (methods)
loadTable(pageNum):
作用: 根据页码加载分类信息数据。
参数: pageNum - 页码。
过程:
如果name为空,则使用”all”作为搜索关键字。
发送GET请求到/classifyInfo/page/{name}?pageNum={pageNum}。
将响应数据中的列表存储在objs中,并将分页信息存储在pageInfo中。
更新preActive和nextActive的状态以控制分页按钮的激活状态。
init(o):
作用: 初始化模态框,填充数据并显示模态框。
参数: o - 要编辑的对象。
过程:
检查用户是否有相应的权限(权限索引为3)。如果没有权限,显示警告消息并返回。
使用深拷贝的方式将对象o赋值给entity。
显示模态框(通过调用$(‘#mod’).modal(‘show’))。
add():
作用: 显示空表单以便添加新记录。
过程:
检查用户是否有相应的权限(权限索引为1)。如果没有权限,显示警告消息并返回。
重置entity对象为{sex: ‘男’}。
显示模态框(通过调用$(‘#mod’).modal(‘show’))。
update():
作用: 保存或更新分类信息。
过程:
检查entity是否有效(假设有一个名为checkValid的函数进行验证)。
如果entity没有ID,则执行POST请求以添加新记录。
发送POST请求到/classifyInfo,携带entity数据。
根据响应结果,显示成功或错误消息,并关闭模态框,重新加载第一页的数据。
否则,执行PUT请求以更新现有记录。
发送PUT请求到/classifyInfo,携带entity数据。
根据响应结果,显示成功或错误消息,并关闭模态框,重新加载第一页的数据。
del(id):
作用: 删除指定ID的分类信息。
参数: id - 要删除的记录的ID。
过程:
检查用户是否有相应的权限(权限索引为2)。如果没有权限,显示警告消息并返回。
弹出确认对话框(通过调用this.$confirm),询问用户是否确认删除。
如果用户确认删除,则发送DELETE请求到/classifyInfo/{id}。
根据响应结果,显示成功或错误消息,并重新加载第一页的数据。
downModel():
作用: 下载Excel模板。
过程:
改变当前页面的URL为/classifyInfo/getExcelModel,触发下载操作。
clickUpload(e):
作用: 检查上传权限,阻止默认行为。
参数: e - 事件对象。
过程:
检查用户是否有相应的权限(权限索引为1)。如果没有权限,显示警告消息并阻止默认行为(通过调用e.preventDefault())。
upload():
作用: 批量上传文件。
过程:
获取文件输入元素中的所有文件。
对于每个文件,创建一个FormData对象并添加文件。
发送POST请求到/classifyInfo/upload,携带FormData对象。
根据响应结果,显示成功或错误消息,并重新加载第一页的数据。
logout():
作用: 登出当前用户。
过程:
调用全局的logout函数,通常用于清除会话信息并跳转到登录页面

entity部分理解

为什么要创建 entity 对象?
在Vue实例中,entity 对象用于存储当前正在编辑或添加的分类信息。以下是具体原因:

数据绑定:
Vue的核心特性之一是双向数据绑定。通过将表单字段绑定到 entity 对象的属性上,可以轻松地同步用户输入和应用程序状态。
简化操作:
当需要向服务器发送数据时,可以直接使用 entity 对象而不需要手动收集每个表单字段的值。这减少了代码的复杂性并降低了出错的可能性。
复用逻辑:
在添加新记录和更新现有记录时,可以使用相同的 entity 对象来存储数据。这样可以避免重复代码,提高代码的可维护性。
初始化状态:
通过初始化 entity 对象,可以确保每次打开模态框时都有一个干净的状态。这对于防止不同操作之间的数据混淆非常重要。
为什么性别默认设置为“男”?
将性别默认设置为“男”是为了提供一个合理的初始值,以便在用户尚未选择其他选项时有一个明确的状态。以下是具体原因:

用户体验:
提供一个默认值可以让用户更快地完成表单填写,特别是在用户通常会选择某个特定选项的情况下(例如大多数情况下可能是男性)。
减少错误:
如果没有默认值,用户可能会忘记选择性别,导致提交表单时出现验证错误。默认值可以帮助用户避免这种疏忽。
一致性:
默认值可以保持表单的一致性,确保所有新建记录都有一个有效的性别字段。

ClassifyInfoController接口

NxSystemFile文件管理相关模块

对应数据库实体类

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
package com.example.entity;

import javax.persistence.*;

@Table(name = "nx_system_file_info")
public class NxSystemFileInfo {
@Id //标记这个字段为主键
@GeneratedValue(strategy = GenerationType.IDENTITY)
//指定主键生成策略
//strategy 属性: 使用 GenerationType.IDENTITY 策略,
//表示主键值由数据库自动生成(通常是自动递增的 ID)。
private Long id;

@Column(name = "originName")
private String originName;
@Column(name = "fileName")
private String fileName;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getOriginName() {
return originName;
}

public void setOriginName(String originName) {
this.originName = originName;
}

public String getFileName() {
return fileName;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}
}

控制器NxSystemFileController

接口之间联系

用户通过 upload() 或 noticeUpload() 上传文件后,文件信息会被保存到数据库。
用户可以通过 filePage() 查看文件列表,点击某个文件后,可以通过 getById() 接口查看该文件的详细信息

upload()文件上传接口

实现思路:

  1. 定义文件的基础保存路径
    BASE_PATH 是文件存储的基本路径,使用 System.getProperty(“user.dir”) 获取项目的根目录,并在其下创建一个 static/file/ 目录来存储上传的文件
    1
    private static final String BASE_PATH = System.getProperty("user.dir") + "/src/main/resources/static/file/";
  2. 注入服务实现类
    使用 @Resource 注解注入 NxSystemFileInfoServiceimpl 实例,用于处理与文件相关的业务逻辑。
    1
    2
    @Resource
    private NxSystemFileInfoServiceimpl nxSystemFileInfoService;
  3. 创建文件上传的upload()接口
    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
    @PostMapping("/upload")
    public Result upload(MultipartFile file, HttpServletRequest request) throws IOException {
    //file.getOriginalFilename() 获取用户上传文件的原始文件名。
    String originName = file.getOriginalFilename();
    // 1. 先查询有没有相同名称的文件(这个没应用)
    // NxSystemFileInfo fileInfo = nxSystemFileInfoService.findByFileName(name);
    // if (fileInfo != null) {
    // throw new CustomException("1001", "文件名:\"" + name + "\"已存在");
    // }
    // 文件名加个时间戳,生成唯一文件名
    //使用 FileUtil.mainName(originName) 获取文件主名。
    //使用 System.currentTimeMillis() 添加时间戳以确保文件名唯一性。
    //使用 FileUtil.extName(originName) 获取文件扩展名。
    String fileName = FileUtil.mainName(originName) + System.currentTimeMillis() + "." + FileUtil.extName(originName);
    // 2. 文件上传
    //FileUtil.writeBytes(file.getBytes(), BASE_PATH + fileName) 将文件字节数组写入指定路径
    FileUtil.writeBytes(file.getBytes(), BASE_PATH + fileName);

    // 3. 信息入库,获取文件id
    NxSystemFileInfo info = new NxSystemFileInfo();
    info.setOriginName(originName);
    info.setFileName(fileName);
    //调用 nxSystemFileInfoService.add(info) 将文件信息存入数据库
    NxSystemFileInfo addInfo = nxSystemFileInfoService.add(info);
    //根据返回结果决定返回成功或失败的消息
    if (addInfo != null) {
    return Result.success(addInfo);
    } else {
    return Result.error("4001", "上传失败");
    }
    }

文件上传前端js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
upload: function () {
// 获取文件输入控件中的文件
let files = this.$refs.file.files;
// 遍历文件数组
for (let i = 0; i < files.length; i++) {
let formData = new FormData();
// 将文件添加到FormData对象中
formData.append('file', files[i]);
// 发起POST请求上传文件
axios.post('/files/upload', formData, {
'Content-Type': 'multipart/form-data'
}).then(res => {
// 文件上传成功后的处理
if (res.data.code === '0') {
msg('success','文件上传成功');
this.entity.fileId = res.data.data.id;
this.entity.fileName = res.data.data.originName;
} else {
msg('error', res.data.msg);
}
})
}
}

filePage() 分页查询文件信息接口

filePage() 接口用于分页查询文件信息,可以根据文件名关键字进行筛选。
该接口依赖于 nxSystemFileInfoService.findPage 方法,从数据库中获取文件信息列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 文件分页查询接口
*
* @param name 文件名关键字
* @param pageNum 当前页号
* @param pageSize 每页大小
* @return 返回文件信息分页结果
*/
@GetMapping("/page/{name}")
public Result<PageInfo<NxSystemFileInfo>> filePage(@PathVariable String name,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {

PageInfo<NxSystemFileInfo> pageInfo = nxSystemFileInfoService.findPage(name, pageNum, pageSize);
return Result.success(pageInfo);
}

download() 文件下载接口

实现思路:

  1. 接口通过id查找文件,@GetMapping(“/download/{id}”): 用于根据文件 ID 下载文件。
  2. 先验证文件id,然后根据id用服务层方法findById()查询数据库中的文件信息
  3. 通过FileUtil.readBytes 方法读取文件内容并将其转换为字节数组
  4. 设置响应头,指示浏览器下载文件,最后将文件内容写入响应输出流并返回给客户端
    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
    @GetMapping("/download/{id}")
    public void download(@PathVariable String id, HttpServletResponse response) throws IOException {
    // 检查文件ID是否为"null"
    if ("null".equals(id)) {
    throw new CustomException("1001", "您未上传文件");
    }
    //调用服务层方法 findById 根据文件 ID 查询文件信息
    // 根据文件ID查询数据库中的文件信息
    NxSystemFileInfo nxSystemFileInfo = nxSystemFileInfoService.findById(Long.parseLong(id));
    if (nxSystemFileInfo == null) {
    throw new CustomException("1001", "未查询到该文件");
    }

    // 读取文件内容
    //BASE_PATH 是文件存储的基本路径。
    //nxSystemFileInfo.getFileName() 获取文件的新文件名。
    //FileUtil.readBytes 方法读取文件内容并将其转换为字节数组
    byte[] bytes = FileUtil.readBytes(BASE_PATH + nxSystemFileInfo.getFileName());

    // 重置响应
    //清除响应中的所有缓存和设置
    //确保响应对象处于干净状态,避免之前的设置干扰当前操作
    response.reset();

    // 设置响应头,指示浏览器如何处理文件
    response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(nxSystemFileInfo.getOriginName(), "UTF-8"));
    response.addHeader("Content-Length", "" + bytes.length);

    // 获取响应的输出流,用于将文件内容写入响应
    //使用 BufferedOutputStream 包装 response.getOutputStream(),提高写入效率
    OutputStream toClient = new BufferedOutputStream(response.getOutputStream());

    // 设置响应内容类型
    // application/octet-stream 是通用的二进制流类型,适用于各种类型的文件。浏览器会根据这个类型决定如何处理文件(通常是下载)
    response.setContentType("application/octet-stream");

    // 将文件内容写入响应输出流并关闭输出流
    toClient.write(bytes);//将文件内容写入输出流
    toClient.flush();//刷新输出流,确保所有数据都被写出
    toClient.close();//关闭输出流,释放资源
    }

getById() 接口

  1. 功能:调用服务层方法 findById 根据文件ID 查询单个文件的信息,并验证文件是否存在。
  2. 返回值:返回包含文件信息的成功结果或抛出自定义异常
  3. 异常处理:
    如果文件 ID 无效或文件不存在,抛出自定义异常 CustomException。
    可能抛出 NumberFormatException 异常,表示文件 ID 格式不正确。
    可能抛出 IOException 异常,表示文件读写过程中发生错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping("/{id}")
public Result<NxSystemFileInfo> getById(@PathVariable String id) {
//将字符串形式的文件 ID 转换为长整型,并调用服务层方法 findById 查询文件信息
//Long.parseLong(id) 将字符串形式的文件 ID 转换为长整型
//nxSystemFileInfoService.findById(Long.parseLong(id)) 方法从数据库中查找指定 ID 的文件信息。
//返回的 NxSystemFileInfo 对象包含文件的各种属性,如原始文件名、新文件名等
NxSystemFileInfo nxSystemFileInfo = nxSystemFileInfoService.findById(Long.parseLong(id));
if (nxSystemFileInfo == null) {
throw new CustomException("1001", "数据库未查到此文件");
}
try {
FileUtil.readBytes(BASE_PATH + nxSystemFileInfo.getFileName());
} catch (Exception e) {
throw new CustomException("1001", "此文件已被您删除");
}
return Result.success(nxSystemFileInfo);
}

deleteFile删除文件接口

实现思路:

  1. 先接收文件ID
    @DeleteMapping(“/{id}”) 表示处理 DELETE 请求。
    @PathVariable String id:从 URL 中提取文件 ID。
    调用 nxSystemFileInfoService.findById(Long.parseLong(id)) 方法根据 ID 查找文件信息。
    如果找不到文件信息,则抛出异常
  2. 获取文件名,通过FileUtil.del(new File(BASE_PATH + name)) 删除文件
  3. 调用 nxSystemFileInfoService.delete(Long.parseLong(id)) 方法删除数据库中的记录
  4. 返回删除成功的消息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @GetMapping("/{id}")
    public Result<NxSystemFileInfo> getById(@PathVariable String id) {
    NxSystemFileInfo nxSystemFileInfo = nxSystemFileInfoService.findById(Long.parseLong(id));
    if (nxSystemFileInfo == null) {
    throw new CustomException("1001", "数据库未查到此文件");
    }
    try {
    FileUtil.readBytes(BASE_PATH + nxSystemFileInfo.getFileName());
    } catch (Exception e) {
    throw new CustomException("1001", "此文件已被您删除");
    }
    return Result.success(nxSystemFileInfo);
    }

NxSystemFileController控制器相关重点理解

  1. 如何实现文件上传功能?
    A: 文件上传功能通过 @PostMapping(“/upload”) 接口实现。首先获取用户上传文件的原始文件名,并生成一个唯一的文件名(添加时间戳)。然后将文件内容保存到指定路径,并将文件信息(如原始文件名和生成的文件名)存入数据库。最后返回上传结果。
  2. 为什么在文件上传时要生成唯一的文件名?
    A: 生成唯一的文件名是为了避免文件名冲突。通过在文件名后添加时间戳,可以确保每个上传的文件都有唯一的标识符,防止不同用户上传同名文件时发生覆盖。
  3. 如何实现图片上传并缩小尺寸?
    A: 图片上传并通过 Thumbnails 库缩小尺寸的功能通过 @PostMapping(“/notice/upload”) 接口实现。首先获取用户上传文件的原始文件名,并生成一个唯一的文件名。然后使用 Thumbnails 库将图片宽度缩小到 400 像素,并保存到指定路径。最后将文件信息存入数据库,并返回上传结果。
  4. 如何实现文件分页查询?
    A: 文件分页查询功能通过 @GetMapping(“/page/{name}”) 接口实现。首先从请求参数中获取文件名关键字、当前页号和每页大小。然后调用 nxSystemFileInfoService.findPage 方法进行分页查询,并返回分页结果。
  5. 如何实现文件下载功能?
    A: 文件下载功能通过 @GetMapping(“/download/{id}”) 接口实现。首先根据文件 ID 查找文件信息。如果找到文件信息,则读取文件内容并将其写入响应输出流,同时设置响应头以便客户端下载文件。如果找不到文件信息,则抛出异常。
  6. 如何实现文件删除功能?
    A: 文件删除功能通过 @DeleteMapping(“/{id}”) 接口实现。首先根据文件 ID 查找文件信息。如果找到文件信息,则先删除文件,再删除数据库中的记录。最后返回删除成功的消息。
  7. 如何确保文件操作的安全性和健壮性?
    A: 为了确保文件操作的安全性和健壮性,代码中进行了以下措施:

在文件上传时生成唯一的文件名,避免文件名冲突。
在文件下载和删除时检查文件是否存在,防止对不存在的文件进行操作。
使用 try-catch 块捕获可能出现的异常,并抛出自定义异常进行处理。
使用 FileUtil 工具类进行文件读写操作,简化文件操作并提高安全性。
8. upload() 接口和 noticeUpload() 接口的区别
用途不同:
upload() 用于通用文件上传。
noticeUpload() 特定用于上传通知公告的图片,并对其进行尺寸调整。
处理流程差异:
upload() 直接保存上传的文件。
noticeUpload() 在保存文件之前使用 Thumbnails 库缩小图片尺寸。
返回值不同:
upload() 返回的是文件信息对象。
noticeUpload() 返回的是包含文件 URL 和标题的映射对象。

服务层NxSystemFileService

NxSystemFileService.jaba代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface NxSystemFileInfoService {
//接口定义了一组抽象方法,这些方法需要由实现该接口的具体类来提供具体的实现。
public NxSystemFileInfo add(NxSystemFileInfo nxSystemFileInfo) ;

public void delete(Long id);

public void update(NxSystemFileInfo nxSystemFileInfo) ;

public NxSystemFileInfo findById(Long id);

public NxSystemFileInfo findByFileName(String name);

public List<NxSystemFileInfo> findAll();

public PageInfo<NxSystemFileInfo> findPage(String name, Integer pageNum, Integer pageSize) ;
}

impl类 NxSystemFileInfoServiceImpl.java

NxSystemFileInfoServiceImpl类,用于实现 NxSystemFileInfoService 接口

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
package com.example.service.impl;

import com.example.dao.NxSystemFileInfoDao;
import com.example.entity.NxSystemFileInfo;
import com.example.service.NxSystemFileInfoService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class NxSystemFileInfoServiceimpl implements NxSystemFileInfoService {
//实现 NxSystemFileInfoService 接口
@Value("${authority.info}")
//@Value 注解:@Value("${authority.info}")
//从配置文件(如 application.properties 或 application.yml)中注入属性值。
//private String authorityInfo;: 声明了一个字符串类型的字段 authorityInfo,用于存储从配置文件中读取的权限信息。
private String authorityInfo;
@Resource
private NxSystemFileInfoDao nxSystemFileInfoDao;
//用于依赖注入,将 NxSystemFileInfoDao 实例注入到当前类中。
//声明了一个 NxSystemFileInfoDao 类型的字段 nxSystemFileInfoDao,用于与数据库进行交互
@Override
public NxSystemFileInfo add(NxSystemFileInfo nxSystemFileInfo) {
//调用 DAO 接口的方法,将文件信息插入数据库。insertSelective 方法会在插入时忽略空字段。
//return nxSystemFileInfo: 返回插入后的文件信息对象
nxSystemFileInfoDao.insertSelective(nxSystemFileInfo);
return nxSystemFileInfo;
}
@Override
public void delete(Long id) {
nxSystemFileInfoDao.deleteByPrimaryKey(id);
}
@Override
public void update(NxSystemFileInfo nxSystemFileInfo) {
nxSystemFileInfoDao.updateByPrimaryKeySelective(nxSystemFileInfo);
}
@Override
public NxSystemFileInfo findById(Long id) {
return nxSystemFileInfoDao.selectByPrimaryKey(id);
}
@Override
public NxSystemFileInfo findByFileName(String name) {
return nxSystemFileInfoDao.findByFileName(name);
}
@Override
public List<NxSystemFileInfo> findAll() {
return nxSystemFileInfoDao.findByName("all");
}
/*
* String name: 文件名关键字。
Integer pageNum: 当前页码。
Integer pageSize: 每页显示的记录数。
实现细节:
PageHelper.startPage(pageNum, pageSize): 使用 PageHelper 设置分页参数。
nxSystemFileInfoDao.findByName(name): 调用 DAO 接口的方法,获取分页数据。
PageInfo.of(all): 将结果包装成 PageInfo 对象,方便前端展示分页信息*/

public PageInfo<NxSystemFileInfo> findPage(String name, Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<NxSystemFileInfo> all = nxSystemFileInfoDao.findByName(name);
return PageInfo.of(all);
}
}

dao层 NxSystemFileInfoDao

NxSystemFileInfoDao接口代码

  1. 继承 Mapper

    1
    public interface NxSystemFileInfoDao extends Mapper<NxSystemFileInfo>

    作用: 继承 tk.mybatis.mapper.common.Mapper 接口,提供了基本的 CRUD 操作方法(如 selectByPrimaryKey, insert, updateByPrimaryKey, deleteByPrimaryKey 等)
    解释: tk.mybatis.mapper.common.Mapper 是 MyBatis 的一个通用 Mapper,简化了常见的数据库操作

  2. @Repository注解
    》 将接口标识为 Spring 的仓库组件,Spring 会自动扫描并注册这个接口为 Bean。
    解释: 使得 NxSystemFileInfoDao 可以被其他组件(如 Service 层)通过依赖注入(Dependency Injection, DI)来使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.example.dao;

    import com.example.entity.NxSystemFileInfo;
    import org.apache.ibatis.annotations.Param;
    import org.springframework.stereotype.Repository;
    import tk.mybatis.mapper.common.Mapper;

    import java.util.List;

    @Repository //将接口标识为 Spring 的仓库组件,Spring 会自动扫描并注册这个接口为 Bean
    public interface NxSystemFileInfoDao extends Mapper<NxSystemFileInfo> {
    List<NxSystemFileInfo> findByName(@Param("name") String name);
    NxSystemFileInfo findByFileName(@Param("name") String name);
    }

NxSystemFileInfoMapper.xml

实现思路:先在mapper根元素中通过namespace属性: 指定了这个映射文件对应的 DAO 接口全限定名。MyBatis 使用这个命名空间来找到对应的接口方法。
然后在MyBatis映射文件NxSystemFileInfoMapper.xml文件中定义了与数据库交互的 SQL 查询语句,对应于 NxSystemFileInfoDao 接口中的方法。先通过传入的name参数进行模糊查询。如果name为null、空字符串或’all’,则不进行名称过滤,并把查询结果按照文件信息的ID进行排序

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 属性: 指定了这个映射文件对应的 DAO 接口全限定名。
MyBatis使用这个命名空间来找到对应的接口方法。-->
<mapper namespace="com.example.dao.NxSystemFileInfoDao">
<!--.通过传入的name参数进行模糊查询
如果name为null、空字符串或'all',则不进行名称过滤
查询结果按照文件信息的ID进行排序-->
<select id="findByName" resultType="com.example.entity.NxSystemFileInfo">
select `nx_system_file_info`.* from `nx_system_file_info`
where 1 = 1
<!-- where 1 = 1 用于避免动态条件拼接时的语法错误,用于简化动态条件拼接。
<if> 元素: 动态 SQL 条件,只有在 name 参数不为空且不是 'all' 时才会添加到 SQL 语句中
-->
<if test="name != null and name != '' and name != 'all'">
and `nx_system_file_info`.`originName` like concat('%', #{name}, '%')
</if>
order by `nx_system_file_info`.id
</select>

<select id="findByFileName" resultType="com.example.entity.NxSystemFileInfo">
select `nx_system_file_info`.* from `nx_system_file_info`
where `nx_system_file_info`.`fileName` = #{name}
</select>
</mapper>