当前位置: 首页 > news >正文

第27周JavaSpringboot电商进阶开发 2.常用功能进阶

电商常用功能进阶 - 课程笔记整理

Excel解析与处理

一、课程内容概述

本小节开始进入电商常用功能进阶部分,主要讲解以下内容:

  1. Excel的解析和处理
  2. 商品图片的处理
  3. Valid注解对列表的验证
  4. 订单数变化趋势图
  5. Spring Boot高级功能

二、Excel解析与处理的背景

在Java中解析Excel表格是非常常见的需求,尤其在电商场景中。例如,运营人员可能需要批量导入商品或用户信息,使用Excel可以提高效率,因为Excel在数据处理、求和、平均值计算等方面能力强大。

三、主流技术栈介绍

目前有两个主流的Excel处理工具:

  1. Apache POI
    • 官网:Apache POI
    • 特点:
      • 用于处理Microsoft文档的Java API,包括Excel、Word、PPT等。
      • 从4.0版本开始,需要Java 8或更高版本。
      • 支持处理Excel(.xls和.xlsx)、Word(.doc和.docx)、PPT(.ppt和.pptx)等格式。
      • Maven依赖可直接引入,无需手动下载。
  2. 阿里EasyExcel
    • 官网:阿里EasyExcel
    • 特点:
      • 阿里巴巴出品,基于语雀展示。
      • 主要解决内存溢出问题,适合处理超大Excel文件。
      • 内部利用了POI,但对POI 07版解析进行了重写,避免内存问题。

四、Excel相关概念

Apache POI中的重要概念
  1. Workbook:整个Excel表格文件,可以包含多个Sheet。
  2. Sheet:工作表,每个Sheet是一个有行有列的内容区域。
  3. Row:行,代表Excel中的一行数据。
  4. Cell:单元格,代表Excel中的一个单元格数据。

五、技术选型建议

  • Apache POI:适用于大多数场景,功能全面,社区支持广泛。不仅可用于Excel,还可用于Word、PPT等文档处理。
  • EasyExcel:适用于需要处理超大Excel文件的场景,避免内存溢出问题。

六、课程实践

代码编写
1. 添加商品批量导入功能

ProductAdminController中添加一个方法,用于处理商品批量导入:

@PostMapping("/admin/upload/product")
public void uploadProduct(@RequestParam("file") MultipartFile file) {
    // 获取文件名
    String fileName = file.getOriginalFilename();
    // 获取文件后缀
    String suffix = fileName.substring(fileName.lastIndexOf("."));
    // 生成唯一文件名
    String uuid = UUID.randomUUID().toString();
    String newFileName = uuid + suffix;
    // 创建文件
    File destFile = new File(Constant.FILE_PATH + newFileName);
    // 创建文件夹(如果不存在)
    File fileDirectory = new File(Constant.FILE_PATH);
    if (!fileDirectory.exists()) {
        boolean created = fileDirectory.mkdirs();
        if (!created) {
            throw new ImMoreException("文件夹创建失败");
        }
    }
    try {
        file.transferTo(destFile);
        // 通过Excel导入商品
        addProductByExcel(destFile);
    } catch (IOException e) {
        throw new ImMoreException("文件上传失败");
    }
}
2. 读取Excel文件并解析商品信息

创建一个方法addProductByExcel,用于读取Excel文件并解析商品信息:

private void addProductByExcel(File excelFile) {
    try (FileInputStream inputStream = new FileInputStream(excelFile)) {
        XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
        Sheet firstSheet = workbook.getSheetAt(0);
        Iterator<Row> rowIterator = firstSheet.iterator();
        List<Product> products = new ArrayList<>();
        while (rowIterator.hasNext()) {
            Row nextRow = rowIterator.next();
            Iterator<Cell> cellIterator = nextRow.cellIterator();
            Product product = new Product();
            while (cellIterator.hasNext()) {
                Cell nextCell = cellIterator.next();
                int columnIndex = nextCell.getColumnIndex();
                Object cellValue = getCellValue(nextCell);
                switch (columnIndex) {
                    case 0:
                        product.setName((String) cellValue);
                        break;
                    case 1:
                        product.setImage((String) cellValue);
                        break;
                    case 2:
                        product.setDetail((String) cellValue);
                        break;
                    case 3:
                        product.setCategoryId(((Double) cellValue).intValue());
                        break;
                    case 4:
                        product.setPrice((Double) cellValue);
                        break;
                    case 5:
                        product.setStock(((Double) cellValue).intValue());
                        break;
                    case 6:
                        product.setStatus((String) cellValue);
                        break;
                }
            }
            products.add(product);
        }
        // 将商品信息存入数据库
        saveProductsToDatabase(products);
    } catch (IOException e) {
        throw new ImMoreException("Excel文件读取失败");
    }
}
3. 工具方法getCellValue

