【Java项目-企悦抽】13-奖品管理模块-奖品列表和创建奖品的实现

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🎯 你正在阅读「Java项目-企悦抽」系列文章 🎯

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥 弹简特 个人主页

❄️ 个人专栏直通车:

✨ 靠热爱去书写自己,靠勇敢去书写生活!

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:


文章目录

一、前言

铁汁们,回顾我们本项目的模块功能,咱们上一期将用户管理模块结束了,那么本期我们就进入奖品管理模块的实现


老铁们,咱们本期这个奖品管理和我们的人员管理是一样的。都是一个创建奖品和这个奖品列表。

你创建完奖品之后呢,你就给我自动跳转到奖品列表中去。

所以很多代码上的实现细节跟我们的人员管理差不太多,我们在这里呢会稍微的快一丢丢。

那很多人问,这个奖品管理有什么作用呢?

它的作用很简单,我们到时候创建活动的时候,你是需要去圈我选我们的奖品的。

所以我们先创建奖品,到时候就可以圈选它作为我们的奖项。

二、创建奖品

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,那么对此我们的奖品列表已经成功实现了~~

奖品模块我们就结束了

接下来,咱们的项目重头戏核心就是:活动管理模块和抽奖模块啦,那么活动管理模块我们就下一篇见啦~ 抽奖模块我们后续活动模块结束之后再介绍~

老铁们如果本篇内容对你有帮助,不妨点赞收藏,也欢迎在评论区留言交流,您的每一份支持都是我持续创作的最大动力~👋

相关推荐
Rosanci1 小时前
从零到合入主线:我的 DeepSeek Harness 开源贡献实战手记
开发语言·前端·开源
小蒜学长1 小时前
基于Spring Boot+Vue的“禾源”农产品销售系统设计与实现(代码+数据库+LW)
java·spring boot·后端·农产品销售系统·产品溯源
光年20001 小时前
Mock-Server开箱即用的Mock服务
java·maven·mock·jetty
弹简特1 小时前
【Java项目-企悦抽】14-活动管理模块01-创建活动的实现
java·开发语言·springboot
迅猛龙办公室1 小时前
Python实现求两个数字之和
java·开发语言·python
范桂飓1 小时前
AWS Agent Infra 架构分析
java·架构·aws
茉莉玫瑰花茶1 小时前
OpenGL [ 纹理 ]
开发语言·c++·opengl
企业数字化笔记1 小时前
视频成片怎么稳定导出?编码选择、GPU/CPU降级与媒体交付验收
java·python·ffmpeg
0+1111 小时前
Linux --应用层自定义协议与序列化
linux·运维·服务器·网络·tcp