来源: 黑马程序员Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战
day1
一、项目效果展示
二、项目开发整体介绍
三、项目介绍
3.1 定位
3.2 功能架构
3.3 产品原型
html页面,包括管理端和用户端两种
3.4 技术选型
四、开发环境搭建
4.1 前端环境
前端工程是基于nginx运行的,已经是打包好的程序,直接双击执行exe文件,接着在浏览器访问localhost
即可。
4.2 后端环境
后端工程是基于maven进行构建的,并且进行分模块开发
导入的时候,需要配置maven的settings文件中阿里云镜像
- 使用git进行版本控制:本地创建仓库,远程创建仓库,将本地仓库推送到远程
- 数据库环境搭建:导入数据库文件即可
- 前后端联调:通过断点调试,一步步理清登录功能。
1.获取前端对象(employeeLoginDTO)
2.使用DTO中username属性通过EmployeeMapper查询数据库,得到emplyee对象
3.判断密码是否一致,且用户状态是否为‘启用’,都是则登录成功
4.生成JWK令牌,通过配置文件获取密钥和过期时间,令牌名称为token
5.通过Builder注解的方法生成employeeLoginVO对象,包括主键值、用户名、姓名和jwt令牌
6.后端统一返回结果为Result<T>,封装了错误信息msg和返回的VO对象
7.另外,文件中定义了各种异常,继承自BaseException,它的父类为RuntimeException。通过全局处理器handler来捕获全部的异常,进行处理
-
前端发送的请求,如何请求到后端服务的?
是通过nginx方向代理,将前端发送的动态请求有nginx转发到后端服务器
-
完善登录功能
在数据库中加密存储密码—> 在server登录功能处,将明文密码先加密在进行对比
五、导入接口文档
接口设计能力
前后端分离开发流程。使用apifox导入接口json文件。划分了两个文件,包括管理端和用户端。
六、Swagger
6.1 介绍
6.2 使用方式
最后去网页访问http://localhost:8080/doc.html,会出现下面的界面
6.3 常用注解
day2
一、新增员工
- 根据接口文档,写service。EmployeeController.java
/**
* 新增员工
* @param employeeDTO
* @return
*/
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}", employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
- 完善EmployeeService接口和EmployeeServiceImpl接口实现类的save方法。EmployeeServiceImpl.java
/**
* 新增员工
* @param employeeDTO
*/
public void save(EmployeeDTO employeeDTO) {
// 对象的转换,将DTO转为实体对象
Employee employee = new Employee();
// 对象属性拷贝,将dto对象拷贝给entity。DTO和entity对象的属性名有一部分一致
BeanUtils.copyProperties(employeeDTO,employee);
// 对entity的其他属性初始化
employee.setStatus(StatusConstant.ENABLE); //状态
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes())); //密码
employee.setCreateTime(LocalDateTime.now()); //创建时间
employee.setUpdateTime(LocalDateTime.now()); //修改时间
// todo 后期需要改为当前用户的属性
employee.setCreateUser(10L); //创建人
employee.setUpdateUser(10L); //修改人
employeeMapper.insert(employee);
}
- 完善EmployeeMapper接口中的sql。EmployeeMapper.java
/**
* 插入员工数据
* @param employee
*/
@Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, update_time, " +
"create_user, update_user) values (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, " +
"#{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
void insert(Employee employee);
- 通过接口文档测试功能(常用)或者是前后端联调测试。
通过接口文档测试的时候,发出的请求需要带上token令牌。因此需要添加一个全局变量,名称为token。
- 完善登录功能。重复录入,抛出异常没有处理;新增员工,创建人和修改人设置的是固定值。完善好仍要进行测试。
//1.重复录入,异常处理即可
//GlobalExceptionHandler.java
/**
* 处理sql异常
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
String message = ex.getMessage();
if(message.contains("Duplicate entry")){
String username = message.split(" ")[2];
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else{
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
//2.使用threadLocal方法解决获取当前用户id
//BaseContext.java
package com.sky.context;
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
//JwtTokenAdminInterceptor.java
BaseContext.setCurrentId(empId);
//EmployeeServiceImpl.java
employee.setCreateUser(BaseContext.getCurrentId()); //创建人
employee.setUpdateUser(BaseContext.getCurrentId()); //修改人
二、员工分页查询
//EmployeeController.java
/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
@GetMapping
@ApiOperation("员工分页查询")
public Result<PageResult> pageQuery(EmployeePageQueryDTO employeePageQueryDTO){
log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
//EmployeeServiceImpl.java
/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//select * from employee limit 0,10
//开始分页查询
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total, records);
}
//EmployeeMapper.java
/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
//EmployeeMapper.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.EmployeeMapper">
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>
</mapper>
完善代码:1时间的问题
/**
* 扩张Spring MVC框架的消息转换器
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转化器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中
converters.add(0,converter);
}
三、启用禁用员工账号
//EmployeeController.java
/**
* 启用禁用员工账号
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("启用禁用员工账号:{},{}", status, id);
employeeService.startOrStop(status,id);
return Result.success();
}
//EmployeeServiceImpl.java
/**
* 启用禁用员工账号
* @param status
* @param id
* @return
*/
public void startOrStop(Integer status, Long id) {
//update employee set status=? where id = ?
//为了更通用的使用update方法,在mapper中定义的是通用的update方法,每次传递一个实体对象,而不是修改的属性
// Employee employee = new Employee();
// employee.setStatus(status);
// employee.setId(id);
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
employeeMapper.update(employee);
}
//EmployeeMapper.xml
<update id="update" parameterType="com.sky.entity.Employee">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="idNumber != null">id_number = #{idNumber},</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>
四、编辑员工
两个接口,根据id查询员工,修改员工信息
//EmployeeController.java
/**
* 根据id查询员工
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询员工")
public Result<Employee> getById(@PathVariable Long id){
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
@PutMapping
@ApiOperation("编辑员工信息")
public Result update(@RequestBody EmployeeDTO employeeDTO){
log.info("编辑员工信息:{}", employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
//EmployeeServiceImpl.java
/**
* 根据id查询员工
* @param id
* @return
*/
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****"); // 将传给前端的密码设置为*
return employee;
}
/**
* 编辑员工信息
* @param employeeDTO
*/
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO,employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
//EmployeeMapper.java
/**
* 根据id查询员工
* @param id
* @return
*/
@Select("select * from employee where id = #{id}")
Employee getById(Long id);
五、导入分类模块功能代码
day3
一、公共字段自动填充
//AutoFill.java
package com.sky.annotation;
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
//AutoFullAspect.java
package com.sky.aspect;
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
* @param joinPoint
*/
@Before(value = "autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取当前被拦截方法的数据库操作类型
MethodSignature signature = (MethodSignature)joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取当前被拦截方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0) return;
Object entity = args[0];
//为公共属性赋值,时间和当前用户的id【反射方式】
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
if(operationType == OperationType.INSERT){
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = 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);
//通过反射为对象属性赋值
setCreateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
try {
Method 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);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
二、文件上传的接口开发
补充学习:阿里云OSS(对象存储服务OSS)【Java web2023版本 第148集】
配置alioss
//配置属性类 AliOssProperties.java
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;
}
//配置类 OssConfiguration.java
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());
}
}
//CommonController.java
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;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file){
log.info("文件上传:{}",file);
try {
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取原始文件名的后缀
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);
e.printStackTrace();
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
三、新增菜品
增加一条菜品记录的同时需要增加多条口味记录,用到事务和插入数据的id获取
//DishController.java
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
return Result.success();
}
}
//DishServiceImpl.java
@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);
//向菜品表插入一条数据
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);
}
}
}
//DishMapper.java
/**
* 增加菜品
* @param dish
*/
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
//DishMapper.xml
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status,
create_time, update_time, create_user, update_user)
values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status},
#{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
//DishFavorMapper.java
/**
* 批量插入口味数据
* @param flavors
*/
void insertBatch(List<DishFlavor> flavors);
//DishFavorMapper.xml
<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>
四、菜品分页查询
根据页码展示信息,每页展示10条数据,可以根据需要输入菜品名称、分类、状态进行查询
//DishController.java
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
log.info("菜品分页查询:{}", dishPageQueryDTO);
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);
}
//DishServiceImpl.java
/**
* 菜品分页查询
* @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());
}
//DishMapper.java
/**
* 菜品分页查询
* @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 like concat('%',#{categoryId},'%')</if>
<if test="status != null">and d.status like concat('%',#{status},'%')</if>
</where>
order by d.create_time desc
</select>
五、删除菜品
单个删除和批量删除;在售卖的菜品不能删除;被套餐关联的菜品不能删除;删除菜品后,口味也要删除;
【事务;批量查询语句;批量删除语句】
//DishMapperController.java
/**
* 批量删除菜品
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("批量删除菜品")
public Result delete(@RequestParam List<Long> ids){
log.info("批量删除菜品,参数:{}",ids);
dishService.delete(ids);
return Result.success();
}
//DishServiceImpl.java
/**
* 批量删除菜品
* @param ids
*/
@Transactional
public void delete(List<Long> ids) {
//判断是否为售卖的菜品
for (Long id : ids) {
Dish dish = dishMapper.getById(id);
if(dish.getStatus() == StatusConstant.ENABLE){
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
}
//判断是否关联套餐
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
if(setmealIds != null && setmealIds.size()>0){
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
//删除菜品表中的菜品数据并删除菜品关联的口味数据
// for (Long id : ids) {
// dishMapper.deleteById(id);
// //
// dishFlavorMapper.deleteByDishId(id);
// }
//删除的时候可以直接批量删除,减少sql语句
dishMapper.deleteByIds(ids); //删除菜品
dishFlavorMapper.deleteByDishIds(ids); //删除口味
}
//DishMapper.java
/**
* 根据主键查询菜品
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);
/**
* 根据主键删除菜品
* @param id
*/
@Delete("delete from dish where id = #{id}")
void deleteById(Long id);
/**
* 根据主键批量删除菜品
* @param ids
*/
void deleteByIds(List<Long> ids);
//DishMapper.xml
<delete id="deleteByIds">
delete from dish where id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
//SetmealDishMapper.java
/**
* 根据多个菜品id查询对应的套餐id
* @param dishIds
* @return
*/
List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
//SetmealDishMapper.xml
<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>
//DishFlavorMapper.java
/**
* 根据菜品id删除口味数据
* @param dishId
*/
@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);
/**
* 根据菜品id批量删除口味数据
* @param dishIds
*/
void deleteByDishIds(List<Long> dishIds);
//DishFlavorMapper.xml
<delete id="deleteByDishIds">
delete from dish_flavor where dish_id in
<foreach collection="dishIds" open="(" close=")" separator="," item="dishId">
#{dishId}
</foreach>
</delete>
六、修改菜品
查询用于回显数据;展示菜品分类;图片的重新上传;修改菜品
//DishController.java
/**
* 修改菜品
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品,参数为{}", dishDTO);
dishService.updateWithFlavor(dishDTO);
return Result.success();
}
//DishServiceImpl.java
/**
* 根据id修改菜品和对应的口味信息
* @param dishDTO
*/
@Transactional
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);
}
}
//DishMapper.java
/**
* 修改菜品
* @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>
七、起售停售菜品
//DishController.java
/**
* 菜品的起售、停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("菜品的起售停售")
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("菜品的起售、停售,参数为:{}",status);
dishService.startOrStop(status, id);
return Result.success();
}
//DishServiceImpl.java
/**
* 菜品的起售、停售
* @param status
* @param id
*/
public void startOrStop(Integer status, Long id) {
Dish build = Dish.builder().status(status).id(id).build();
dishMapper.update(build);
// 停售菜品的时候,把含相关菜品的套餐也停售
if (status == StatusConstant.DISABLE) {
List<Long> dishIds = new ArrayList<>();
dishIds.add(id);
// select setmeal_id from setmeal_dish where dish_id in (?,?,?)
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);
if (setmealIds != null && setmealIds.size() > 0) {
for (Long setmealId : setmealIds) {
Setmeal setmeal = Setmeal.builder()
.id(setmealId)
.status(StatusConstant.DISABLE)
.build();
setmealMapper.update(setmeal);
}
}
}
}
day4
项目实战—套餐管理
一、新增套餐
增加一条套餐记录,并返回主键;增加多条对应的套餐菜品数据;显示出菜品列表;上传文件
//SetmealController.java
/**
* 新增套餐
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation("新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO){
log.info("新增套餐,参数为:{}", setmealDTO);
setmeatService.saveWithDish(setmealDTO);
return Result.success();
}
//SetmealServiceImpl.java
/**
* 新增套餐和对应的菜品
* @param setmealDTO
*/
@Transactional
public void saveWithDish(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);
//向套餐表插入一条数据
setmealMapper.insert(setmeal);
//获取insert语句生成的主键值
Long setmealId = setmeal.getId();
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
if(setmealDishes != null && setmealDishes.size()>0){
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//向套餐菜品表插入多条数据
setmealDishMapper.insertBatch(setmealDishes);
}
}
//SetmealMapper.java
/**
* 增加套餐
* @param setmeal
*/
@AutoFill(value = OperationType.INSERT)
void insert(Setmeal setmeal);
//SetmealMapper.xml
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into setmeal (category_id, name, price, status, description, image, create_time,
update_time, create_user, update_user) VALUES
(#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime},
#{updateTime}, #{createUser}, #{updateUser})
</insert>
//SetmealDishMapper.java
/**
* 批量插入菜品数据
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);
//SetmealDishMapper.xml
<insert id="insertBatch">
insert into setmeal_dish (setmeal_id, dish_id, name, price, copies) VALUES
<foreach collection="setmealDishes" item="sd" separator=",">
(#{sd.setmealId}, #{sd.dishId}, #{sd.name}, #{sd.price}, #{sd.copies})
</foreach>
</insert>
//DishMapper.java
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
@Select("select * from dish where category_id = #{categoryId}")
List<Dish> getByCategoryId(Integer categoryId);
二、套餐分页查询
分页查询,使用PageHelper,返回类型为Page。需要进行多表查询,(套餐表和分类表)
//SetmealController.java
/**
* 套餐分页查询
* @param setmealPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("套餐分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){
log.info("套餐分页查询,参数为:{}",setmealPageQueryDTO);
PageResult pageResult = setmeatService.pageQuery(setmealPageQueryDTO);
return Result.success(pageResult);
}
//SetmealServiceImpl.java
/**
* 套餐分页查询
* @param setmealPageQueryDTO
* @return
*/
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
PageHelper.startPage(setmealPageQueryDTO.getPage(),setmealPageQueryDTO.getPageSize());
Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
return new PageResult(page.getTotal(),page.getResult());
}
//SetmealMapper.java
/**
* 套餐分页查询
* @param setmealPageQueryDTO
* @return
*/
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
//SetmealMapper.xml
<select id="pageQuery" resultType="com.sky.vo.SetmealVO">
select s.*, c.name as categoryName from setmeal s LEFT OUTER JOIN category c on s.category_id = c.id
<where>
<if test="name != null">and s.name like concat('%',#{name},'%')</if>
<if test="categoryId != null">and s.category_id like concat('%',#{categoryId},'%')</if>
<if test="status != null">and s.status like concat('%',#{status},'%')</if>
</where>
order by s.create_time desc
</select>
三、删除套餐
删除套餐的时候,起售中的不能删除。同时需要将相关的套餐菜品数据删除
//SetmealController.java
/**
* 批量删除套餐
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids){
log.info("批量删除套餐,参数为:{}",ids);
setmeatService.delete(ids);
return Result.success();
}
//SetmealServiceImpl.java
/**
* 根据id批量删除套餐
* @param ids
*/
@Transactional
public void delete(List<Long> ids) {
//判断是否为售卖的套餐
for (Long id : ids) {
Setmeal setmeal = setmealMapper.getById(id);
if (setmeal.getStatus() == StatusConstant.ENABLE){
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
}
//删除套餐和套餐菜品数据,直接批量删除
setmealMapper.deleteByIds(ids);
setmealDishMapper.deleteBySetmealIds(ids);
}
//SetmealMapper.java
/**
* 批量删除套餐
* @param ids
*/
void deleteByIds(List<Long> ids);
//SetmealMapper.xml
<delete id="deleteByIds">
delete from setmeal where id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
//SetmealDishMapper.java
/**
* 根据套餐id批量删除套餐菜品数据
* @param setmealIds
*/
void deleteBySetmealIds(List<Long> setmealIds);
//SetmealDishMapper.xml
<delete id="deleteBySetmealIds">
delete from setmeal_dish where setmeal_id in
<foreach collection="setmealIds" open="(" close=")" separator="," item="setmealId">
#{setmealId}
</foreach>
</delete>
四、修改套餐
先根据id查询套餐用于数据回显,查询套餐,查询套餐菜品,将两者封装为VO对象返回给前端;
接着修改套餐表和套餐菜品表,套餐菜品表中的相关记录先全部删除,再全部插入
//SetmealController.java
/**
* 根据id查询套餐
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id){
log.info("根据id查询套餐,参数为:{}", id);
SetmealVO setmealVO = setmeatService.getByIdWithSetmealDish(id);
return Result.success(setmealVO);
}
/**
* 修改套餐
* @param setmealDTO
* @return
*/
@PutMapping
@ApiOperation("修改套餐")
public Result update(@RequestBody SetmealDTO setmealDTO){
log.info("修改套餐,参数为:{}",setmealDTO);
setmeatService.updateWithSetmealDish(setmealDTO);
return Result.success();
}
//SetmealServiceImpl.java
/**
* 根据id查询套餐和对应的套餐菜品
* @param id
* @return
*/
public SetmealVO getByIdWithSetmealDish(Long id) {
//查询套餐
Setmeal setmeal = setmealMapper.getById(id);
//查询套餐菜品
List<SetmealDish> setmealDishes = setmealDishMapper.getByDishIds(id);
//封装为VO
SetmealVO setmealVO = new SetmealVO();
BeanUtils.copyProperties(setmeal,setmealVO);
setmealVO.setSetmealDishes(setmealDishes);
return setmealVO;
}
/**
* 修改套餐和对应的套餐菜品数据
* @param setmealDTO
*/
@Transactional
public void updateWithSetmealDish(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);
setmealMapper.update(setmeal);
//删除套餐中菜品数据
setmealDishMapper.deleteBySetmealId(setmealDTO.getId());
//将新的套餐菜品数据插入
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
if(setmealDishes != null && setmealDishes.size()>0){
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealDTO.getId());
});
//向套餐菜品表插入多条数据
setmealDishMapper.insertBatch(setmealDishes);
}
}
//SetmealMapper.java
/**
* 根据id查询数据
* @param id
* @return
*/
@Select("select * from setmeal where id = #{id}")
Setmeal getById(Long id);
/**
* 修改套餐数据
* @param setmeal
*/
@AutoFill(value = OperationType.UPDATE)
void update(Setmeal setmeal);
//SetmealMapper.xml
<update id="update">
update setmeal
<set>
<if test="categoryId">category_id = #{categoryId},</if>
<if test="name">name = #{name},</if>
<if test="price">price = #{price},</if>
<if test="status">status = #{status},</if>
<if test="description">description = #{description},</if>
<if test="image">image = #{image},</if>
<if test="updateTime">update_time = #{updateTime},</if>
<if test="updateUser">update_user = #{updateUser},</if>
</set>
where id = #{id}
</update>
//SetmealDishMapper.java
/**
* 批量插入菜品数据
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);
/**
* 根据套餐id查询菜品
* @param setmealId
* @return
*/
@Select("select * from setmeal_dish where setmeal_id = #{setmealId)}")
List<SetmealDish> getByDishIds(Long setmealId);
/**
* 根据套餐id删除套餐菜品数据
* @param setmealId
*/
@Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
void deleteBySetmealId(Long setmealId);
//SetmealDishMapper.xml
<insert id="insertBatch">
insert into setmeal_dish (setmeal_id, dish_id, name, price, copies) VALUES
<foreach collection="setmealDishes" item="sd" separator=",">
(#{sd.setmealId}, #{sd.dishId}, #{sd.name}, #{sd.price}, #{sd.copies})
</foreach>
</insert>
五、起售停售套餐
修改套餐中的状态即可
//SetmealController.java
/**
* 套餐起售、停售
* @param status
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐起售、停售")
public Result startOrStop(@PathVariable Integer status, Long id){
log.info("套餐起售、停售,参数为:{}",status);
setmeatService.startOrStop(status,id);
return Result.success();
}
//SetmealServiceImpl.java
/**
* 套餐起售、停售
* @param status
* @param id
*/
public void startOrStop(Integer status,Long id) {
// 起售套餐要先判断所含菜品是否起售
if(status == StatusConstant.ENABLE){
// 根据套餐setmeal_id在setmeal_dish表中查询dish_id,接着根据菜品dish_id在dish表中查询status
List<Long> dishIds = setmealDishMapper.getDishIdsBySetmealId(id);
for (Long dishId : dishIds) {
Dish dish = dishMapper.getById(dishId);
if(dish.getStatus() == StatusConstant.DISABLE) {
// status = 0时,抛出异常
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
}
}
}
Setmeal build = Setmeal.builder().status(status).id(id).build();
setmealMapper.update(build);
}
// SetmealDishMapper.java
/**
* 根据套餐id查询套餐菜品数据
* @param setmealId
*/
@Select("select dish_id from setmeal_dish where setmeal_id = #{setmealId)}")
List<Long> getDishIdsBySetmealId(Long setmealId);
day5
一、Redis入门
1.1 Redis简介
- 是一个基于内存的key-value结构数据库
- 基于内存存储、读写性能高
- 适合存储热点数据
1.2 下载与安装
windows安装包直接解压,就可直接使用
启动服务端:
客户端连接:
有密码的连接方式
在配置文件中设置密码
也可以安装redis客户端another redis desktop manager,通过图像界面操作客户端。新建连接localhost,要确保redis服务器打开。点击就可以看到存储的键值对信息。
二、常用数据类型
三、常用命令
1. 字符串常用操作命令
set key value # 设置指定key的值
get key # 获取指定key的值
setex key seconds value # 设置指定key的值,并将key的过期时间设为seconds秒
setnx key value # 只有在key不存在时设置key的值
2. 哈希常用操作命令
hset key field value # 将哈希表key中的字段field的值设为value
hget key field # 获取存储在哈希表中指定字段的值
hdel key field # 删除存储在哈希表中指定字段
hkeys key # 获取哈希表中所有字段
hvals key # 获取哈希表中所有值
3. 列表常用操作命令
lpush key value1 [value2] # 将1个或多个值插入到列表头部
lrange key start stop # 获取列表指定范围内的元素
rpop key # 移除并获取列表最后一个元素
llen key # 获取列表长度
4. 集合常用操作命令
sadd key member1 [member2] # 向集合添加一个或多个成员
smembers key # 返回集合中的所有成员
scard key # 获取集合的成员数
sinter key1 [key2] # 返回给定所有集合的交集
sunion key1 [key2] # 返回所有给定集合的并集
srem key member1 [member2] # 删除集合中一个或多个成员
5. 有序集合常用操作命令
zadd key score1 member1 [score2 member2] # 向有序集合添加一个或多个成员
zrange key start stop [WITHSCORES] # 通过索引区间返回有序集合中指定区间内的成员
zincrby key increment member # 有序集合中对指定成员的分数加上增量increment
zrem key member [member] # 移除有序集合中的一个或多个成员
6. 通用命令
keys pattern # 查找所有符合给定模式(pattern)的key
exists key # 检查给定key是否存在
type key # 返回key所存储的值的类型
del key # 在key存在的时候删除key
四、在java中操作Redis
4.1 Redis的java客户端
常用的有Jedis、Lettuce、Spring Data Redis
4.2 Spring Data Redis使用方式操作步骤:
- 导入Spring Data Redis 的maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置Redis数据源
spring:
redis:
host: ${sky.redis.host}
port: ${sky.redis.port}
password: ${sky.redis.password}
database: ${sky.redis.database}
- 编写配置类,创建RedisTemplate对象
package com.sky.config;
@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;
}
}
- 通过RedisTemplate对象操作Redis,这里使用springboot的测试类操作对象
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.connection.DataType;
import org.springframework.data.redis.core.*;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
}
/**
* 操作字符串类型的数据
*/
@Test
public void testString(){
// set get setex setnx
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("name","小明");
String name = (String)valueOperations.get("name");
valueOperations.set("code","1234",3, TimeUnit.MINUTES);
valueOperations.setIfAbsent("lock","1");
valueOperations.setIfAbsent("lock","2");
System.out.println(name);
}
/**
* 操作哈希类型的数据
*/
@Test
public void testHash(){
// hset hget hdel hkeys hvals
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.put("100","name","tom");
hashOperations.put("100","age","20");
String name = (String)hashOperations.get("100","name");
System.out.println(name);
Set keys = hashOperations.keys("100");
System.out.println(keys);
List values = hashOperations.values("100");
System.out.println(values);
hashOperations.delete("100","age");
}
/**
* 操作列表类型的数据
*/
@Test
public void testList(){
//lpush lrange rpop llen
ListOperations listOperations = redisTemplate.opsForList();
listOperations.leftPushAll("list","1","2","3","4","5");
listOperations.leftPush("list","a");
listOperations.leftPush("list","b");
listOperations.leftPush("list","c");
List list = listOperations.range("list",1, 2);
System.out.println(list);
Object value = listOperations.leftPop("list");
System.out.println(value);
Long len = listOperations.size("list");
System.out.println(len);
}
/**
* 操作集合类型的数据
*/
@Test
public void testSet(){
//sadd smembers scard sinter sunion srem
SetOperations setOperations = redisTemplate.opsForSet();
setOperations.add("set","a","b","c","d");
setOperations.add("set1","1","2","3","a");
Set members = setOperations.members("set");
System.out.println(members);
Long len = setOperations.size("set");
System.out.println(len);
Set intersect = setOperations.intersect("set", "set1");
System.out.println(intersect);
Set union = setOperations.union("set", "set1");
System.out.println(union);
setOperations.remove("set","a","b");
}
/**
* 操作有序集合类型的数据
*/
@Test
public void testzSet(){
//zadd zrange zincrby zrem
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("zset","3",0.1);
zSetOperations.add("zset","2",0.8);
zSetOperations.add("zset","8",0.7);
Set zset = zSetOperations.range("zset", 1, 2);
System.out.println(zset);
zSetOperations.incrementScore("zset","3",0.8);
zSetOperations.remove("zset","3");
}
/**
* 操作通用命令
*/
@Test
public void testCommon(){
//keys exists type del
Set keys = redisTemplate.keys("*");
System.out.println(keys);
Boolean code = redisTemplate.hasKey("code");
Boolean list = redisTemplate.hasKey("list");
for (Object key : keys) {
DataType type = redisTemplate.type(key);
System.out.println(type.name());
}
redisTemplate.delete("list");
}
}
五、店铺营业状态设置
5.1 代码实现
三个接口:设置店铺的营业状态;管理端获取店铺的营业状态;用户端获取店铺的营业状态。
//admin.ShopController.java
package com.sky.controller.admin;
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Slf4j
@Api(tags = "店铺相关接口")
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();
}
/**
* 获取店铺的营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("获取店铺的营业状态为:{}", status == 1 ? "营业中" : "打样中");
return Result.success(status);
}
}
//user.ShopController
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")
@Slf4j
@Api(tags = "店铺相关接口")
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);
}
}
5.2 接口文档实现分组管理
在配置文件WebConfiguration.java中,新增加docket()方法,并修改相应内容,使其扫描不同的包
效果为: