sky-take-out(一)1-2https://blog.youkuaiyun.com/kussm_/article/details/138614737?spm=1001.2014.3001.5501
第三天
公共字段填充--利用AOP
问题提出
这些字段属于公共字段 :在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段
赋值方式为:
1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。
2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。
目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作
问题:处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
答:可以,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。
实现思路
在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
---|---|---|---|---|
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | insert |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint | insert、update |
实现步骤:
1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
3). 在 Mapper 的方法上加入 AutoFill 注解
若要实现上述步骤,需掌握以下知识(之前课程内容都学过)
技术点:枚举、注解、AOP、反射
代码开发
一、自定义注解 AutoFill
进入到sky-server模块,创建com.sky.annotation包。
其中OperationType已在sky-common模块中定义
二、自定义切面 AutoFillAspect
在sky-server模块,创建com.sky.aspect包。
/**
* 自定义切面,完善公共部分填充的问题
*/
@Aspect//切面的注解
@Component//动态代理,bean类中,spring的知识
@Slf4j//日志:酸辣粉4斤
public class AspectAutoFill {
/**
* 切入点
*/
//@Pointcut("execution(* com.sky.mapper.*.*(..))")
@Pointcut("@annotation(com.sky.annotation.AutoFill)")//切入点是有AutoFill这个注解的方法
public void autoFillPointCut(){}
/**
* 前置通知
*
* @param joinPoint
*/
@Before("autoFillPointCut()")//表示在这个方法前执行这个通知
public void autoFill(JoinPoint joinPoint) {//接入点JoinPoint
log.info("公告部分填充开始...........");
//获取当前拦截方法的数据库操作方法:insert还是update
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取方法签名对象
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象;
OperationType operationType = annotation.value();//获取数据库的操作类型
//获取当前被截取的方法参数--实体对象
Object[] args = joinPoint.getArgs();//获取所有的实体对象;
Object entity = args[0];//取第一个实体对象:约定:将需要获取设置的实体对象写在一个位置;
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();//取到当前时间
long currentId = BaseContext.getCurrentId();//取到当前操作人的id
//根据操作方法的不同,选用特定的数据,inset有四个,update只有两个
if (operationType == OperationType.INSERT) {
//证明是insert的方法,则需要4个方法
try {
//获取实体对象中的方法
Method setCrateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCrateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//利用反射将数据写进方法中;
setCrateTime.invoke(entity, now);//创建时间
setUpdateTime.invoke(entity, now);//修改时间
setCrateUser.invoke(entity, currentId);//创建人id
setUpdateUser.invoke(entity, currentId);//修改人的id
} catch (Exception e) {
throw new RuntimeException(e);
}
} else if (operationType == OperationType.UPDATE) {
//update方法,需要两个方法
//获取实体对象中的方法
Method setUpdateTime = null;
try {
setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//利用反射将数据写进方法中;
setUpdateTime.invoke(entity, now);//修改时间
setUpdateUser.invoke(entity, currentId);//修改人的id
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
三、在Mapper接口的方法上加入 AutoFill 注解
同时,将业务层为公共字段赋值的代码注释掉。
1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。
2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。
新增菜品
需求分析与接口设计
产品原型
业务规则
-
菜品名称必须是唯一的
-
菜品必须属于某个分类下,不能单独存在
-
新增菜品时可以根据情况选择菜品的口味
-
每个菜品必须对应一张图片
接口设计
-
根据类型查询分类(已完成)
-
文件上传
-
新增菜品
第一个
第二个
第三个
表设计
原图分析
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:
表名 | 说明 |
---|---|
dish | 菜品表 |
dish_flavor | 菜品口味表 |
所以:
菜品表
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
name | varchar(32) | 菜品名称 | 唯一 |
category_id | bigint | 分类id | 逻辑外键 |
price | decimal(10,2) | 菜品价格 | |
image | varchar(255) | 图片路径 | |
description | varchar(255) | 菜品描述 | |
status | int | 售卖状态 | 1起售 0停售 |
create_time | datetime | 创建时间 | |
update_time | datetime | 最后修改时间 | |
create_user | bigint | 创建人id | |
update_user | bigint | 最后修改人id |
菜品口味表
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
dish_id | bigint | 菜品id | 逻辑外键 |
name | varchar(32) | 口味名称 | |
value | varchar(255) | 口味值 |
代码开发
文件上传实现OSS
在本项目选用阿里云的OSS服务进行文件存储。(前面课程已学习过阿里云OSS,不再赘述)
实现步骤
1. 定义相关配置
在sky-server模块 application-dev.yml
sky:
alioss:
endpoint: oss-cn-hangzhou.aliyuncs.com //创建时默认杭州
access-key-id: LTAI5tPeFLzsPPT8gG3LPW64 //密钥名称
access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7 //密钥密码
bucket-name: sky-take-out //自己创建的bucket名字
application.yml
spring: profiles: active: dev #设置环境 sky: alioss: endpoint: ${sky.alioss.endpoint} access-key-id: ${sky.alioss.access-key-id} access-key-secret: ${sky.alioss.access-key-secret} bucket-name: ${sky.alioss.bucket-name}
2). 读取OSS配置
在sky-common模块中,已定义
package com.sky.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.alioss") @Data public class AliOssProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
3). 生成OSS工具类对象
在sky-server模块
package com.sky.config; import com.sky.properties.AliOssProperties; import com.sky.utils.AliOssUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 配置类,用于创建AliOssUtil对象 */ @Configuration @Slf4j public class OssConfiguration { @Bean @ConditionalOnMissingBean public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){ log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties); return new AliOssUtil(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret(), aliOssProperties.getBucketName()); } }其中,AliOssUtil.java已在sky-common模块中定义
package com.sky.utils; import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.io.ByteArrayInputStream; @Data @AllArgsConstructor @Slf4j public class AliOssUtil { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; /** * 文件上传 * * @param bytes * @param objectName * @return */ public String upload(byte[] bytes, String objectName) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { // 创建PutObject请求。 ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes)); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } //文件访问路径规则 https://BucketName.Endpoint/ObjectName StringBuilder stringBuilder = new StringBuilder("https://"); stringBuilder .append(bucketName) .append(".") .append(endpoint) .append("/") .append(objectName); log.info("文件上传到:{}", stringBuilder.toString()); return stringBuilder.toString(); } }
4). 定义文件上传接口
在sky-server模块中定义接口
package com.sky.controller.admin; import com.sky.constant.MessageConstant; import com.sky.result.Result; import com.sky.utils.AliOssUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.UUID; /** * 通用接口 */ @RestController @RequestMapping("/admin/common") @Api(tags = "通用接口") @Slf4j public class CommonController { @Autowired private AliOssUtil aliOssUtil; /** * 文件上传 * @param file * @return */ @PostMapping("/upload") @ApiOperation("文件上传") public Result<String> upload(MultipartFile file){ log.info("文件上传:{}",file); try { //原始文件名 String originalFilename = file.getOriginalFilename(); //截取原始文件名的后缀 dfdfdf.png String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); //构造新文件名称 String objectName = UUID.randomUUID().toString() + extension; //文件的请求路径 String filePath = aliOssUtil.upload(file.getBytes(), objectName); return Result.success(filePath); } catch (IOException e) { log.error("文件上传失败:{}", e); } return Result.error(MessageConstant.UPLOAD_FAILED); } }
新增菜品
1. 设计DTO
在sky-pojo模块中
package com.sky.dto; import com.sky.entity.DishFlavor; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @Data public class DishDTO implements Serializable { private Long id; //菜品名称 private String name; //菜品分类id private Long categoryId; //菜品价格 private BigDecimal price; //图片 private String image; //描述信息 private String description; //0 停售 1 起售 private Integer status; //口味 private List<DishFlavor> flavors = new ArrayList<>(); }
2). Controller层
进入到sky-server模块
package com.sky.controller.admin; import com.sky.dto.DishDTO; import com.sky.dto.DishPageQueryDTO; import com.sky.entity.Dish; import com.sky.result.PageResult; import com.sky.result.Result; import com.sky.service.DishService; import com.sky.vo.DishVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Set; /** * 菜品管理 */ @RestController @RequestMapping("/admin/dish") @Api(tags = "菜品相关接口") @Slf4j public class DishController { @Autowired private DishService dishService; /** * 新增菜品 * * @param dishDTO * @return */ @PostMapping @ApiOperation("新增菜品") public Result save(@RequestBody DishDTO dishDTO) { log.info("新增菜品:{}", dishDTO); dishService.saveWithFlavor(dishDTO);//后绪步骤开发 return Result.success(); } }
3). Service层接口
package com.sky.service; import com.sky.dto.DishDTO; import com.sky.entity.Dish; public interface DishService { /** * 新增菜品和对应的口味 * * @param dishDTO */ public void saveWithFlavor(DishDTO dishDTO); }
4). Service层实现类
package com.sky.service.impl; @Service @Slf4j public class DishServiceImpl implements DishService { @Autowired private DishMapper dishMapper; @Autowired private DishFlavorMapper dishFlavorMapper; /** * 新增菜品和对应的口味 * * @param dishDTO */ @Transactional public void saveWithFlavor(DishDTO dishDTO) { Dish dish = new Dish(); BeanUtils.copyProperties(dishDTO, dish); //向菜品表插入1条数据 dishMapper.insert(dish);//后绪步骤实现 //获取insert语句生成的主键值 Long dishId = dish.getId(); List<DishFlavor> flavors = dishDTO.getFlavors(); if (flavors != null && flavors.size() > 0) { flavors.forEach(dishFlavor -> { dishFlavor.setDishId(dishId); }); //向口味表插入n条数据 dishFlavorMapper.insertBatch(flavors);//后绪步骤实现 } } }
5). Mapper层
DishMapper.java中添加
/** * 插入菜品数据 * * @param dish */ @AutoFill(value = OperationType.INSERT) void insert(Dish dish);在/resources/mapper中创建DishMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.sky.mapper.DishMapper"> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> insert into dish (name, category_id, price, image, description, create_time, update_time, create_user,update_user, status) values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status}) </insert> </mapper>
DishFlavorMapper.java
package com.sky.mapper; import com.sky.entity.DishFlavor; import java.util.List; @Mapper public interface DishFlavorMapper { /** * 批量插入口味数据 * @param flavors */ void insertBatch(List<DishFlavor> flavors); }
在/resources/mapper中创建DishFlavorMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.sky.mapper.DishFlavorMapper"> <insert id="insertBatch"> insert into dish_flavor (dish_id, name, value) VALUES <foreach collection="flavors" item="df" separator=","> (#{df.dishId},#{df.name},#{df.value}) </foreach> </insert> </mapper>
分页查询
需求分析与设计
设计原型
接口设计
代码开发
1.设计DTO类
根据菜品分页查询接口定义设计对应的DTO:
在sky-pojo模块中,已定义
package com.sky.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class DishPageQueryDTO implements Serializable {
private int page;
private int pageSize;
private String name;
private Integer categoryId; //分类id
private Integer status; //状态 0表示禁用 1表示启用
}
2.设计VO类
根据菜品分页查询接口定义设计对应的VO:
在sky-pojo模块中,已定义
package com.sky.vo;
import com.sky.entity.DishFlavor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishVO implements Serializable {
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
//更新时间
private LocalDateTime updateTime;
//分类名称
private String categoryName;
//菜品关联的口味
private List<DishFlavor> flavors = new ArrayList<>();
}
3.controller层
DishController的page分页查询方法
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
log.info("菜品分页查询:{}", dishPageQueryDTO);
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);//后绪步骤定义
return Result.success(pageResult);
}
4.service层
DishService
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
DishServiceImpl
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);//后绪步骤实现
return new PageResult(page.getTotal(), page.getResult());
}
5.mapper层
DishMapper 接口中声明 pageQuery 方法
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
DishMapper.xml
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
删除菜品
需求分析与接口设计
需求原型
可以一次删除一个菜品,也可以批量删除菜品
起售中的菜品不能删除
被套餐关联的菜品不能删除
删除菜品后,关联的口味数据也需要删除掉
接口设计
表设计
注意事项:
在dish表中删除菜品基本数据时,同时,也要把关联在dish_flavor表中的数据一块删除。
setmeal_dish表为菜品和套餐关联的中间表。
若删除的菜品数据关联着某个套餐,此时,删除失败。
若要删除套餐关联的菜品数据,先解除两者关联,再对菜品进行删除。
代码开发
1.controller层
DishController
/**
* 菜品批量删除
*
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {
log.info("菜品批量删除:{}", ids);
dishService.deleteBatch(ids);//后绪步骤实现
return Result.success();
}
2.service层
DishService
/**
* 菜品批量删除
*
* @param ids
*/
void deleteBatch(List<Long> ids);
DishServiceImpl
@Autowired
private SetmealDishMapper setmealDishMapper;
/**
* 根据菜品id删除菜品
* @param ids
*/
@Override
public void deleteByIds(List<Long> ids) {
//1. 检查是否可以删除--起售中的菜品不能删除
for (Long id : ids) {//遍历传递过来的ids集合
Dish dish = dishMapper.getById(id);//根据id查找dish表中的数据,返回Dish类
//常量1
if(dish.getStatus() == StatusConstant.ENABLE){//判断是否在售
//当前菜品在售所以不允许删除;
//抛不允许删除的异常, //常量,起售菜品不允许删除
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
}
//通过就能知道当前菜品是停售的;进入下一步
//2. 检查是否库删除--被关联的菜品不能删除
//管联的菜品和套餐数据库表--setmeal_dish
List<Long> setmealIds = setmealDishMapper.getSetmealDishIdsByDishIds(ids);
//根据菜品id查找关联表中的id能找到便是关联的,无法删除
if(setmealIds != null && setmealIds.size() > 0){
//经过判断知道菜品是关联着的
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
//到这步可以知道是没有关联的,可以进入下一步;
//3. 删除菜品数据和关联的口味表;
//遍历ids来删除
for (Long id : ids) {
dishMapper.deleteById(id);//根据id删除菜品
dishFlavorMapper.deleteByDishId(id);//根据菜品的id删除口味
}
}
3.mapper层
DishMapper
/**
* 根据主键查询菜品
*
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);
SetmealDishMapper
package com.sky.mapper;
import com.sky.entity.SetmealDish;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SetmealDishMapper {
/**
* 根据菜品id查询对应的套餐id
*
* @param dishIds
* @return
*/
//select setmeal_id from setmeal_dish where dish_id in (1,2,3,4)
List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}
SetmealDishMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealDishMapper">
<select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
select setmeal_id from setmeal_dish where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</select>
</mapper>
DishMapper
/**
* 根据主键删除菜品数据
*
* @param id
*/
@Delete("delete from dish where id = #{id}")
void deleteById(Long id);
DishFlavorMapper
/**
* 根据菜品id删除对应的口味数据
* @param dishId
*/
@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);
修改菜品
需求分析与接口设计
产品原型
接口设计
根据id查询菜品
根据类型查询分类(已实现)
文件上传(已实现)
修改菜品
代码开发
根据id查询菜品实现
controller层
DishController
/**
* 根据id查询菜品
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id) {
log.info("根据id查询菜品:{}", id);
DishVO dishVO = dishService.getByIdWithFlavor(id);//后绪步骤实现
return Result.success(dishVO);
}
service层
DishService
/**
* 根据id查询菜品和对应的口味数据
*
* @param id
* @return
*/
DishVO getByIdWithFlavor(Long id);
DishServiceImpl
/**
* 根据id查询菜品和对应的口味数据
*
* @param id
* @return
*/
public DishVO getByIdWithFlavor(Long id) {
//根据id查询菜品数据
Dish dish = dishMapper.getById(id);
//根据菜品id查询口味数据
List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);//后绪步骤实现
//将查询到的数据封装到VO
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish, dishVO);
dishVO.setFlavors(dishFlavors);
return dishVO;
}
mapper层
DishFlavorMapper
/**
* 根据菜品id查询对应的口味数据
* @param dishId
* @return
*/
@Select("select * from dish_flavor where dish_id = #{dishId}")
List<DishFlavor> getByDishId(Long dishId);
修改菜品实现
controller层
DishController
/**
* 修改菜品
*
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
log.info("修改菜品:{}", dishDTO);
dishService.updateWithFlavor(dishDTO);
return Result.success();
}
service层
DishService
/**
* 根据id修改菜品基本信息和对应的口味信息
*
* @param dishDTO
*/
void updateWithFlavor(DishDTO dishDTO);
DishServiceImpl
/**
* 根据id修改菜品基本信息和对应的口味信息
*
* @param dishDTO
*/
public void updateWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
//修改菜品表基本信息
dishMapper.update(dish);
//删除原有的口味数据
dishFlavorMapper.deleteByDishId(dishDTO.getId());
//重新插入口味数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishDTO.getId());
});
//向口味表插入n条数据
dishFlavorMapper.insertBatch(flavors);
}
}
mapper层
DishMapper
/**
* 根据id动态修改菜品数据
*
* @param dish
*/
@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);
DishMapper.xml
<update id="update">
update dish
<set>
<if test="name != null">name = #{name},</if>
<if test="categoryId != null">category_id = #{categoryId},</if>
<if test="price != null">price = #{price},</if>
<if test="image != null">image = #{image},</if>
<if test="description != null">description = #{description},</if>
<if test="status != null">status = #{status},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="updateUser != null">update_user = #{updateUser},</if>
</set>
where id = #{id}
</update>
第四天内容为实战训练
里面包含了
新增套餐
套餐分页查询
删除套餐
修改套餐
起售停售套餐
第五天
Redis
它是什么?
Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。
Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。
官网:https://redis.io 中文网:https://www.redis.net.cn/
key-value结构存储:
主要特点:
-
基于内存存储,读写性能高
-
适合存储热点数据(热点商品、资讯、新闻)
-
企业应用广泛
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。它存储的value类型比较丰富,也被称为结构化的NoSql数据库。
NoSql(Not Only SQL),不仅仅是SQL,泛指非关系型数据库。NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充。
关系型数据库(RDBMS):
-
Mysql
-
Oracle
-
DB2
-
SQLServer
非关系型数据库(NoSql):
-
Redis
-
Mongo db
-
MemCached
下载安装
Redis安装包分为windows版和Linux版:
-
Windows版下载地址:https://github.com/microsoftarchive/redis/releases
-
Linux版下载地址: Index of /releases/
直接解压即可使用
启动
Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务
当Redis服务启动成功后,可通过客户端进行连接。
redis-server.exe redis.windows.conf
修改Redis配置文件
设置Redis服务密码,修改redis.windows.conf
requirepass 123456
Redis客户端图形工具
下载官网
新建连接
连接成功
在java中使用redis:Spring Data Redis
Spring Boot提供了对应的Starter,maven坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring Data Redis中提供了一个高度封装的类:RedisTemplate,对相关api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:
- ValueOperations:string数据操作
- SetOperations:set类型数据操作
- ZSetOperations:zset类型数据操作
- HashOperations:hash类型的数据操作
- ListOperations:list类型的数据操作
环境搭建
1). 导入Spring Data Redis的maven坐标(已完成)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2). 配置Redis数据源
在application-dev.yml中添加
sky:
redis:
host: localhost
port: 6379
password: 123456
database: 10
解释说明:
database:指定使用Redis的哪个数据库,Redis服务启动后默认有16个数据库,编号分别是从0到15。
可以通过修改Redis配置文件来指定数据库的数量。
在application.yml中添加读取application-dev.yml中的相关Redis配置
spring:
profiles:
active: dev
redis:
host: ${sky.redis.host}
port: ${sky.redis.port}
password: ${sky.redis.password}
database: ${sky.redis.database}
3). 编写配置类,创建RedisTemplate对象
package com.sky.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模板对象...");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
解释说明:
当前配置类不是必须的,因为 Spring Boot 框架会自动装配 RedisTemplate 对象,但是默认的key序列化器为
JdkSerializationRedisSerializer,导致我们存到Redis中后的数据和原始数据有差别,故设置为
StringRedisSerializer序列化器。
4). 通过RedisTemplate对象操作Redis
在test下新建测试类
package com.sky.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;
@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
//string数据操作
ValueOperations valueOperations = redisTemplate.opsForValue();
//hash类型的数据操作
HashOperations hashOperations = redisTemplate.opsForHash();
//list类型的数据操作
ListOperations listOperations = redisTemplate.opsForList();
//set类型数据操作
SetOperations setOperations = redisTemplate.opsForSet();
//zset类型数据操作
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
}
}
店铺营业状态设置
需求分析与接口设计
产品原型
接口设计
设置营业状态
管理端查询营业状态
用户端查询营业状态
营业状态存储方式
代码开发
1. 设置营业状态
在sky-server模块中,创建ShopController.java
根据接口定义创建ShopController的setStatus设置营业状态方法:
package com.sky.controller.admin;
import com.sky.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
public static final String KEY = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置店铺的营业状态
* @param status
* @return
*/
@PutMapping("/{status}")
@ApiOperation("设置店铺的营业状态")
public Result setStatus(@PathVariable Integer status){
log.info("设置店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
redisTemplate.opsForValue().set(KEY,status);
return Result.success();
}
}
2. 管理端查询营业状态
ShopController
/**
* 获取店铺的营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
3. 用户端查询营业状态
创建com.sky.controller.user包,在该包下创建ShopController.java
根据接口定义创建ShopController的getStatus查询营业状态方法:
package com.sky.controller.user;
import com.sky.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
@RestController("userShopController")
@RequestMapping("/user/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
public static final String KEY = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取店铺的营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
return Result.success(status);
}
}
接口分组展示
在WebMvcConfiguration.java中,分别扫描"com.sky.controller.admin"和"com.sky.controller.user"这两个包。
@Bean
public Docket docket1(){
log.info("准备生成接口文档...");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("管理端接口")
.apiInfo(apiInfo)
.select()
//指定生成接口需要扫描的包
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
.paths(PathSelectors.any())
.build();
return docket;
}
@Bean
public Docket docket2(){
log.info("准备生成接口文档...");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("用户端接口")
.apiInfo(apiInfo)
.select()
//指定生成接口需要扫描的包
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
.paths(PathSelectors.any())
.build();
return docket;
}