全栈项目篇-食悦平台(重后端)
写在前面
这篇文章记录学校课程,web全栈项目中我负责的部分,以后端为主(因为我后端的知识遗忘太多了..)
重要文件解读
常见注解
- @Resource:
这是一个 Java 注解,用于依赖注入。它类似于 Spring 的 @Autowired,但更通用,支持更多的注入方式。 - @Service
标记这是一个
Spring 服务组件。Spring 会自动扫描并管理这个类的实例 - @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 接口中的方法一一对应
控制层
- 作用: 处理 HTTP 请求和响应。
将业务逻辑交给 Service 层处理
返回业务结果 - 特点:
处理 HTTP 请求:接收客户端的请求并返回响应。
路由和参数解析:根据 URL 和请求参数调用相应的方法。
调用 Service 层:将具体的业务逻辑交给 Service 层处理。
返回结果:将 Service 层的结果封装成 HTTP 响应返回给客户端。
服务层
- 作用:处理业务逻辑:实现具体的业务功能。
调用 DAO 层:与数据库交互,执行 CRUD 操作
返回响应 - 特点
业务逻辑处理:实现应用程序的核心业务逻辑。
事务管理:管理数据库事务,确保数据的一致性。
权限控制:检查用户是否有权执行某些操作。
数据校验:验证输入数据的有效性和完整性。
调用 DAO 层:与数据库交互,执行 CRUD 操作。
抽象接口:提供清晰的业务接口,便于测试和维护
dao层
- 作用:与数据库交互:执行 SQL 操作,如查询、插入、更新和删除。
提供基础 CRUD 操作:通过继承通用 Mapper 接口简化操作
返回数据结果 - 特点
与数据库交互:执行 SQL 操作,如查询、插入、更新和删除。
CRUD 操作:提供基础的增删改查方法。
自定义查询:可以根据需要编写自定义的查询方法。
隔离数据库细节:隐藏底层数据库的具体实现细节,使得业务逻辑层不直接依赖数据库。
提高可维护性:集中管理数据库操作,减少重复代码
三者关系
- 控制层调用服务层:
Controller 接收请求后,调用 Service 层的方法处理业务逻辑。 - 服务层调用 DAO 层:
Service 层在处理业务逻辑时,调用 DAO 层的方法与数据库交互。 - 数据流:
请求 -> 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> |
对象,该对象包含了分页信息(如总记录数、总页数、当前页数据等)。
- 分页查询的优势
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 | //负责处理前端请求,并返回导航菜单数据 |
前端代码
获取用户的权限信息的get请求
1 | created: function () { |
html动态渲染部分代码
1 | <li> |
classifyInfo.html
1 | <div class="header-right"> |
Echarts图标生成模块
前端
1 | <div class="panel panel-info"> |
1 | methods: { |
控制器EchartsController.java
获取ECharts数据:
getEchartsData方法接收一个路径参数modelName,根据不同的模型名称调用相应的服务方法获取数据。
对于每个模型,统计性别分布或分类数量,并调用getPieData和getBarData方法生成饼图和柱状图数据。
最后将生成的图表数据列表封装在Result对象中返回。
获取选项列表:
getOptions方法返回一个包含所有可用模型名称及其标签的列表,用于前端选择。
生成图表数据:
getPieData方法生成饼图数据,包括标题、系列数据、图例等。
getBarData方法生成柱状图数据,包括标题、X轴数据、Y轴数据、图例等。
ClassifyInfo菜谱大类分类模块
功能思想
1) 菜谱分类逻辑
在后端管理系统中,菜单栏有“菜谱大类信息”和“菜谱小类信息”两栏。管理员用户可以对菜谱分类进行管理。菜谱分类包括菜谱大类和菜谱小类,菜谱小类是菜谱大类的详细分类。这样分类在数据增加后可以对美食进行详细的划分,让数据更有条理,给用户更好的使用体验。
2) 菜谱的增删查改
在菜谱大类信息和菜谱小类信息中,都可以实现对对应分类的添加、搜索、编辑、删除的操作。
3) 能供用excel表格批量导入菜谱分类
在菜谱大类信息和菜谱小类信息亮栏中,提供excel模板下载的功能,下载模板后通过在模板上编写要添加的分类信息和对应的描述,再将写好后的模板excel表格文件进行批量导入,可以实现一次性批量增加多条分类信息。
前端js模块
js代码
1 | <script> |
实体类 entity
1 | package com.example.entity; |
控制层 ClassifyInfoController.java
1 |
|
dao ClassifyInfoDao.java
1 |
|
作用: 定义了一个数据访问层接口。
说明:
@Repository 注解表示该接口是一个数据访问层组件,Spring会自动扫描并管理这个接口。
extends Mapper
Mapper 接口是TkMapper库提供的一个通用Mapper接口,包含了常用的数据库操作方法,如 select, insert, update, delete 等。
自定义方法 (findByName)
1 | List<ClassifyInfoVo> findByName(; String name) |
作用: 定义了一个自定义查询方法,根据名称查找分类信息。
说明:
List
findByName: 方法名称,表示根据名称查找分类信息。
@Param(“name”) String name: 参数注解,指定参数名称为 name。在MyBatis的XML映射文件中可以通过 #{name} 来引用这个参数。
这个方法需要在对应的XML映射文件中实现具体的SQL查询逻辑。
1 | package com.example.dao; |
vo ClassifyInfoVo.java
使用场景
服务层到表现层的数据传输:
在服务层中,你可以创建 ClassifyInfoVo 对象,并将其填充数据后返回给表现层(如控制器)。
这种方式可以确保表现层接收到的数据不仅包含分类的基本信息,还包含与其相关的子分类信息。
JSON 序列化:
当将 ClassifyInfoVo 对象序列化为 JSON 格式时,subList 字段也会被包含在内。
这对于需要展示分类及其子分类的应用程序非常有用。使用场景
服务层到表现层的数据传输:
在服务层中,你可以创建 ClassifyInfoVo 对象,并将其填充数据后返回给表现层(如控制器)。
这种方式可以确保表现层接收到的数据不仅包含分类的基本信息,还包含与其相关的子分类信息。
JSON 序列化:
当将 ClassifyInfoVo 对象序列化为 JSON 格式时,subList 字段也会被包含在内。
这对于需要展示分类及其子分类的应用程序非常有用。
1 | package com.example.vo; |
服务层 ClassifyInfoService.java
1 | package com.example.service; |
impl ClassifyInfoServiceimpl.java
1 | package com.example.service.impl; |
ClassifyInfoMapper.xml
ClassifyInfoMapper.xml部分实现代码
1 |
|
属性和方法理解
数据属性 (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 | package com.example.entity; |
控制器NxSystemFileController
接口之间联系
用户通过 upload() 或 noticeUpload() 上传文件后,文件信息会被保存到数据库。
用户可以通过 filePage() 查看文件列表,点击某个文件后,可以通过 getById() 接口查看该文件的详细信息
upload()文件上传接口
实现思路:
- 定义文件的基础保存路径
BASE_PATH 是文件存储的基本路径,使用 System.getProperty(“user.dir”) 获取项目的根目录,并在其下创建一个 static/file/ 目录来存储上传的文件1
private static final String BASE_PATH = System.getProperty("user.dir") + "/src/main/resources/static/file/";
- 注入服务实现类
使用 @Resource 注解注入 NxSystemFileInfoServiceimpl 实例,用于处理与文件相关的业务逻辑。1
2
private NxSystemFileInfoServiceimpl nxSystemFileInfoService; - 创建文件上传的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
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 | upload: function () { |
filePage() 分页查询文件信息接口
filePage() 接口用于分页查询文件信息,可以根据文件名关键字进行筛选。
该接口依赖于 nxSystemFileInfoService.findPage 方法,从数据库中获取文件信息列表
1 | /** |
download() 文件下载接口
实现思路:
- 接口通过id查找文件,@GetMapping(“/download/{id}”): 用于根据文件 ID 下载文件。
- 先验证文件id,然后根据id用服务层方法findById()查询数据库中的文件信息
- 通过FileUtil.readBytes 方法读取文件内容并将其转换为字节数组
- 设置响应头,指示浏览器下载文件,最后将文件内容写入响应输出流并返回给客户端
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
public void download( 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() 接口
- 功能:调用服务层方法 findById 根据文件ID 查询单个文件的信息,并验证文件是否存在。
- 返回值:返回包含文件信息的成功结果或抛出自定义异常
- 异常处理:
如果文件 ID 无效或文件不存在,抛出自定义异常 CustomException。
可能抛出 NumberFormatException 异常,表示文件 ID 格式不正确。
可能抛出 IOException 异常,表示文件读写过程中发生错误。
1 |
|
deleteFile删除文件接口
实现思路:
- 先接收文件ID
@DeleteMapping(“/{id}”) 表示处理 DELETE 请求。
@PathVariable String id:从 URL 中提取文件 ID。
调用 nxSystemFileInfoService.findById(Long.parseLong(id)) 方法根据 ID 查找文件信息。
如果找不到文件信息,则抛出异常 - 获取文件名,通过FileUtil.del(new File(BASE_PATH + name)) 删除文件
- 调用 nxSystemFileInfoService.delete(Long.parseLong(id)) 方法删除数据库中的记录
- 返回删除成功的消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Result<NxSystemFileInfo> getById( { 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控制器相关重点理解
- 如何实现文件上传功能?
A: 文件上传功能通过 @PostMapping(“/upload”) 接口实现。首先获取用户上传文件的原始文件名,并生成一个唯一的文件名(添加时间戳)。然后将文件内容保存到指定路径,并将文件信息(如原始文件名和生成的文件名)存入数据库。最后返回上传结果。 - 为什么在文件上传时要生成唯一的文件名?
A: 生成唯一的文件名是为了避免文件名冲突。通过在文件名后添加时间戳,可以确保每个上传的文件都有唯一的标识符,防止不同用户上传同名文件时发生覆盖。 - 如何实现图片上传并缩小尺寸?
A: 图片上传并通过 Thumbnails 库缩小尺寸的功能通过 @PostMapping(“/notice/upload”) 接口实现。首先获取用户上传文件的原始文件名,并生成一个唯一的文件名。然后使用 Thumbnails 库将图片宽度缩小到 400 像素,并保存到指定路径。最后将文件信息存入数据库,并返回上传结果。 - 如何实现文件分页查询?
A: 文件分页查询功能通过 @GetMapping(“/page/{name}”) 接口实现。首先从请求参数中获取文件名关键字、当前页号和每页大小。然后调用 nxSystemFileInfoService.findPage 方法进行分页查询,并返回分页结果。 - 如何实现文件下载功能?
A: 文件下载功能通过 @GetMapping(“/download/{id}”) 接口实现。首先根据文件 ID 查找文件信息。如果找到文件信息,则读取文件内容并将其写入响应输出流,同时设置响应头以便客户端下载文件。如果找不到文件信息,则抛出异常。 - 如何实现文件删除功能?
A: 文件删除功能通过 @DeleteMapping(“/{id}”) 接口实现。首先根据文件 ID 查找文件信息。如果找到文件信息,则先删除文件,再删除数据库中的记录。最后返回删除成功的消息。 - 如何确保文件操作的安全性和健壮性?
A: 为了确保文件操作的安全性和健壮性,代码中进行了以下措施:
在文件上传时生成唯一的文件名,避免文件名冲突。
在文件下载和删除时检查文件是否存在,防止对不存在的文件进行操作。
使用 try-catch 块捕获可能出现的异常,并抛出自定义异常进行处理。
使用 FileUtil 工具类进行文件读写操作,简化文件操作并提高安全性。
8. upload() 接口和 noticeUpload() 接口的区别
用途不同:
upload() 用于通用文件上传。
noticeUpload() 特定用于上传通知公告的图片,并对其进行尺寸调整。
处理流程差异:
upload() 直接保存上传的文件。
noticeUpload() 在保存文件之前使用 Thumbnails 库缩小图片尺寸。
返回值不同:
upload() 返回的是文件信息对象。
noticeUpload() 返回的是包含文件 URL 和标题的映射对象。
服务层NxSystemFileService
NxSystemFileService.jaba代码
1 | public interface NxSystemFileInfoService { |
impl类 NxSystemFileInfoServiceImpl.java
NxSystemFileInfoServiceImpl类,用于实现 NxSystemFileInfoService 接口
1 | package com.example.service.impl; |
dao层 NxSystemFileInfoDao
NxSystemFileInfoDao接口代码
继承 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,简化了常见的数据库操作@Repository注解
》 将接口标识为 Spring 的仓库组件,Spring 会自动扫描并注册这个接口为 Bean。
解释: 使得 NxSystemFileInfoDao 可以被其他组件(如 Service 层)通过依赖注入(Dependency Injection, DI)来使用1
2
3
4
5
6
7
8
9
10
11
12
13
14package 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;
//将接口标识为 Spring 的仓库组件,Spring 会自动扫描并注册这个接口为 Bean
public interface NxSystemFileInfoDao extends Mapper<NxSystemFileInfo> {
List<NxSystemFileInfo> findByName(; String name)
NxSystemFileInfo findByFileName(; String name)
}
NxSystemFileInfoMapper.xml
实现思路:先在mapper根元素中通过namespace属性: 指定了这个映射文件对应的 DAO 接口全限定名。MyBatis 使用这个命名空间来找到对应的接口方法。
然后在MyBatis映射文件NxSystemFileInfoMapper.xml文件中定义了与数据库交互的 SQL 查询语句,对应于 NxSystemFileInfoDao 接口中的方法。先通过传入的name参数进行模糊查询。如果name为null、空字符串或’all’,则不进行名称过滤,并把查询结果按照文件信息的ID进行排序
1 |
|