黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第四部分

本文主要是介绍黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第四部分,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第四部分

  • 1. 套餐管理
    • 1.1 新增套餐
      • 1.1.1 添加菜品数据回显
    • 1.2 保存添加套餐
    • 1.3 套餐信息分页查询
    • 1.4 删除套餐
    • 1.5 需要自己单独实现的功能
      • 1.5.1 套餐管理的启售、停售
      • 1.5.2 套餐管理的修改
      • 1.5.3 后台订单展示和查询

在这里插入图片描述

在这里插入图片描述

1. 套餐管理

需求分析:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1 新增套餐

套餐就是菜品的集合。

后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。

在这里插入图片描述

数据模型:
新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:

  • setmeal——套餐表
  • setmeal dish——套餐菜品关系表

代码开发:

准备工作:

在开发业务功能前,先将需要用到的类和接口基本结构创建好:

  • 实体类SetmealDish ( 直接从课程资料中导入即可,Setmeal实体前面课程中已经导入过了)
  • DTO SetmealDto (直接从课程资料中导入即可)
  • Mapper接口SetmealDishMapper
  • 业务层接口SetmealDishService
  • 业务层实现类SetmealDishServicelmpl
  • 控制层SetmealController
package com.itheima.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 套餐菜品关系*/
@Data
public class SetmealDish implements Serializable {private static final long serialVersionUID = 1L;private Long id;//套餐idprivate Long setmealId;//菜品idprivate Long dishId;//菜品名称 (冗余字段)private String name;//菜品原价private BigDecimal price;//份数private Integer copies;//排序private Integer sort;@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;//是否删除private Integer isDeleted;
}

创建mapper:

package com.itheima.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.SetmealDish;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
}

创建service:

package com.itheima.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.SetmealDish;public interface SetmealDishService extends IService<SetmealDish> {
}
package com.itheima.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.SetmealDish;
import com.itheima.reggie.mapper.SetmealDishMapper;
import com.itheima.reggie.service.SetmealDishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishService {
}

1.1.1 添加菜品数据回显

controller层代码:

在开发代码之前,需要梳理一下新增套餐时前端页面和服务端的交互过程:

  1. 页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中
  2. 页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中
  3. 页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
  4. 页面发送请求进行图片上传,请求服务端将图片保存到服务器
  5. 页面发送请求进行图片下载,将上传的图片进行回显
  6. 点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端

开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。

第一个交互前面写了;分类管理通过type的值来控制在前端展示的是 菜品分类(type=1) 或者是 套餐分类(type=2)
在这里插入图片描述

在这里插入图片描述

第二个交互前面也写了,在categorycontroller里面的list方法;

第四和第五前面也写了;

第三个交互:前端请求的地址;

在这里插入图片描述