创建一个工具类ExcelUtils,用于处理Excel单元格的值:

public class ExcelUtils {
    public static Object getCellValue(Cell cell) {
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case NUMERIC:
                return cell.getNumericCellValue();
            default:
                return null;
        }
    }
}
4. 将商品信息存入数据库
private void saveProductsToDatabase(List<Product> products) {
    for (Product product : products) {
        Product existingProduct = productMapper.selectByName(product.getName());
        if (existingProduct != null) {
            throw new ImMoreException("商品名不允许重复");
        }
        int count = productMapper.insertSelective(product);
        if (count == 0) {
            throw new ImMoreException("新增失败");
        }
    }
}

七、测试与验证

  1. 启动项目:确保项目正常启动。
  2. 准备Excel文件:按照模板格式准备商品数据。
  3. 使用Postman测试
    • 创建一个POST请求,URL为/admin/upload/product
    • 在请求体中选择form-data,添加一个文件字段file,选择准备好的Excel文件。
    • 发送请求,检查响应是否成功。
  4. 验证数据库:检查数据库中是否成功插入了商品数据。

八、总结

本小节通过实际案例演示了如何使用Apache POI进行Excel文件的解析和处理,包括文件上传、Excel读取、数据解析和数据库操作。希望小伙伴们能够掌握这些技能,灵活应用于实际项目中。

图片处理

一、图片处理的背景

在电商项目中,用户或运营人员上传的图片往往尺寸较大,不适用于移动端浏览,不仅耗费流量,还会增加加载时间。此外,图片容易被其他商家盗用,因此需要进行处理,包括缩放和添加水印。

二、工具介绍

我们将使用 Thumbnails 工具,它支持读取图像并进行缩放、旋转、透明化、打水印等操作,支持多种图片格式(如 JPG、PNG、GIF、BMP)。其官网提供了丰富的示例,便于学习和使用。

三、功能实现

1. 引入依赖

pom.xml 中添加以下依赖:

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>
2. 图片处理工具类

创建 ImageUtils 工具类,实现图片的裁剪、缩放、旋转和打水印功能:

import javax.imageio.ImageIO;
import java.io.File;
import java.util.UUID;

public class ImageUtils {

