✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯 你正在阅读「Java项目-企悦抽」系列文章 🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🔥 弹简特 个人主页
❄️ 个人专栏直通车:
- 🔌 接口测试从入门到跑路
- ☕ 一个后端的 JavaEE 续命指南
- 🛜 网络原理续命手册
- ☕ Java项目-轻聊
✨ 靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🌟 博主简介:

文章目录
- 一、前言
- 二、创建奖品
-
- 1、上传图片
-
- [1.1 时序图](#1.1 时序图)
- [1.2 在服务层去新建一个服务用于专门保存图片](#1.2 在服务层去新建一个服务用于专门保存图片)
- [1.3 在配置文件中配置后天存所上传文件的目标目录](#1.3 在配置文件中配置后天存所上传文件的目标目录)
- [1.4 简单写一个控制层的文件上传接口并简单测试](#1.4 简单写一个控制层的文件上传接口并简单测试)
- 2、实现创建奖品
-
- [2.1 约定前后端交互接口](#2.1 约定前后端交互接口)
- [2.2 实现控制层](#2.2 实现控制层)
-
- [2.2.1 创建前端传递给后端的实体类Param实体](#2.2.1 创建前端传递给后端的实体类Param实体)
- [2.3 实现服务层](#2.3 实现服务层)
-
- [2.3.1 接口定义](#2.3.1 接口定义)
- [2.3.2 接口实现](#2.3.2 接口实现)
- [2.3.3 业务逻辑解释](#2.3.3 业务逻辑解释)
- [2.4 实现持久层](#2.4 实现持久层)
-
- [2.4.1 奖品数据库实体类](#2.4.1 奖品数据库实体类)
- [2.4.1 持久层Mapper接口和实现类](#2.4.1 持久层Mapper接口和实现类)
- [2.5 测试:`重点`](#2.5 测试:
重点)
- 3、实现创建奖品前端交互
- 三、获取奖品列表
-
- 1、时序图
- 2、约定前后端交互的接口
- 3、实现控制层
-
- [3.1 封装请求参数](#3.1 封装请求参数)
- [3.2 封装Result实体类](#3.2 封装Result实体类)
- [3.3 控制层代码](#3.3 控制层代码)
- 4、实现服务层
-
- [4.1 实现DTO实体类](#4.1 实现DTO实体类)
- [4.2 实现服务层的逻辑](#4.2 实现服务层的逻辑)
- 5、实现持久层的SQL编写
- 6、测试一下(使用Postman)
- 7、实现前后端交互
一、前言
铁汁们,回顾我们本项目的模块功能,咱们上一期将用户管理模块结束了,那么本期我们就进入奖品管理模块的实现
老铁们,咱们本期这个奖品管理和我们的人员管理是一样的。都是一个创建奖品和这个奖品列表。
你创建完奖品之后呢,你就给我自动跳转到奖品列表中去。
所以很多代码上的实现细节跟我们的人员管理差不太多,我们在这里呢会稍微的快一丢丢。
那很多人问,这个奖品管理有什么作用呢?
它的作用很简单,我们到时候创建活动的时候,你是需要去圈我选我们的奖品的。
所以我们先创建奖品,到时候就可以圈选它作为我们的奖项。
二、创建奖品
1、上传图片

这个创建奖品里面的其他数据倒是没问题,唯独这个上传图片需要我们做一下处理,因此,我们本地需要实现一个服务,来保存我们上传的图片而且上传完图片之后呢,将这个图片保存到我们的服务中。
1.1 时序图

1.2 在服务层去新建一个服务用于专门保存图片

代码解释:


接下来写他的实现类

文件的三个核心步骤:
1、你想把文件上传到后台哪一个目录下?:创建目录

2、创建索引:将你上传的文件的文件名 转换 为我们自己后台目录中的文件名
1)首先你得拿到你从你上传那个文件的原名称
2)然后你再把他的后缀给拿到过来
3)然后你再去生成你服务器中的文件名也就是索引
4)最后将你的这个索引和后缀名拼起来,就是我们的服务器中的文件名

3、把图片存起来

1.3 在配置文件中配置后天存所上传文件的目标目录
bash
## 文件上传 ##
# 目标路径
pic.local-path=E:/self_devp
# spring boot3 升级配置名:代表前端请求的时候会将他当做今天起资源去请求
spring.web.resources.static-locations=classpath:/static/,file:${pic.local-path}
1.4 简单写一个控制层的文件上传接口并简单测试

java
@RestController
public class PrizeController {
@Autowired
PictureService pictureService;
@RequestMapping("/pic/upload")
public String uploadPic(MultipartFile file) {
return pictureService.uploadPicture(file);
}
}
使用Postman测试

上传文件太大了,Spring Boot 默认不让传!
Spring Boot 默认上传文件最大限制:1MB你传的图片 / 文件 超过 1MB → 直接报错!
修改配置文件将文件的大小设置为10M
java
# 文件上传大小限制 默认是1M 我们将他设置为10M
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

再次重试

原因就是我们的配置文件的读取方法出错了
🔥 错误代码(就是这行!)
java
@Value("pic.local-path")
正确写法必须是:
java
@Value("${pic.local-path}")

修改之后再次重试


2、实现创建奖品
首先看时序图:

我们前端提交奖品信息之后,对于图片我们会存在服务器指定的硬盘下,那么对于我们的奖品信息,我们会将他存到数据库中(同时还要将你已经保存到后端的图片路径存到我们的数据库中),注意真实的图片是存到服务器硬盘空间的,而我们的服务器中存好的图片的路径会被存到数据库中,如此一来,到时候 我们前端去请求查询的时候,我们就直接查询的是数据库,然后读取到图片路径,再将我们的图片路径作为索引在磁盘上找到对饮的文件,而由于之前我们配置过
需要将你上传之后的图片作为一个静态资源供给前端访问,而且你的拦截器不要去拦截他,所以我们去端就会看得到我们存在后台的图片啦~~
2.1 约定前后端交互接口
接口地址 :
/prize/create请求方式 :
POST提交类型:form表单
表单参数1:param
json{"prizeName": "吹⻛机","description": "吹⻛机", "price": 100}表单参数2 :prizePic (文件)
值:Obj-C.jpg
接口响应:
json{ "code": 200, "data": 17, "msg": "" }
2.2 实现控制层
代码解释:



代码
java
/**
* 创建奖品
* RequestPart:用于接收表单数据的 multipart/form-data
*
* @param param
* @param picFile
* @return
*/
@RequestMapping("/prize/create")
public CommonResult<Long> createPrize(@Valid @RequestPart("param") CreatePrizeParam param,
@RequestPart("prizePic") MultipartFile picFile) {
//首先打印日志
logger.info("PrizeController createPrize 创建奖品的 CreatePrizeParam参数:{}",
JackSonUtil.writeValueAsString(param));//注意使用序列化,将Java对象变为JSON对象 到时候方便我们查看
return CommonResult.success(
prizeService.createPrize(param, picFile));
}
2.2.1 创建前端传递给后端的实体类Param实体

创建

核心解释知识:

代码
java
@Data
public class CreatePrizeParam implements Serializable {
//奖品名称
@NotBlank(message = "奖品名称不能为空")
private String prizeName;
//奖品描述
private String description;//可写可不写
//奖品价格
@NotNull(message = "奖品价格不能为空")
private BigDecimal price;
}
2.3 实现服务层
2.3.1 接口定义
java
/**
* @InterfaceName PrizeService
* @Description TODO 奖品服务层接口
* @Author zhongge
* @Version 1.0
*/
public interface PrizeService {
/**
* 创建单个奖品
* @param param 奖品属性
* @param picFile 奖品的图片
* @return 奖品id
*/
Long createPrize(CreatePrizeParam param, MultipartFile picFile);
}
2.3.2 接口实现

java
/**
* @ClassName PrizeServiceImpl
* @Description TODO 奖品服务层实现类
* @Author zhongge
* @Version 1.0
*/
@Service
public class PrizeServiceImpl implements PrizeService {
//图片服务
@Autowired
private PictureService pictureService;
@Autowired
private PrizeMapper prizeMapper;
/**
* 创建单个奖品
* @param param 奖品属性
* @param picFile 奖品的图片
* @return 创建好的奖品ID
*/
@Override
public Long createPrize(CreatePrizeParam param, MultipartFile picFile) {
//第一步:先上传图片:所以先将图片服务引过来
String s = pictureService.uploadPicture(picFile);
//咱将服务器中图片的索引给拿到 再和 图片信息一起存到数据库中去
//所以先构造一个DO
PrizeDO prizeDO = new PrizeDO();
prizeDO.setName(param.getPrizeName());
prizeDO.setImageUrl(s);
prizeDO.setPrice(param.getPrice());
prizeDO.setDescription(param.getDescription());
//第二步:将图片存到我们的数据库中
prizeMapper.insert(prizeDO);
return prizeDO.getId();
}
}
2.3.3 业务逻辑解释

2.4 实现持久层
2.4.1 奖品数据库实体类

java
@Data
@EqualsAndHashCode(callSuper = true)
public class PrizeDO extends BaseDO{
/**
* 奖品名
*/
private String name;
/**
* 图片索引
*/
private String imageUrl;
/**
* 价格
*/
private BigDecimal price;
/**
* 描述
*/
private String description;
}
我们数据库中有奖品表,所以需要将我们的奖品表呢对应的Java实体类DO实体给我创建出来
同时,对于数据库对应的实体类DO实体 也需要序列化,因为涉及到存库等等,总之网络传输,存库都需要序列化

2.4.1 持久层Mapper接口和实现类

java
@Mapper
public interface PrizeMapper {
@Insert("INSERT INTO prize(name,image_url,price,description)" +
" VALUES(#{name},#{imageUrl},#{price},#{description})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
int insert(PrizeDO prizeDO);
}
2.5 测试:重点

第一个知识:在精通Postman接口测试的时候形如这种既有表单又有文件的,我们的body选择的是form-data 那么具体请看图

第二个知识:先登录再将鉴权码带上

第三个知识点:解决序列化问题

解决序列化问题,直接按照以下步骤做,直接抄就行,我当时是借助AI的,解决成功了,就是序列化问题
在下面包下加一个converter包

在该包下写一个类MultipartJackson2HttpMessageConverter 用于序列化的
代码
java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
/**
* @ClassName MultipartJackson2HttpMessageConverter
* @Description TODO 文件上传序列化
* @Author zhongge
* @Version 1.0
*/
@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
protected MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
// MediaType.APPLICATION_OCTET_STREAM 表示这个转换器用于处理二进制流数据,通常用于文件上传。
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
// 转换器不用于写入(即不用于响应的序列化)
return false;
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
protected boolean canWrite(MediaType mediaType) {
return false;
}
}
重启服务器重新测试

3、实现创建奖品前端交互

OK,此时启动服务器,然后咱们测试一下:

数据库中也确实存进去了

三、获取奖品列表
1、时序图

这个获取奖品列表的功能包含一个分页逻辑,总之还是很简单的,具体的分页逻辑我有在图书管理系统中介绍过👉图书管理系统,可以去参考,思路以及逻辑都解释得很清楚。
2、约定前后端交互的接口
请求
/prize/find-list?currentPage=1&pageSize=10请求方式
GET响应 JSON 完整内容
json{ "code": 200, "data": { "total": 3, "records": [ { "prizeId": 17, "prizeName": "吹风机", "description": "吹风机", "price": 100, "imageUrl": "d11fa79c-9cfb-46b9-8fb6-3226ba1ff6d6.jpg" }, { "prizeId": 13, "prizeName": "华为手机", "description": "华为手机", "price": 5000, "imageUrl": "5a85034b-91b7-48fe-953d-67aef2bdcc2d.jpg" }, { "prizeId": 12, "prizeName": "咖啡机", "description": "家用咖啡机", "price": 3000, "imageUrl": "https://ts1.cn.mm.bing.net/th/id/RC.59493f741a4d956f354d241ec1034624?rik=JpdNO%2bfC3NMONw&riu=http%3a%2f%2fcdn02.ehaier.com%2fproduct%2f5600fa6c1a0a2ebc278b47e8_1200_1200.jpg&ehk=8MptQ5r5ILWiL4v%2f5mn3s0%2f1H05r1yp%2fL6feezFw89Q%3d&risl=&pid=ImgRaw&r=0" } ] }, "msg": "" }
3、实现控制层
3.1 封装请求参数
前端请求的时候要把当前页和当前页要显示的记录数给后端,后端根据他们去查询,就是说我前端要显示第几页,这页要显示多少数据,告诉后端之后,你后端就给我返回
于是我们将当前页currentPage和当前页要显示的记录数pageSize封装成一个类:PageParam
那么我就先构造这个请求参数类

代码
java
@Data
public class PageParam implements Serializable {
/**
* 当前页
*/
private Integer currentPage = 1;//写一个默认值,如果没有这个值就默认第一页
/**
* 每页显示的记录数
*/
private Integer pageSize = 10;//写一个默认值,如果没有这个值就默认每页显示10条
}
3.2 封装Result实体类
根据需求文档,你要返回什么?我要返回的是总数和当前页要显示的记录,那么这个我们也把他封装成一个类就好了喔~~

代码
java
@Data
public class FindPrizeListResult implements Serializable {
/**
* 总记录数
*/
private Long total;
/**
* 奖品列表
*/
private List<PrizeInfo> records;
}
那么集合里面的每一个元素都是PrizeInfo,我们把他定义为内部类
代码
java
@Data
public class FindPrizeListResult implements Serializable {
/**
* 总量
*/
private Integer total;
/**
* 当前列表
*/
private List<PrizeInfo> records;
@Data
public static class PrizeInfo implements Serializable {
/**
* 奖品id
*/
private Long prizeId;
/**
* 名称
*/
private String prizeName;
/**
* 描述
*/
private String description;
/**
* 价值
*/
private BigDecimal price;
/**
* 奖品图
*/
private String imageUrl;
}
}
3.3 控制层代码

java
/**
* 查询奖品列表(分页查询)
* @param param 页号和页码(当前页和当前页要显示的记录数)
* @return
*/
@RequestMapping("/prize/find-list")
public CommonResult<FindPrizeListResult> findPrizeList(PageParam param) {
//首先打印日志
logger.info("PrizeController findPrizeList 查询奖品的 PageParam参数:{}",
JackSonUtil.writeValueAsString(param));//注意使用序列化,将Java对象变为JSON对象 到时候方便我们查看
PageListDTO<PrizeDTO> pageListDTO = prizeService.findPrizeList(param);
return CommonResult.success(convertToFindPrizeListResult(pageListDTO));
}
/**
* 将从服务层得到的DTO 变为 前端需要的Result
* @param pageListDTO 服务层返回的分页数据
* @return 前端需要的分页数据
*/
private FindPrizeListResult convertToFindPrizeListResult(PageListDTO<PrizeDTO> pageListDTO) {
// 如果参数为空,抛出异常
if (null == pageListDTO) {
throw new ControllerException(ControllerErrorCodeConstants.FIND_PRIZE_LIST_ERROR);
}
// 创建返回结果对象
FindPrizeListResult result = new FindPrizeListResult();
// 设置总条数
result.setTotal(pageListDTO.getTotal());
// 获取原始的奖品列表
List<PrizeDTO> prizeDTOList = pageListDTO.getRecords();
// 创建空的奖品信息列表,用来存放转换后的数据
List<FindPrizeListResult.PrizeInfo> prizeInfoList = new ArrayList<>();
// 普通for循环,逐个转换
for (PrizeDTO prizeDTO : prizeDTOList) {
// 如果单条数据为空,跳过
if (prizeDTO == null) {
continue;
}
// 创建单个奖品信息对象
FindPrizeListResult.PrizeInfo prizeInfo = new FindPrizeListResult.PrizeInfo();
// 挨个赋值(和原来功能完全一样)
prizeInfo.setPrizeId(prizeDTO.getPrizeId());
prizeInfo.setPrizeName(prizeDTO.getName());
prizeInfo.setDescription(prizeDTO.getDescription());
prizeInfo.setImageUrl(prizeDTO.getImageUrl());
prizeInfo.setPrice(prizeDTO.getPrice());
// 把转换好的对象放进列表里
prizeInfoList.add(prizeInfo);
}
// 把转换好的列表设置到结果中
result.setRecords(prizeInfoList);
// 返回结果
return result;
}
4、实现服务层
4.1 实现DTO实体类
1)控制层需要的数据类型
java
@Data
public class PageListDTO<T> {
/**
* 总量
*/
private Integer total;
/**
* 当前页列表
*/
private List<T> records;
public PageListDTO(Integer total, List<T> records) {
this.total = total;
this.records = records;
}
}

2)奖品信息

代码
java
@Data
public class PrizeDTO {
/**
* 奖品Id
*/
private Long prizeId;
/**
* 奖品名
*/
private String name;
/**
* 图片索引
*/
private String imageUrl;
/**
* 价格
*/
private BigDecimal price;
/**
* 描述
*/
private String description;
}
3)一张图理清楚关系

如何将dto类型变为result类型?
就用下面这个函数就行了,那函数我们是写在控制层中,那这样的方法我们前面也写过很多次,每次我都会充分的解释。
java
private FindPrizeListResult convertToFindPrizeListResult(PageListDTO<PrizeDTO> pageListDTO) {
// 如果参数为空,抛出异常
if (null == pageListDTO) {
throw new ControllerException(ControllerErrorCodeConstants.FIND_PRIZE_LIST_ERROR);
}
// 创建返回结果对象
FindPrizeListResult result = new FindPrizeListResult();
// 设置总条数
result.setTotal(pageListDTO.getTotal());
// 获取原始的奖品列表
List<PrizeDTO> prizeDTOList = pageListDTO.getRecords();
// 创建空的奖品信息列表,用来存放转换后的数据
List<FindPrizeListResult.PrizeInfo> prizeInfoList = new ArrayList<>();
// 普通for循环,逐个转换
for (PrizeDTO prizeDTO : prizeDTOList) {
// 如果单条数据为空,跳过
if (prizeDTO == null) {
continue;
}
// 创建单个奖品信息对象
FindPrizeListResult.PrizeInfo prizeInfo = new FindPrizeListResult.PrizeInfo();
// 挨个赋值(和原来功能完全一样)
prizeInfo.setPrizeId(prizeDTO.getPrizeId());
prizeInfo.setPrizeName(prizeDTO.getName());
prizeInfo.setDescription(prizeDTO.getDescription());
prizeInfo.setImageUrl(prizeDTO.getImageUrl());
prizeInfo.setPrice(prizeDTO.getPrice());
// 把转换好的对象放进列表里
prizeInfoList.add(prizeInfo);
}
// 把转换好的列表设置到结果中
result.setRecords(prizeInfoList);
// 返回结果
return result;
}

别看代码很多,其实逻辑很简单,就是一个简单的类型转换这个没什么多说的,多多品一下就好了😄
4.2 实现服务层的逻辑
怎么这里就是处理前端的请求,并将分页数据封装出来

如何转换?如图所示:

从上面可以看出,咱们前端需要有一个总数,这个总数很好查,直接查询就行。
另外一个是前端需要为当前页的记录,一个条件是前端给你一个当前的页号,以及给你一个当前页要展示的多少记录数
那么你根据他给我从数据库中查询,数据库中查询,我们使用的是分页查询,分页查询两个参数,第1个是偏移量,第2个是从偏移量开始要查询多少条。
第二个参数很简单,就是你前端给我的当前页,要展示的记录总数,就是你要查询的条数。
对于第1个参数偏移量来说,就需要我们去计算一下,这个过程我们在之前学开发基础的时候推过,
可参考博客👉分页
那么咱们的偏移量offset怎么去计算呢?它的公式是可以推的,公式就是当前页减去一,再去乘以当前页要显示的记录数,如果忘记了不知道可以自己去推,或者的话直接问ai,现在ai那么好用,一定要提效。
咱们把这个计算的方法封装在我们的请求参数类里PageParam里面:
java
/**
* @ClassName PageParam
* @Description TODO 分页查询参数
* @Author zhongge
* @Version 1.0
*/
@Data
public class PageParam implements Serializable {
/**
* 当前页
*/
private Integer currentPage = 1;//写一个默认值,如果没有这个值就默认第一页
/**
* 每页显示的记录数
*/
private Integer pageSize = 10;//写一个默认值,如果没有这个值就默认每页显示10条
/**
* 获取偏移量
*
* @return
*/
public Integer offset() {
return (currentPage-1) * pageSize;
}
}
于是我们就可以在代码中去传递偏移量,以及当前要查询多少条数据。

代码
java
/**
* 查询奖品列表(分页查询)
* @param param 页号和页码(当前页和当前页要显示的记录数)
* @return
*/
@Override
public PageListDTO<PrizeDTO> findPrizeList(PageParam param) {
//首先查询咱们奖品表中有多少数据:因为前端需要总数
int total = prizeMapper.count();
//查询奖品表,怎么查?分页查询 得到当前页要显示的记录
List<PrizeDTO> prizeDTOList = new ArrayList<>();
List<PrizeDO> prizeDOList = prizeMapper.selectPrizeList(param.offset(), param.getPageSize());
//将查询到的数据转成DTO
for (PrizeDO prizeDO : prizeDOList) {
PrizeDTO prizeDTO = new PrizeDTO();
prizeDTO.setPrizeId(prizeDO.getId());
prizeDTO.setName(prizeDO.getName());
prizeDTO.setImageUrl(prizeDO.getImageUrl());
prizeDTO.setPrice(prizeDO.getPrice());
prizeDTO.setDescription(prizeDO.getDescription());
prizeDTOList.add(prizeDTO);
}
return new PageListDTO<>(total, prizeDTOList);
}
5、实现持久层的SQL编写
代码
java
@Select("select count(1) from prize")
int count();
@Select("select * from prize order by id desc limit #{offset}, #{pageSize}")
List<PrizeDO> selectPrizeList(@Param("offset") Integer offset,
@Param("pageSize") Integer pageSize);
很简单的

6、测试一下(使用Postman)

7、实现前后端交互

测试:


OK,那么对此我们的奖品列表已经成功实现了~~
奖品模块我们就结束了
接下来,咱们的项目重头戏核心就是:活动管理模块和抽奖模块啦,那么活动管理模块我们就下一篇见啦~ 抽奖模块我们后续活动模块结束之后再介绍~
老铁们如果本篇内容对你有帮助,不妨点赞收藏,也欢迎在评论区留言交流,您的每一份支持都是我持续创作的最大动力~👋