在DishController书写代码:

    /*** 根据条件查询对应的菜品数据* @param dish* @return*/@GetMapping("/list")public R<List<Dish>> list(Dish dish){ //会自动映射的//这里可以传categoryId,但是为了代码通用性更强,这里直接使用dish类来接受(因为dish里面是有categoryId的),以后传dish的其他属性这里也可以使用//构造查询条件LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());//添加条件,查询状态为1(起售状态)的菜品queryWrapper.eq(Dish::getStatus,1);//添加排序条件queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);List<Dish> list = dishService.list(queryWrapper);return R.success(list);}

控制台输出的sql语句:

SELECT id,name,category_id,price,code,image,description,status,sort,create_time,update_time,create_user,update_user,is_deleted FROM dish WHERE (category_id = ? AND status = ?) ORDER BY sort ASC,update_time DESC

在这里插入图片描述

1.2 保存添加套餐

实现要求:点击保存按钮,发送ajax请求,将套餐相关的数据以json形式提交到服务端;

前端提交请求:

在这里插入图片描述

前端携带的参数:重要

在这里插入图片描述

根据前端传过来的数据我们可以在后端确定我们需要在后端使用什么来接受前端的参数;

编写controller:上面的dishList,我们数据库并不需要这个数据,所以接收数据的实体类没有dishList这个属性也没有关系,前端传过来的数据都是自动映射到接收数据的实体类的属性上的,没有对应起来就不会映射。

涉及两张表的操作:套餐表和菜品表;

    /*** 新增套餐* 涉及两张表的操作:套餐表和菜品表;* @param setmealDto* @return*/@PostMappingpublic R<String> save(@RequestBody SetmealDto setmealDto){setmealService.saveWithDish(setmealDto);return R.success("新增套餐成功");}

SetmealService中添加自定义的方法:

/*** 新增套餐,同时需要保存套餐和菜品的关联关系* @param setmealDto*/
void saveWithDish(SetmealDto setmealDto);
@Autowired
SetmealDishService setmealDishService;/*** 新增套餐,同时需要保存套餐和菜品的关联关系* @param setmealDto*/
@Transactional
@Override
public void saveWithDish(SetmealDto setmealDto) {//保存套餐的基本信息,操作setmeal,执行insertthis.save(setmealDto);log.info(setmealDto.toString()); //查看一下这个套餐的基本信息是什么//保存套餐和菜品的关联信息,操作setmeal_dish ,执行insert操作List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();//注意上面拿到的setmealDishes是没有setmeanlId这个的值的,通过debug可以发现setmealDishes.stream().map((item)->{item.setSetmealId(setmealDto.getId());return item; //这里返回的就是集合的泛型}).collect(Collectors.toList());setmealDishService.saveBatch(setmealDishes); //批量保存
}

功能测试,自己测试;

在这里插入图片描述

1.3 套餐信息分页查询

需求分析:

在这里插入图片描述

代码开发:

前端发起的请求以及携带的参数:
在这里插入图片描述

查询分页:

在这里插入图片描述

在开发代码之前,需要梳理一下套餐 分页查询时前端页面和服务端的交互过程:

  1. 页面(backend/ page/combo/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据

  2. 页面发送请求,请求服务端进行图片下载,用于页面图片展示

开发套餐信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求

controller层代码编写:

/*** 套餐分页查询* @param page* @param pageSize* @param name* @return*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){//分页构造器对象Page<Setmeal> pageInfo = new Page<>(page,pageSize);//构造条件查询对象LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();//添加查询条件,根据name进行like模糊查询queryWrapper.like(name != null,Setmeal::getName,name);//添加排序条件,根据更新时间降序排列queryWrapper.orderByDesc(Setmeal::getUpdateTime);setmealService.page(pageInfo,queryWrapper);/*** 注意如果这里直接返回R.success(pageInfo),* 虽然不会报错但是分页的数据的套餐分类的名字是显示不了的;* 因为这个分页的泛型是Setmeal,Setmeal只封装了f分类的Id categoryId,没有分类的名称 name* 所以又需要进行name的获取和设值*/return R.success(pageInfo);
}

bug修复:

/*** 套餐分页查询* @param page* @param pageSize* @param name* @return*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){//分页构造器对象Page<Setmeal> pageInfo = new Page<>(page,pageSize);Page<SetmealDto> dtoPage = new Page<>(page,pageSize);//构造条件查询对象LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();//添加查询条件,根据name进行like模糊查询queryWrapper.like(name != null,Setmeal::getName,name);//添加排序条件,根据更新时间降序排列queryWrapper.orderByDesc(Setmeal::getUpdateTime);setmealService.page(pageInfo,queryWrapper);//对象的拷贝  注意这里要把分页数据的全集合records给忽略掉BeanUtils.copyProperties(pageInfo,dtoPage,"records");List<Setmeal> records = pageInfo.getRecords();//对records对象进行处理然后封装好赋值给listList<SetmealDto> list = records.stream().map((item)->{SetmealDto setmealDto = new SetmealDto();//对setmealDto进行除categoryName的属性进行拷贝(因为item里面没有categoryName)BeanUtils.copyProperties(item,setmealDto);//获取分类id  通过分类id获取分类对象  然后再通过分类对象获取分类名Long categoryId = item.getCategoryId();//根据分类id获取分类对象  判断是否为nullCategory category = categoryService.getById(categoryId);if (category != null){String categoryName = category.getName();setmealDto.setCategoryName(categoryName);}return setmealDto;}).collect(Collectors.toList());dtoPage.setRecords(list);return R.success(dtoPage);
}

在这里插入图片描述

1.4 删除套餐

在这里插入图片描述

代码开发:

单个套餐删除前端发的请求和携带的参数:

在这里插入图片描述

套餐批量删除前端发的请求和携带的参数:

在这里插入图片描述

在这里插入图片描述

controller层开发

在SetmealService中添加自定义的方法:

/*** 删除套餐,同时需要删除套餐和菜品的关联数据* @param ids*/
void removeWithDish(List<Long> ids);

实现该方法:

/*** 删除套餐,同时需要删除套餐和菜品的关联数据* @param ids*/
@Override
@Transactional
public void removeWithDish(List<Long> ids) {//sql语句应该是这样的:select count(*) setmeal where id in () and status = 1;//查询套餐的状态,看是否可以删除LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();queryWrapper.in(Setmeal::getId,ids);queryWrapper.eq(Setmeal::getStatus,1);int count = this.count(queryWrapper);//如果不能删除,抛出一个业务异常if (count > 0){throw new CustomException("套餐正在售卖中,不能删除");}//如果可以删除,先删除套餐表中的数据--setmeal this.removeByIds(ids);//删除关系表中的数据--setmeal_dish//delete from setmeal_dish where setmeal_id in (1,2,3)LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper();lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);setmealDishService.remove(lambdaQueryWrapper);
}

功能测试;

在这里插入图片描述

1.5 需要自己单独实现的功能

1.5.1 套餐管理的启售、停售

前端发来的请求:

根据前面菜品模块自己实现的功能,我们可以知道,我们只需要写一个批量处理的方法就可以完成单个或者是批量套餐的启售,停售;

SetmealController中的controller层代码:

/*** 对菜品批量或者是单个 进行停售或者是起售* @return*/
@PostMapping("/status/{status}")
//这个参数这里一定记得加注解才能获取到参数,否则这里非常容易出问题
public R<String> status(@PathVariable("status") Integer status,@RequestParam List<Long> ids){setmealService.updateSetmealStatusById(status,ids);return R.success("售卖状态修改成功");
}

SetmealService中添加下面方法:

/*** 根据套餐id修改售卖状态* @param status* @param ids*/
void updateSetmealStatusById(Integer status,List<Long> ids);

该方法的实现:

/*** 根据套餐id修改售卖状态* @param status* @param ids*/
@Override
public void updateSetmealStatusById(Integer status,  List<Long> ids) {LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();queryWrapper.in(ids !=null,Setmeal::getId,ids);List<Setmeal> list = this.list(queryWrapper);for (Setmeal setmeal : list) {if (setmeal != null){setmeal.setStatus(status);this.updateById(setmeal);}}
}

1.5.2 套餐管理的修改

分为两步:数据回显示,和提交修改数据到数据库

前端点击套餐修改,前端发过来的请求

SetmealController 中添加下面的代码:

/*** 回显套餐数据:根据套餐id查询套餐* @return*/
@GetMapping("/{id}")
public R<SetmealDto> getData(@PathVariable Long id){SetmealDto setmealDto = setmealService.getDate(id);return R.success(setmealDto);
}

SetmealService添加下面的代码:

/*** 回显套餐数据:根据套餐id查询套餐* @return*/
SetmealDto getDate(Long id);

该方法的实现:

/*** 回显套餐数据:根据套餐id查询套餐* @return*/
@Override
public SetmealDto getDate(Long id) {Setmeal setmeal = this.getById(id);SetmealDto setmealDto = new SetmealDto();LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper();//在关联表中查询,setmealdishqueryWrapper.eq(id!=null,SetmealDish::getSetmealId,id);if (setmeal != null){BeanUtils.copyProperties(setmeal,setmealDto);List<SetmealDish> list = setmealDishService.list(queryWrapper);setmealDto.setSetmealDishes(list);return setmealDto;}return null;
}

测试:数据回显成功

但是这样我们再点击添加菜品会发现,右边只展示菜品的价格并没有展示菜品对应的名称:

已选菜品中的菜品并没有展示对应的菜品名;

在这里插入图片描述

修改后的运行情况展示:

在这里插入图片描述

搜索框的作用:

在这里插入图片描述
在这里插入图片描述
DishController.java修改

    @GetMapping("/list")public R<List<Dish>> list(Dish dish){if(dish.getId()!=null)log.info(dish.getCategoryId().toString());if(dish.getName()!=null)log.info(dish.getName().toString());LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());queryWrapper.like(dish.getName()!=null,Dish::getName,dish.getName());queryWrapper.eq(Dish::getStatus,1);queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);List<Dish> list = dishService.list(queryWrapper);return R.success(list);}