    public static void main(String[] args) {
        String path = "你的图片路径"; // 替换为你的图片路径
        String fileName = "草莓.jpg";

        // 裁剪图片
        new File(path + File.separator + "crop.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .sourceRegion(Positions.BOTTOM_RIGHT, 200, 200)
            .size(200, 200)
            .toFile(path + File.separator + "crop.jpg");

        // 缩放图片
        new File(path + File.separator + "scale_1.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .scale(0.7)
            .toFile(path + File.separator + "scale_1.jpg");

        new File(path + File.separator + "scale_2.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .scale(1.5)
            .toFile(path + File.separator + "scale_2.jpg");

        new File(path + File.separator + "size_1.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .size(500, 500)
            .keepAspectRatio(false)
            .toFile(path + File.separator + "size_1.jpg");

        new File(path + File.separator + "size_2.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .size(500, 500)
            .keepAspectRatio(true)
            .toFile(path + File.separator + "size_2.jpg");

        // 旋转图片
        new File(path + File.separator + "rotate_90.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .rotate(90)
            .toFile(path + File.separator + "rotate_90.jpg");

        new File(path + File.separator + "rotate_180.jpg")
            .getParentFile().mkdirs();
        Thumbnails.of(new File(path + File.separator + fileName))
            .rotate(180)
            .toFile(path + File.separator + "rotate_180.jpg");

        // 打水印
        new File(path + File.separator + "watermark.jpg")
            .getParentFile().mkdirs();
        try {
            Thumbnails.of(new File(path + File.separator + fileName))
                .watermark(Positions.BOTTOM_RIGHT,
                    ImageIO.read(new File(path + File.separator + "watermark.png")), 0.5f)
                .toFile(path + File.separator + "watermark.jpg");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
3. 图片上传接口的改造

ProductAdminController 中改造图片上传接口,实现图片的自动处理:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
@RequestMapping("/admin")
public class ProductAdminController {

    @PostMapping("/upload/image")
    public String uploadImage(@RequestParam("file") MultipartFile file) {
        // 获取文件名
        String fileName = file.getOriginalFilename();
        // 获取文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        // 生成唯一文件名
        String newFileName = UUID.randomUUID().toString() + suffix;
        // 创建文件
        File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);
        // 创建文件夹(如果不存在)
        File fileDirectory = new File(Constant.FILE_UPLOAD_DIR);
        if (!fileDirectory.exists()) {
            boolean created = fileDirectory.mkdirs();
            if (!created) {
                throw new ImMoreException("文件夹创建失败");
            }
        }
        try {
            file.transferTo(destFile);
            // 处理图片:缩放并打水印
            Thumbnails.of(destFile)
                .size(400, 400)
                .watermark(Positions.BOTTOM_RIGHT,
                    ImageIO.read(new File(Constant.FILE_UPLOAD_DIR + Constant.WATERMARK_JPG)), Constant.IMAGE_OPACITY)
                .toFile(Constant.FILE_UPLOAD_DIR + "processed_" + newFileName);
            return "图片上传并处理成功";
        } catch (IOException e) {
            throw new ImMoreException("图片处理失败");
        }
    }
}

四、测试与验证

  1. 启动项目:确保项目正常启动。
  2. 准备图片:选择一张图片作为测试文件。
  3. 使用Postman测试
    • 创建一个POST请求,URL为/admin/upload/image
    • 在请求体中选择form-data,添加一个文件字段file,选择准备好的图片。
    • 发送请求,检查响应是否成功。
  4. 验证处理结果:检查处理后的图片是否符合预期,包括尺寸、水印和透明度等。

五、总结

本小节学习了使用 Thumbnails 工具进行图片处理的方法,包括裁剪、缩放、旋转和打水印。通过改造图片上传接口,实现了自动处理上传图片的功能,提高了图片的实用性和美观度。

列表参数校验的实现方法

一、问题背景

在实际开发中,前端传入的参数可能是一个列表,需要对列表中的每个元素进行校验。例如,校验列表中的每个 ID 是否大于零。常见的校验注解如 @Min@Max 在列表场景下无法直接使用,因此需要寻找解决方案。

二、解决方案

方法一:手动校验
  1. 实现步骤
    • 使用 for 循环遍历列表。
    • 对每个元素的属性进行手动校验,如 if (updateRequest.getPrice() < 1)
    • 如果校验失败,抛出自定义异常。
  2. 优点:简单直接,易于理解。
  3. 缺点:代码冗长,不优雅,难以统一管理。
方法二:自定义列表类
  1. 实现步骤
    • 创建一个自定义列表类 ValidList,实现 List 接口。
    • 在自定义列表类中,对每个元素添加校验逻辑。
    • 将普通列表替换为自定义列表。
  2. 优点:代码优雅,复用性强。
  3. 缺点:需要额外实现一个类,稍微增加复杂度。
方法三:使用 @Validated 注解
  1. 实现步骤
    • 在控制器类上添加 @Validated 注解。
    • 在方法参数前添加 @Valid 注解,对列表中的每个元素进行校验。
    • 处理校验异常,提供友好的错误信息。
  2. 优点:代码简洁,充分利用 Spring 的校验机制。
  3. 缺点:需要处理特定的校验异常。

三、代码示例

手动校验
@PostMapping("/admin/product/batchUpdate")
public APIResponse batchUpdateProduct(@RequestBody List<UpdateProductRequest> updateRequests) {
    for (UpdateProductRequest updateRequest : updateRequests) {
        if (updateRequest.getPrice() < 1) {
            throw new ImMoreException("价格过低");
        }
        if (updateRequest.getStock() > 10000) {
            throw new ImMoreException("库存过多");
        }
        // 其他校验逻辑
    }
    // 更新逻辑
    return APIResponse.success();
}
自定义列表类
public class ValidList<E> implements List<E> {
    private List<E> list;

    public ValidList(List<E> list) {
        this.list = list;
    }

    @Override
    public int size() {
        return list.size();
    }

    // 其他 List 方法的实现

    public static void main(String[] args) {
        List<UpdateProductRequest> updateRequests = new ArrayList<>();
        ValidList<UpdateProductRequest> validList = new ValidList<>(updateRequests);
        // 校验逻辑
    }
}
使用 @Validated 注解
@Validated
@RestController
@RequestMapping("/admin/product")
public class ProductAdminController {

    @PostMapping("/batchUpdate3")
    public APIResponse batchUpdateProduct3(@Valid @RequestBody List<UpdateProductRequest> updateRequests) {
        // 更新逻辑
        return APIResponse.success();
    }
}

四、总结

本小节介绍了三种对列表参数进行校验的方法:手动校验、自定义列表类和使用 @Validated 注解。每种方法都有其优缺点,选择时需根据项目实际情况和技术选型进行权衡。手动校验适合简单场景,自定义列表类和 @Validated 注解则更适合复杂和批量校验场景。

电商功能优化与高级特性

一、功能优化

订单状态提示升级

order service impl 中,优化订单状态流转相关的异常提示:

  1. 取消订单:新增枚举 CANCEL_ORDER_STATUS,提示 “订单状态有误,付款后暂不支持取消订单”。
  2. 付款操作:新增枚举 PAY_RUN_OTHER_STATUS,提示 “仅能在未付款时付款”。
  3. 发货操作:新增枚举 DELIVER_RUN_ORDER_STATUS,提示 “仅能在付款后发货”。
  4. 完单操作:新增枚举 FINISH_RUN_ORDER_STATUS,提示 “仅能在发货后完单”。
商品图片上传优化

product admin controller 中,优化图片上传功能:

  1. 问题分析:原实现方式在服务器部署时可能因转发导致获取的 IP 不准确。
  2. 解决方案:将 IP 和端口号配置为固定值,避免动态获取。
  3. 代码调整
    • 引入配置文件中的 file.upload.uri,替换动态获取 IP 的代码。
    • 拼接图片访问地址时,使用配置的 URI 作为前缀。

二、新功能开发

订单数变化趋势图
  1. 接口开发
    • order admin controller 中新增 GET 接口 adminOrderStatistics,用于获取每日订单量统计。
    • 接口参数为 start_dateend_date,指定统计的时间范围。
  2. 服务实现
    • order service 中新增 statistics 方法,接收起始时间和结束时间作为参数。
    • 调用 order mapper 中的查询方法,获取统计数据。
  3. 数据查询与转换
    • 创建 order statistics query 类,封装查询条件。
    • order mapper 中编写 SQL 语句,使用 date_format 函数按天聚合订单数据,计算每天的订单数量。
    • 处理时区问题,确保统计结果的准确性。

三、Spring Boot 高级功能

指定配置
  1. 命令行参数:通过命令行指定配置参数,如 server.portspring.profiles.active,覆盖默认配置。
  2. 多环境配置:介绍常见的四种环境(本地开发、测试、预发、生产),根据实际需求调整配置。
热加载与调试技巧(详情)
  1. 热加载配置
    • 在 IDEA 中配置调试选项,启用热加载功能。
    • 自动编译和更新类资源,提高开发效率。
  2. 调试技巧
    • 使用断点调试,灵活控制程序执行流程。
    • 强制返回值和抛出异常,快速验证逻辑。
    • 重新加载修改后的类,实时查看代码改动效果。

相关文章:

  • 要登录的设备ip未知时的处理方法
  • WPF-DataGrid的增删查改
  • 【MapSet】哈希表
  • 麒麟操作系统和统信的区别,上面一般用什么OFFICE,excel软件?
  • Java 什么是线程安全及如何实现线程安全
  • EasyRTC嵌入式音视频通话SDK:基于纯C语言的跨平台实时通信系统设计与实践
  • leetcode144 二叉树的前序遍历 递归法、迭代法
  • 一维数组的增删改查:对元素的影响
  • 解决pip安装uv时下载速度慢
  • 【嵌入式linux】网口和USB热插拔检测
  • qt之No executable specified
  • 【ES6】基础特性总结
  • 通义万相 2.1:AIGC 领域的 “王炸” 组合如何颠覆创作生态?
  • TDengine 使用教程:从入门到实践
  • Android控件Selector封装优化指南:高效实现动态UI效果
  • LLM训练中常用的Benchmarks
  • uvm_transaction, uvm_seq_item, uvm_object, uvm_component的关系
  • 仅仅使用pytorch来手撕transformer架构(3):编码器模块和编码器类的实现和向前传播
  • 前端高阶面试题·每日一题
  • 【大模型知识点】RMSNorm(Root Mean Square Normalization)均方根归一化
  • 媒体评教师拎起学生威胁要扔下三楼:师风师德不能“悬空”
  • 台湾关闭最后的核电,岛内担忧“非核家园”缺电、涨电价困局难解
  • 网文书单|推荐4本网文,可以当作《绍宋》代餐
  • 以色列媒体:哈马斯愿意释放部分人员换取两个月停火
  • 机器人为啥热衷“搞体育”,经济日报:是向加速融入日常生活发起的冲锋
  • “走进书适圈”:一周城市生活