controller层代码:

为了不把问题复杂化,我是先把相关的setmealDish内容移除然后再重新添加,这样就可以不用考虑dish重复的问题和哪些修改哪些没修改;

@PutMapping
public R<String> edit(@RequestBody SetmealDto setmealDto){if (setmealDto==null){return R.error("请求异常");}if (setmealDto.getSetmealDishes()==null){return R.error("套餐没有菜品,请添加套餐");}List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();Long setmealId = setmealDto.getId();LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SetmealDish::getSetmealId,setmealId);setmealDishService.remove(queryWrapper);//为setmeal_dish表填充相关的属性for (SetmealDish setmealDish : setmealDishes) {setmealDish.setSetmealId(setmealId);}//批量把setmealDish保存到setmeal_dish表setmealDishService.saveBatch(setmealDishes);setmealService.updateById(setmealDto);return R.success("套餐修改成功");
}

另一套

    @Overridepublic void updateWithDish(SetmealDto setmealDto) {this.updateById(setmealDto);LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());setmealDishService.remove(queryWrapper);List<SetmealDish> list = setmealDto.getSetmealDishes();list.stream().map((item)->{item.setSetmealId(setmealDto.getId());return item;}).collect(Collectors.toList());setmealDishService.saveBatch(list);}

1.5.3 后台订单展示和查询

点击订单明细,前端会发下面的请求:携带的数据是分页使查询用的;

先写个controller看能不能接收到前端传过来的参数:发现只要参数和前端传过来的参数名对应就可以拿到参数的

orders.java

package com.itheima.reggie.entity;import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 订单*/
@Data
public class Orders implements Serializable {private static final long serialVersionUID = 1L;private Long id;//订单号private String number;//订单状态 1待付款,2待派送,3已派送,4已完成,5已取消private Integer status;//下单用户idprivate Long userId;//地址idprivate Long addressBookId;//下单时间private LocalDateTime orderTime;//结账时间private LocalDateTime checkoutTime;//支付方式 1微信,2支付宝private Integer payMethod;//实收金额private BigDecimal amount;//备注private String remark;//用户名private String userName;//手机号private String phone;//地址private String address;//收货人private String consignee;
}

在这里插入图片描述
OrdersMapper

package com.itheima.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Orders;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OrdersMapper extends BaseMapper<Orders> {
}

OrdersService

package com.itheima.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Orders;public interface OrdersService extends IService<Orders> {
}

OrdersServiceImpl

package com.itheima.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Orders;
import com.itheima.reggie.mapper.OrdersMapper;
import com.itheima.reggie.service.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
@Transactional
@Slf4j
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper,Orders> implements OrdersService {
}

主要使用到mybatis-plus动态sql语句的生成:

这里我就直接把功能直接写在controller层了,看自己需求分层;(本人这里偷个懒)

    /*** 后台查询订单明细* @param page* @param pageSize* @param number* @param beginTime* @param endTime* @return*/@GetMapping("/page")public R<Page> page(int page, int pageSize, String number,String beginTime,String endTime){//分页构造器对象Page<Orders> pageInfo = new Page<>(page,pageSize);//构造条件查询对象LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();//添加查询条件  动态sql  字符串使用StringUtils.isNotEmpty这个方法来判断//这里使用了范围查询的动态SQL,这里是重点!!!queryWrapper.like(number!=null,Orders::getNumber,number).gt(StringUtils.isNotEmpty(beginTime),Orders::getOrderTime,beginTime).lt(StringUtils.isNotEmpty(endTime),Orders::getOrderTime,endTime);orderService.page(pageInfo,queryWrapper);return R.success(pageInfo);}

在这里插入图片描述

这篇关于黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第四部分的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/585961

相关文章

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable