1.项目结构
1.项目模块
序号 | 名称 | 说明 |
---|---|---|
1 | sky-take-out | maven父工程,统一管理依赖版本,聚合其他子模块 |
2 | sky-common | 子模块,存放公共类,例如:工具类、常量类、异常类等 |
3 | sky-pojo | 子模块,存放实体类、VO、DTO等 |
4 | sky-server | 子模块,后端服务,存放配置文件、Controller、Service、Mapper等 |
2.sky-common
名称 | 说明 |
---|---|
constant | 存放相关常量类 |
context | 存放上下文类 |
enumeration | 项目的枚举类存储 |
exception | 存放自定义异常类 |
json | 处理json转换的类 |
properties | 存放SpringBoot相关的配置属性类 |
result | 返回结果类的封装 |
utils | 常用工具类 |
3.sky-pojo
名称 | 说明 |
---|---|
Entity | 实体,通常和数据库中的表对应 |
DTO | 数据传输对象,通常用于程序中各层之间传递数据 |
VO | 视图对象,为前端展示数据提供的对象 |
POJO | 普通Java对象,只有属性和对应的getter和setter |
4.sky-server
名称 | 说明 |
---|---|
config | 存放配置类 |
controller | 存放controller类 |
interceptor | 存放拦截器类 |
mapper | 存放mapper接口 |
service | 存放service类 |
SkyApplication | 启动类 |
2.ThreadLocal
用户发起的每一次请求是一个线程,每个线程有一个共享的线程空间
ThreadLocal类用来提供线程内部的局部变量,不同的线程之间不会相互干扰,可以使用线程空间
使用三个方法set(),get(),remove()
本项目使用工具类封装ThreadLocal便于使用
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();
}
}
3.员工模块
1.员工登录
1.EmployeeController
接收用户请求->调用Service处理业务->将处理结果返回给前端
(1)Controller类上添加注解
//Spring注解,用于标记控制类
@RestController
//类上添加,整个类上的方法都被添加上url
@RequestMapping("/admin/employee")
//lombok注解,将为该类创建一个名为log的日志对象
//生成的对象log可以直接使用,调用log.debug(),log.info()进行日志输出
@Slf4j
//swagger注解,用于Controller类,对类进行说明
@Api(tags = "员工相关接口")
(2)创建EmployeeService和JwtProperties对象并添加@Autowired注解自动注入
(3)创建login方法,并添加注解
//前端提交方式->Post,URL->/admin/employee/login
@PostMapping("/login")
//swagger用于生成说明方法功能的注解,若不添加在swagger中默认以方法名作为说明
@ApiOperation("员工登录")
//返回EmployeeLoginVO->VO是视图对象,为前端展示数据提供的对象
//传入EmployeeLoginDTO->数据传输对象,用于程序中各层之间传递数据
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成jwt令牌
//放入到token中
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
//建造者模式->相当于构造函数+set()
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
2.EmployeeService
处理业务
(1)在EmployeeServiceImpl类上添加@Service注解
(2)创建EmployeeMapper对象,添加@Autowired注解自动注入
(3)添加login方法
//返回查询到的Employee实体对象
public Employee login(EmployeeLoginDTO employeeLoginDTO) {
String username = employeeLoginDTO.getUsername();
String password = employeeLoginDTO.getPassword();
//1、根据用户名查询数据库中的数据
Employee employee = employeeMapper.getByUsername(username);
//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
if (employee == null) {
//账号不存在
//自定义的账号查询异常,传递自定义的信息常量->
//public static final String ACCOUNT_NOT_FOUND = "账号不存在";
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
//密码比对
//进行md5加密,然后再进行比对->Java提供的工具类可用于md5加密
password = DigestUtils.md5DigestAsHex(password.getBytes());
if (!password.equals(employee.getPassword())) {
//密码错误
//自定义的密码错误异常,传递自定义的信息常量->
//public static final String PASSWORD_ERROR = "密码错误";
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
if (employee.getStatus() == StatusConstant.DISABLE) {
//账号被锁定
//自定义的账号锁定异常,传递自定义的信息常量->
//public static final String ACCOUNT_LOCKED = "账号被锁定";
throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
}
//账号存在,密码正确,账号未锁定
//3、返回实体对象
return employee;
}
3.EmployeeMapper
与数据库交互
//与数据库交互的类使用实体类
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
2.员工登出
@PostMapping("/logout")
@ApiOperation("员工登出")
public Result<String> logout() {
return Result.success();
}
3.Jwt校验拦截器
(1)创建JwtTokenAdminInterceptor类,继承HandlerInterceptor类,添加@Component注解
(2)创建JwtProperties类,添加@Autowired注解
(3)重写preHandle方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取jwt令牌(Controller中创建jwt后放入到了token中)
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:", empId);
//BaseContext为封装ThreadLocal的工具类,此方法可以将用户ID存储到ThreadLocal中
BaseContext.setCurrentId(empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
4.新增员工
1.EmployeeController
创建save方法,并添加注解
//新增员工
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
employeeService.save(employeeDTO);
return Result.success();
}
2.EmployeeService
添加save方法
//新增员工
//DTO用于不同层级之间传递
@Override
public void save(EmployeeDTO employeeDTO) {
//Mapper层与数据库交互,需要使用实体类
Employee employee = new Employee();
//对象属性拷贝
//将两个对象的属性根据名称拷贝赋值内容(source,target)
BeanUtils.copyProperties(employeeDTO,employee);
//设置状态,状态常量->可使用
employee.setStatus(StatusConstant.ENABLE);
//设置密码,默认123456
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置当前创建人和修改人
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.insert(employee);
}
3.EmployeeMapper
//插入用户
@Insert("insert into sky_take_out.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);
4.用户名重复时的异常处理
在全局异常处理器GlobalExceptionHandler(sky-server->handler->GlobalExceptionHandler)中添加exceptionHandler方法,拦截抛出的SQLIntegrityConstraintViolationException异常,并添加ExceptionHandler注解
且当message中包含Duplicate entry时提示该用户名已存在,否则抛出信息常量的未知异常
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException exception){
//java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'lisi' for key 'employee.idx_username'
String message = exception.getMessage();
if (message.contains("Duplicate entry")){
String[] split = message.split(" ");
String username = split[2];
String msg = username+ MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
5.分页查询
1.EmployeeController
创建pageQuery方法,并添加注解,指定URL
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> pageQuery(EmployeePageQueryDTO employeePageQueryDTO){
//使用自定义的PageResult类接收数据->total,总记录数;records,当前页数据集合
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
2.EmployeeService
添加pageQuery方法
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//使用PageHelper插件自动分页->传递第几页和页大小
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
return new PageResult(page.getTotal(),page.getResult());
}
3.EmployeeMapper
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
由于分页查询是动态SQL->必须使用mapper.xml文件
在resources包内创建mapper包,包内创建EmployeeMapper.xml文件(同名)
<!--namespace->指定绑定的java文件-->
<mapper namespace="com.sky.mapper.EmployeeMapper">
<!--
id->指定绑定的类方法
resultType->指定返回的参数类型
-->
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from sky_take_out.employee
<where>
<!--判断name是否存在-->
<if test="name!=null and name!=''">
<!--模糊查询-->
and name like concat('%',#{name},'%')
</if>
</where>
<!--根据创建时间倒序排序-->
order by create_time desc
</select>
</mapper>
4.修改时间格式
由于从数据库中查询出的时间的格式不正确,需要修改时间格式,有两种方式
1.在返回数据的实体类上使用注解
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
在需要修改的项少时可以使用,多时可以直接统一在配置处修改
2.在WebMvcConfiguration类中添加消息转化器
复写extendMessageConverters方法,扩展SpringMVC框架的消息转化器
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建一个消息转换器对象
MappingJackson2CborHttpMessageConverter converter = new MappingJackson2CborHttpMessageConverter();
//为消息转换器设置一个对象转换器,将Java对象序列化转化为json数据
//新建的对象转换器是自定义的
converter.setObjectMapper(new JacksonObjectMapper());
//将自定义的消息转换器加入容器中
converters.add(0,converter);
}
自定义SpringMVC配置
创建WebMvcConfiguration类继承WebMvcConfigurationSupport
类中可以自定义许多SpringMVC中的配置
(1)添加自定义拦截器->addInterceptors
在类中创建自定义的JwtTokenAdminInterceptor拦截器对象
使用addInterceptors方法添加拦截器和拦截的URL
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
}
(2)设置静态资源映射
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
(3)扩展SpringMVC框架的消息转化器
7.启用禁用账号状态
1.EmployeeController
创建startOrStop方法,并添加注解,指定URL
@PostMapping("/status/{status}")
@ApiOperation("启动或停用员工账号")
public Result startOrStop(@PathVariable Integer status,Long id){
employeeService.startOrStop(status,id);
return Result.success();
}
@PathVariable("status")可以将URL中占位符参数绑定到方法中->
前端URL为/admin/employee/status/1
方法注解为/status/{status}
->将{}内的数据绑定到方法中->status=1
2.EmployeeService
添加startOrStop方法
@Override
public void startOrStop(Integer status, Long id) {
Employee employee = Employee.builder()
.status(status)
.id(id)
.createUser(BaseContext.getCurrentId())
.build();
employeeMapper.update(employee);
}
3.EmployeeMapper
添加update方法,使方法可以用于多种内容的更新
->使用动态SQL语句,判断参数是否存在,如果存在则修改
->动态SQL不能使用注解SQL,在xml文件中添加SQL语句
<update id="update" parameterType="Employee">
update sky_take_out.employee
<set>
<if test="name!=null and name!=''">name = #{name},</if>
<if test="username!=null and username!=''">username = #{username},</if>
<if test="password!=null and password!=''">password = #{password},</if>
<if test="phone!=null and phone!=''">phone = #{name},</if>
<if test="sex!=null and sex!=''">sex = #{sex},</if>
<if test="idNumber!=null and idNumber!=''">id_number = #{idNumber},</if>
<if test="status!=null">status = #{status},</if>
<if test="updateUser!=null">update_user = #{updateUser},</if>
update_time = now()
</set>
where id=#{id}
</update>
8.编辑员工
1.根据id查询员工信息
1.EmployeeController
创建queryById方法,并添加注解,指定URL
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<EmployeeDTO> queryById(@PathVariable Long id){
EmployeeDTO employeeDTO = employeeService.queryById(id);
return Result.success(employeeDTO);
}
2.EmployeeService
添加queryById方法
@Override
public EmployeeDTO queryById(Long id) {
return employeeMapper.queryById(id);
}
3.EmployeeMapper
@Select("select * from employee where id = #{id}")
EmployeeDTO queryById(Long id);
2.编辑员工信息
1.EmployeeController
创建modify方法,并添加注解,指定URL
@PutMapping
@ApiOperation("修改员工信息")
public Result modify(@RequestBody EmployeeDTO employeeDTO){
employeeService.modify(employeeDTO);
return Result.success();
}
2.EmployeeService
添加modify方法
@Override
public void modify(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO,employee);
employee.setCreateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
3.修改用户信息的mapper方法已存在,直接调用即可
4.菜品分类模块
1.新增菜品分类
1.新建CategoryController
(1)添加注解
@RestController
@Api(tags = "菜品分类")
@RequestMapping(("/admin/category"))
Controller应该添加的注解是@RestController而不是@Controller
(2)创建对象CategoryService,添加注解@Autowired自动注入
(3)添加save方法
@PostMapping
@ApiOperation("新增分类")
public Result save(@RequestBody CategoryDTO categoryDTO){
categoryService.save(categoryDTO);
return Result.success();
}
2. 新建CategoryServiceImpl类
(1)实现CategoryService接口,添加@Service注解
(2)创建对象CategoryMapper,添加注解@Autowired自动注入
(3)添加save方法
@Override
public void save(CategoryDTO categoryDTO) {
Category category = new Category();
BeanUtils.copyProperties(categoryDTO,category);
category.setStatus(StatusConstant.DISABLE);
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
category.setCreateUser(BaseContext.getCurrentId());
category.setUpdateUser(BaseContext.getCurrentId());
categoryMapper.save(category);
}
3.新建CategoryMapper接口
(1)添加@Mapper注解
(2)添加save方法
@Insert("insert into sky_take_out.category (type, name, sort, status, create_time, update_time, create_user, update_user) " +
"VALUES " +
"(#{type},#{name},#{sort},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
void save(Category category);
2.菜品分类分页查询
1.CategoryController
创建pageQuery方法,并添加注解,指定URL
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO){
PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);
return Result.success(pageResult);
}
2.CategoryService
添加pageQuery方法
@Override
public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize());
Page<Category> categories = categoryMapper.pageQuery(categoryPageQueryDTO);
return new PageResult(categories.getTotal(),categories.getResult());
}
3.CategoryMapper
Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);
由于pageQuery传递的参数可变,必须使用动态SQL->创建CategoryMapper.xml
将namespace指定为com.sky.mapper.CategoryMapper
添加select
<select id="pageQuery" resultType="Category">
select * from sky_take_out.category
<where>
<if test="name!=null and name!=''">
and name like concat('%',#{name},'%')
</if>
<if test="type!=null">
and type = #{type}
</if>
</where>
order by sort asc,create_time desc
</select>
3.根据id删除菜品分类
1.CategoryController
创建deleteById方法,并添加注解,指定URL
@DeleteMapping
@ApiOperation("删除菜品分类")
public Result<String> deleteById(Long id){
categoryService.deleteById(id);
return Result.success();
}
2.CategoryService
添加deleteById方法
@Override
public void deleteById(Long id) {
if (dishMapper.queryCount(id)>0) {
//当前分类下有菜品,不能删除
throw new DeletionNotAllowedException("当前分类下有菜品,不能删除");
}
if (setMealMapper.queryCount(id)>0) {
//当前套餐下有菜品,不能删除
throw new DeletionNotAllowedException("当前套餐下有菜品,不能删除");
}
categoryMapper.deleteById(id);
}
由于数据库中的表之间有关联关系,需要根据Category的ID判断是否存在关联,没有关联时才能删除
(1)创建SetMealMapper和DishMapper,分别添加根据category_id计算数量的方法
@Select("select count(id) from setmeal where category_id = #{categoryId}")
Integer queryCount(Long categoryId);
@Select("select count(id) from dish where category_id = #{categoryId}")
Integer queryCount(Long categoryId);
(2)在CategoryServiceImpl中创建SetMealMapper和DishMapper对象,添加@Autowired注解
当两个表中使用该id的表项都为0时才可删除,否则抛出自定义异常DeletionNotAllowedException
3.CategoryMapper
@Delete("delete from category where id = #{id}")
void deleteById(Long id);
4.修改菜品分类
1.CategoryController
创建modify方法,并添加注解,指定URL
@PutMapping
@ApiOperation("修改菜品分类")
public Result<String> modify(@RequestBody CategoryDTO categoryDTO){
categoryService.modify(categoryDTO);
return Result.success();
}
2.CategoryService
添加modify方法
@Override
public void modify(CategoryDTO categoryDTO) {
Category category = new Category();
BeanUtils.copyProperties(categoryDTO,category);
category.setUpdateTime(LocalDateTime.now());
category.setUpdateUser(BaseContext.getCurrentId());
categoryMapper.update(category);
}
3.CategoryMapper
使用动态SQL,可复用于更新数据
void update(Category category);
<update id="update" parameterType="Category">
update sky_take_out.category
<set>
<if test="name!=null and name!=''">name=#{name},</if>
<if test="type!=null">type=#{type},</if>
<if test="sort!=null">sort=#{sort},</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>
5.启用禁用菜品分类
1.CategoryController
创建startOrStop方法,并添加注解,指定URL
@PostMapping("/status/{status}")
@ApiOperation("启用禁用分类")
public Result<String> startOrStop(@PathVariable Integer status,Long id){
categoryService.startOrStop(status,id);
return Result.success("修改成功");
}
2.CategoryService
添加startOrStop方法
@Override
public void startOrStop(Integer status, Long id) {
Category category = new Category();
category.setId(id);
category.setStatus(status);
categoryMapper.update(category);
}
3.CategoryMapper
可以直接调用update的方法
6.根据类型查询菜品分类
1.CategoryController
创建queryByType方法,并添加注解,指定URL
@GetMapping("/list")
@ApiOperation("根据类型查询分类")
public Result<List<Category>> queryByType(Integer type){
List<Category> categories = categoryService.queryByType(type);
return Result.success(categories);
}
2.CategoryService
添加queryByType方法
@Override
public List<Category> queryByType(Integer type) {
return categoryMapper.queryByType(type);
}
3.CategoryMapper
List<Category> queryByType(Integer type);
<select id="queryByType" resultType="Category">
select * from sky_take_out.category
where status = 1
<if test="type != null">
and type = #{type}
</if>
order by sort asc,create_time desc
</select>
5.公共字段自动填充
1.自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
(1)新建annotation包用于存储自定义注解
(2)在annotation包中新建注解类AutoFill
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT->自定义的枚举类型
OperationType value();
}
类上方添加的注解为固定格式
Target中的ElementType.METHOD表示应用在方法之上
2.自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
(3)新建aspect包用于存储切面类
(4)在aspect包中新建切面类AutoFillAspect
/**
* 自定义切面类,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点 需要同时满足前后两个
* 前->mapper包中的 所有类,所有方法
* 后->annotation中的AutoFill注解
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
@Before("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];
//准备赋值的数据
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 setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setUpdateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
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();
}
}
}
}
3.在Mapper的方法上加入AutoFill注解
在Mapper文件中使用update和insert的方法上添加@AutoFill注解
//自定义字段填充,自定义的注解,值等于枚举值INSERT
@AutoFill(value = OperationType.INSERT)
@AutoFill(value = OperationType.UPDATE)
6.阿里云OSS配置
1.自定义配置属性类AliOssProperties
//配置属性类,添加ConfigurationProperties注解
//可以自动读取配置文件中的内容并存储,需要参数名相同(可以类中使用驼峰命名,yml中使用-命名)
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
添加@Component将创建对象放到容器中
添加ConfigurationProperties注解可以自动读取配置文件中的内容并存储,需要参数名相同(可以类中使用驼峰命名,yml中使用-命名)
添加@Data自动生成set()get()方法
2.在application.yml文件中添加配置属性
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: LTAI5t8mnTi4ZtfaH24bJPqY
access-key-secret: 3v0EUA7LZHmFShPOqwGjcIHxzdLv5R
bucket-name: dioker
3.添加配置类OssConfiguration
/**
* 配置类:用于创建AliOssUtil对象
*/
@Configuration
@Slf4j
public class OssConfiguration {
//添加Bean注解后,让对象在程序启动时被自动创建
@Bean
@ConditionalOnMissingBean
public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
return new AliOssUtil(aliOssProperties.getEndpoint()
, aliOssProperties.getAccessKeyId()
, aliOssProperties.getAccessKeySecret()
, aliOssProperties.getBucketName());
}
}
4.导入工具类AliOssUtil
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.使用
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件名需要与前端请求保持一致->必须叫file
* @param file
* @return
*/
@PostMapping("/upload")
@ApiOperation("上传文件")
public Result<String> upload(MultipartFile file){
try {
//获取原始文件名
String originalFilename = file.getOriginalFilename();
String objectName = "";
if (originalFilename != null) {
//截取原始文件名后缀
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
//构造新的文件名
objectName = UUID.randomUUID().toString()+extension;
}
//文件请求路径
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
7.菜品管理相关接口
1.新增菜品
1.DishController
接收用户请求->调用Service处理业务->将处理结果返回给前端
(1)Controller类上添加注解
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品管理相关接口")
(2)创建DishService对象并添加@Autowired注解自动注入
(3)创建save方法,并添加注解
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
dishService.saveDishWithFlavor(dishDTO);
return Result.success();
}
2.DishService
处理业务
(1)在DishServiceImpl类上添加@Service注解
(2)创建DishMapper和DishFlavorMapper对象,添加@Autowired注解自动注入
(3)添加saveDishWithFlavor方法
@Transactional
@Override
public void saveDishWithFlavor(DishDTO dishDTO) {
//向菜品表插入数据(1条)
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
dish.setStatus(StatusConstant.DISABLE);
//需要主键回显,从数据库返回新加数据自增的主键值,用于向flavors数据库中插入数据
dishMapper.insert(dish);
//获取insert语句生成的主键值
Long dishId = dish.getId();
//向口味表插入数据(0~n条数据)
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
dishFlavorMapper.insert(flavors);
}
}
1.本方法需要在向菜品表中插入菜品后,再向dish_flavor表中插入数据 ->
对两个表进行数据操作,应使用@Transaction注解
@Transaction注解
数据库事务: 当一个方法中需要对多个数据库表进行更新操作时,应该使用 @Transactional 注解来确保所有的操作都能够成功提交或回滚,从而保证数据的一致性和完整性。
业务逻辑: 当用户下单时,需要同时更新用户的账户信息和订单信息。使用 @Transactional 注解可以确保这些操作在同一个事务中执行。
缓存更新: 在更新缓存时,需要确保缓存和数据库的一致性。使用 @Transactional 注解可以确保在更新数据库之前,缓存已经被更新或者被清空。
消息队列: 在使用消息队列处理业务时,需要确保消息被正确处理或者回滚。使用 @Transactional 注解可以保证在处理消息时,消息和数据库的操作是在同一个事务中执行的。
需要在Application类上先使用@EnableTransactionManagement开启注解方式的事务管理
3.Mapper
2.insert语句从数据库返回值
向dish_flavor表插入数据时需要使用dish新生成的id值作为逻辑外键 ->
由于id值为数据库自增,无法从前端得到,可以在向数据库插入数据时返回参数->
->必须使用xml文件,在select操作处添加参数 useGeneratedKeys="true" keyProperty="id"
useGeneratedKeys =true 表示插入数据之后返回主键值
keyproperty = "" 指明数据库中返回的主键值赋值给实体类中的哪个属性(赋值给传递过来的实体类),之后可以在Service中直接使用get()方法调用
切记:不是方法返回值,而是赋值给对象参数
@AutoFill(OperationType.INSERT)
void insert(Dish dish);
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into sky_take_out.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>
3.insert语句插入多条数据
向dish_flavor表插入的数据为0~n条(前段的DTO传入后端的flavors是一个List集合)
Service向Mapper传递的数据也为List集合 ->
DishFlavorMapper中操作数据库的方法必须使用动态SQL,使用foreach遍历集合
<!--
批量插入数据 传入的是集合对象,使用动态SQL foreach遍历
collection值需要与传过来的参数名一致
item为循环中的自定义变量值
separator表示语句之间用逗号分隔
-->
<insert id="insert">
insert into sky_take_out.dish_flavor
(dish_id, name, value) VALUES
<foreach collection="dishFlavors" item="flavor" separator=",">
(#{flavor.dishId},#{flavor.name},#{flavor.value})
</foreach>
</insert>
2.菜品分页查询
1.DishController
创建pageQuery方法,并添加注解,指定URL
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO){
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);
}
2.DishService
添加pageQuery方法
@Override
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
Page<DishVO> dishes = dishMapper.pageQuery(dishPageQueryDTO);
return new PageResult(dishes.getTotal(),dishes.getResult());
}
3.DishMapper
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
<select id="pageQuery" resultType="com.sky.vo.DishVO">
<!--
必须起别名,不然框架对应不上属性,因为是根据属性名对应的,如果不起别名会返回name
因为数据库中的表项与DishVO中的属性值不同,设置别名让返回的值与DishVO中的属性值相同
-->
SELECT sky_take_out.dish.*,sky_take_out.category.name as categoryName
FROM sky_take_out.dish, sky_take_out.category
<where>
dish.category_id=category.id
<if test="name!=null and name!=''">
and dish.name like concat('%',#{name},'%')
</if>
<if test="categoryId!=null">
and dish.category_id = #{categoryId}
</if>
<if test="status!=null">
and dish.status = #{status}
</if>
</where>
order by dish.create_time desc
</select>
3.菜品批量删除
1.DishController
创建delete方法,并添加注解,指定URL
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result<String> delete(@RequestParam List<Long> ids){
dishService.delete(ids);
return Result.success();
}
2.DishService
(1)创建SetMealMapper对象,添加@Autowired注解自动注入
(2)添加delete方法
@Override
public void delete(List<Long> ids) {
List<Dish> dishes = dishMapper.queryById(ids);
dishes.forEach(dish -> {
//菜品状态,如果为启动状态->抛异常,不能删除
if (dish.getStatus() == StatusConstant.ENABLE) {
throw new DeletionNotAllowedException(dish.getName() + ":" + MessageConstant.DISH_ON_SALE);
}
//判断菜品是否在套餐中,如果在套餐中->抛异常,不能删除
List<Long> setMealIds = setMealMapper.getSetMealIdsByDishId(dish.getId());
if (setMealIds != null && setMealIds.size() > 0) {
throw new DeletionNotAllowedException(dish.getName() + ":" + MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
});
//删除菜品表中的菜品数据
dishMapper.delete(ids);
//删除菜品时同时删除 菜品口味
dishFlavorMapper.deleteByDishIds(ids);
}
3.在DishMapper中添加方法,传入菜品ids的集合,返回菜品类的集合
List<Dish> queryById(List<Long> ids);
<!--查询所有ids中的数据-->
<select id="queryById" resultType="com.sky.entity.Dish">
select * from sky_take_out.dish where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
4.在SetMealMapper中添加方法,返回套餐ID的集合
//根据菜品Id中获取套餐Id
List<Long> getSetMealIdsByDishId(Long dishId);
在SetMealMapper.xml中添加select
<!--根据菜品Id中获取套餐Id-->
<select id="getSetMealIdsByDishId" resultType="java.lang.Long">
select setmeal_id from sky_take_out.setmeal_dish where dish_id = #{dishId}
</select>
5.在DishMapper中添加方法,删除ids中所有数据
void delete(List<Long> ids);
<!--删除菜品-->
<delete id="delete">
delete from sky_take_out.dish where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
6.在DishMapper中添加方法,删除ids中所有数据
void deleteByDishIds(List<Long> ids);
<!--删除DishId存在ids中的数据-->
<delete id="deleteByDishIds">
delete from sky_take_out.dish_flavor where dish_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
3.根据ID查询菜品
1.DishController
创建queryById方法,并添加注解,指定URL
@GetMapping("/{id}")
@ApiOperation("根据ID查询菜品")
public Result<DishVO> queryById(@PathVariable Long id){
DishVO dishVO = dishService.queryById(id);
return Result.success(dishVO);
}
2.DishService
添加queryById方法
@Transactional
@Override
public DishVO queryById(Long id) {
//从菜品表中获得菜品数据,并拷贝数据给VO类
List<Long> ids = new ArrayList<>();
ids.add(id);
List<Dish> dishes = dishMapper.queryById(ids);
Dish dish = dishes.get(0);
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish,dishVO);
//从菜品口味表中获得菜品对应的口味数据,并拷贝数据给VO类
List<DishFlavor> dishFlavors = dishFlavorMapper.queryById(dish.getId());
dishVO.setFlavors(dishFlavors);
BeanUtils.copyProperties(dish,dishFlavors);
return dishVO;
}
返回的是前端页面展示需要的类DishVO,包含菜品存储在数据库Dish中的基本信息及存储在DishCategory中的口味信息
在DishCategory中的口味信息需要使用逻辑外键dish_id进行返回,返回结果为List集合
3.DishMapper
List<Dish> queryById(List<Long> ids);
<!--查询所有ids中的数据-->
<select id="queryById" resultType="com.sky.entity.Dish">
select * from sky_take_out.dish where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
4.DishFlavourMapper
@Select("select * from sky_take_out.dish_flavor where dish_id = #{id}")
List<DishFlavor> queryById(Long id);
4.修改菜品及菜品口味
1.DishController
创建modify方法,并添加注解,指定URL
@PutMapping
@ApiOperation("修改菜品及菜品口味")
public Result<String> modify(@RequestBody DishDTO dishDTO){
dishService.modify(dishDTO);
return Result.success();
}
2.DishService
添加modify方法
@Transactional
@Override
public void modify(DishDTO dishDTO) {
//从DTO中获取Dish的数据修改
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish);
dishMapper.update(dish);
//删除原来的Dish_Flavour数据
List<Long> ids = new ArrayList<>();
ids.add(dishDTO.getId());
dishFlavorMapper.deleteByDishIds(ids);
//新增新的Dish_Flavour口味
List<DishFlavor> dishFlavors = dishDTO.getFlavors();
dishFlavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishDTO.getId());
});
dishFlavorMapper.insert(dishFlavors);
}
要想修改Dish_Flavour中的数据无法进行定位修改,若要修改菜品关联的口味数据,只能根据菜品ID将原来关联的菜品ID都删除,然后重新插入新的菜品口味数据
3.DishMapper
@AutoFill(OperationType.UPDATE)
void update(Dish dish);
<!--修改菜品-->
<update id="update">
update sky_take_out.dish set
<if test="name!=null and name!=''">name=#{name},</if>
<if test="image!=null and image!=''">image=#{image},</if>
<if test="price!=null">price=#{price},</if>
<if test="status!=null">status=#{status},</if>
<if test="categoryId!=null">category_id=#{categoryId},</if>
<if test="description!=null and description!=''">description=#{description},</if>
update_user = #{updateUser},
update_time = #{updateTime}
where id = #{id}
</update>
4.DishFlavourMapper
void deleteByDishIds(List<Long> ids);
<!--删除DishId存在ids中的数据-->
<delete id="deleteByDishIds">
delete from sky_take_out.dish_flavor where dish_id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
void insert(List<DishFlavor> dishFlavors);
<!--
批量插入数据 传入的是集合对象,使用动态SQL foreach遍历
collection值需要与传过来的参数名一致
item为循环中的自定义变量值
separator表示语句之间用逗号分隔
-->
<insert id="insert">
insert into sky_take_out.dish_flavor
(dish_id, name, value) VALUES
<foreach collection="dishFlavors" item="flavor" separator=",">
(#{flavor.dishId},#{flavor.name},#{flavor.value})
</foreach>
</insert>
5.起售或停售菜品
1.DishController
创建startOrStop方法,并添加注解,指定URL
@PostMapping("/status/{status}")
@ApiOperation("启售或停售菜品")
public Result startOrStop(@PathVariable Integer status,Long id){
dishService.startOrStop(status,id);
return Result.success();
}
2.DishService
添加startOrStop方法
@Override
public void startOrStop(Integer status, Long id) {
Dish dish = new Dish();
dish.setStatus(status);
dish.setId(id);
dishMapper.update(dish);
}
update方法已写,直接调用即可
6.根据分类ID查询菜品
1.DishController
创建queryByCategoryId方法,并添加注解,指定URL
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<Dish>> queryByCategoryId(Long categoryId){
List<Dish> dishes = dishService.list(categoryId);
return Result.success(dishes);
}
2.DishService
添加queryByCategoryId方法
@Override
public List<Dish> list(Long categoryId) {
return dishMapper.queryByCategoryId(categoryId);
}
3.DishMapper
@Select("select * from sky_take_out.dish where category_id=#{categoryId}")
List<Dish> queryByCategoryId(Long categoryId);
8.套餐管理相关接口
1.新增套餐及菜品的关联关系
1.SetmealController
接收用户请求->调用Service处理业务->将处理结果返回给前端
(1)Controller类上添加注解
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
(2)创建SetmealService对象并添加@Autowired注解自动注入
(3)创建save方法,并添加注解
@PostMapping
@ApiOperation("新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO) {
setmealService.saveWithDish(setmealDTO);
return Result.success();
}
2.SetmealService
处理业务
(1)在SetmealServiceImpl类上添加@Service注解
(2)创建SetmealMapper和SetmealDishMapper对象,添加@Autowired注解自动注入
(3)添加save方法
@Transactional
@Override
public void saveWithDish(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);
//向套餐表中插入数据,获取生成的套餐ID
setmealMapper.insert(setmeal);
//将获得套餐ID值赋值给连接类
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmeal.getId());
log.info(setmealDish.toString());
});
//保存套餐和菜品的关联关系
setmealDishMapper.insert(setmealDishes);
}
除了向Setmeal表中添加套餐数据外,还需要向setmeal_dish表中添加套餐和菜品的关联关系
其中DTO中包含Setmeal的数据和SetmealDish的数据
新建与Setmeal表关联的Setmeal类和与SetmealDish表关联的SetmealDish类
给类赋值后分别使用对应的Mapper向表中插入数据
3.SetmealMapper
与数据库交互
@AutoFill(OperationType.INSERT)
void insert(Setmeal setmeal);
<!--向套餐表中插入数据,获取生成的套餐ID-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into sky_take_out.setmeal
(category_id, name, price, description, image, create_time, update_time, create_user, update_user)
VALUES (#{categoryId}, #{name}, #{price}, #{description}, #{image}, #{createTime}, #{updateTime}, #{createUser},
#{updateUser})
</insert>
4.SetmealDishMapper
void insert(List<SetmealDish> setmealDishes);
<!--
保存套餐和菜品的关联关系(多条)
foreach标签循环处只写了collection项(集合)少写了item项(循环项)
且使用#{}指定属性时需要用item项指定(setmealDish.SetmealId)
-->
<insert id="insert">
insert into sky_take_out.setmeal_dish
(setmeal_id, dish_id, name, price, copies)
VALUES
<foreach collection="setmealDishes" item="setmealDish" separator=",">
(#{setmealDish.setmealId},#{setmealDish.dishId},#{setmealDish.name},#{setmealDish.price},#{setmealDish.copies})
</foreach>
</insert>
2.套餐分页查询
1.SetmealController
创建pageQuery方法,并添加注解,指定URL
@GetMapping("/page")
@ApiOperation("套餐分页查询")
public Result<PageResult> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO){
PageResult result = setmealService.pageQuery(setmealPageQueryDTO);
return Result.success(result);
}
2.SetmealService
添加pageQuery方法
@Override
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
PageHelper.startPage(setmealPageQueryDTO.getPage(),setmealPageQueryDTO.getPageSize());
Page<SetmealVO> setmealVOs = setmealMapper.pageQuery(setmealPageQueryDTO);
return new PageResult(setmealVOs.getTotal(),setmealVOs.getResult());
}
3.SetmealMapper
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
<!--
套餐分页查询
设置返回参数别名,返回的参数名与resultType指定的类对应
concat用,连接而不是用+连接
-->
<select id="pageQuery" resultType="com.sky.vo.SetmealVO">
select setmeal.*,category.name as categoryName from sky_take_out.setmeal,sky_take_out.category
where
setmeal.category_id=category.id
<if test="name!=null and name!=''">and setmeal.name like concat('%',#{name},'%')</if>
<if test="categoryId!=null">and setmeal.category_id = #{categoryId}</if>
<if test="status!=null">and setmeal.status = #{status}</if>
order by setmeal.create_time desc
</select>
3.批量删除套餐
1.SetmealController
创建delete方法,并添加注解,指定URL
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids){
setmealService.deleteBatch(ids);
return Result.success();
}
2.SetmealService
添加delete方法
@Transactional
public void deleteBatch(List<Long> ids) {
ids.forEach(id -> {
Setmeal setmeal = setmealMapper.queryById(id);
if(StatusConstant.ENABLE == setmeal.getStatus()){
//起售中的套餐不能删除
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
});
//删除套餐表中的数据
setmealMapper.deleteById(ids);
//删除套餐菜品关系表中的数据
setmealDishMapper.deleteBySetmealId(ids);
}
在删除前先判断套餐的状态,若为起售中则抛出异常,提示不能删除
若判断都能删除后,则删除套餐中的数据,同时删除套餐关系表中的数据
3.SetmealMapper
void deleteById(List<Long> ids);
<!--批量删除套餐-->
<delete id="deleteById">
delete from sky_take_out.setmeal
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
4.SetmealDishMapper
void deleteBySetmealId(List<Long> ids);
<!--批量删除套餐和菜品的关联关系-->
<delete id="deleteBySetmealId">
delete from sky_take_out.setmeal_dish
where setmeal_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
4.根据ID查询套餐
1.SetmealController
创建queryById方法,并添加注解,指定URL
@GetMapping("/{id}")
@ApiOperation("根据ID查询套餐")
public Result<SetmealVO> queryById(@PathVariable Long id){
SetmealVO setmealVO = setmealService.queryById(id);
return Result.success(setmealVO);
}
2.SetmealService
添加queryById方法
@Transactional
@Override
public SetmealVO queryById(Long id) {
SetmealVO setmealVO = new SetmealVO();
Setmeal setmeal = setmealMapper.queryById(id);
BeanUtils.copyProperties(setmeal,setmealVO);
List<SetmealDish> setmealDishes = setmealDishMapper.queryBySetmealId(id);
setmealVO.setSetmealDishes(setmealDishes);
return setmealVO;
}
返回传递给前端的SetmealVO类
先在setmeal表中查询套餐数据
再在setmeal_dish表中查询关联数据
3.SetmealMapper
@Select("select * from sky_take_out.setmeal where id=#{id}")
Setmeal queryById(Long id);
4.SetmealDishMapper
@Select("select * from sky_take_out.setmeal_dish where setmeal_id = #{setmealId}")
List<SetmealDish> queryBySetmealId(Long setmealId);
5.修改套餐数据
1.SetmealController
创建modify方法,并添加注解,指定URL
@PutMapping
@ApiOperation("修改套餐数据")
public Result modify(@RequestBody SetmealDTO setmealDTO){
setmealService.modify(setmealDTO);
return Result.success();
}
2.SetmealService
添加modify方法
@Transactional
@Override
public void modify(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);
//修改套餐数据
setmealMapper.update(setmeal);
//更新不了,删了
List<Long> ids = new ArrayList<>();
ids.add(setmeal.getId());
setmealDishMapper.deleteBySetmealId(ids);
//重新添加新的 套餐和菜品的关联数据
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmeal.getId());
});
setmealDishMapper.insert(setmealDishes);
}
先修改套餐数据
数据库Setmeal_Dish表中的数据无法进行定位修改,若要修改套餐和菜品关联数据,只能根据套餐ID将原来关联的菜品ID都删除,然后重新插入新的数据
3.SetmealMapper
@AutoFill(OperationType.UPDATE)
void update(Setmeal setmeal);
<update id="update">
update sky_take_out.setmeal
<set>
<if test="categoryId!=null">category_id=#{categoryId},</if>
<if test="name!=null and name!=''">name=#{name},</if>
<if test="price!=null">price=#{price},</if>
<if test="status!=null">status=#{status},</if>
<if test="description!=null and description!=''">description=#{description},</if>
<if test="image!=null and image!=''">image=#{image},</if>
<if test="updateTime!=null">update_time=#{updateTime},</if>
<if test="updateUser!=null">update_user=#{updateUser},</if>
</set>
where id = #{id}
</update>
4.SetmealDishMapper
void deleteBySetmealId(List<Long> ids);
<!--批量删除套餐和菜品的关联关系-->
<delete id="deleteBySetmealId">
delete from sky_take_out.setmeal_dish
where setmeal_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
6.起售停售套餐
1.SetmealController
创建startOrStop方法,并添加注解,指定URL
@PostMapping("/status/{status}")
public Result startOrStop(@PathVariable Integer status,Long id){
setmealService.startOrStop(status,id);
return Result.success();
}
2.SetmealService
添加startOrStop方法
@Override
public void startOrStop(Integer status, Long id) {
//停售
if (status==StatusConstant.DISABLE){
Setmeal setmeal = new Setmeal();
setmeal.setId(id);
setmeal.setStatus(status);
setmealMapper.update(setmeal);
return;
}else {
//起售
//查setmeal_dish表->根据setmeal_id查询,要得到所有dish_id
List<SetmealDish> setmealDishes = setmealDishMapper.queryBySetmealId(id);
List<Long> dishIds = new ArrayList<>();
setmealDishes.forEach(setmealDish -> {
dishIds.add(setmealDish.getDishId());
});
//查dish表->根据id查询,要得到所有status
List<Dish> dishes = dishMapper.queryById(dishIds);
//若status中包含0->抛异常
dishes.forEach(dish -> {
if (dish.getStatus()==StatusConstant.DISABLE){
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
}
});
}
//更改
Setmeal setmeal = new Setmeal();
setmeal.setId(id);
setmeal.setStatus(status);
setmealMapper.update(setmeal);
}
套餐停售可以直接操作,
起售需要进行判断套餐中是否有停售中的菜品,若包含停售中的菜品,则不能起售
不包含停售的菜品时才可以起售
方法都已经包含,直接调用即可
9.Redis
1.数据类型
1.String(字符串)
2.List(列表)->类似于LinkedList
3.Hash(哈希表)->类似于HashMap
4.Set(集合)->类似于HashSet
5.SortedSet(有序集合)->根据分数升序排序,没有重复元素
2.数据结构
10.店铺管理相关接口
1.店铺端
1.ShopController
(1)Controller类上添加注解
//自定义spring创建类时的名称,否则会默认以类名命名
//有同名的类时会提示无法创建报错
@Slf4j
@RestController("adminShopController")
@Api(tags = "店铺相关接口")
@RequestMapping("/admin/shop")
自定义spring创建类时的名称,否则会默认以类名命名,有同名的类时会提示无法创建报错
(2)创建RedisTemplate对象并添加@Autowired注解自动注入
需要引入Redis的依赖->阿里云OSS依赖已经包含Redis依赖,不需要再次引入可以直接创建RedisTemplate对象
1.设置店铺营业状态
创建setStatus方法,并添加注解
直接调用redisTemplate提供的方法,向Redis中添加数据
@PutMapping("/{status}")
@ApiOperation("设置店铺营业状态")
public Result setStatus(@PathVariable Integer status){
//输出日志,输出店铺的营业状态
log.info("设置店铺的营业状态为: {}",status==1?"营业中":"打烊中");
//向Redis中添加数据,key值为静态常量,状态从url中获取
redisTemplate.opsForValue().set(KEY,status);
return Result.success();
}
2.获取店铺营业状态
创建setStatus方法,并添加注解
直接调用redisTemplate提供的方法,从Redis中获取数据
@GetMapping("/status")
@ApiOperation("获取店铺营业状态")
public Result<Integer> getStatus(){
//根据key值获取的value值,返回的是object对象,需要强制转换
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
//输出日志,输出店铺的营业状态
log.info("设置店铺的营业状态为: {}",status==1?"营业中":"打烊中");
return Result.success(status);
}
2.用户端
1.获取店铺营业状态
1.ShopController
(1)Controller类上添加注解
//自定义spring创建类时的名称,否则会默认以类名命名
//有同名的类时会提示无法创建报错
@Slf4j
@RestController("userShopController")
@Api(tags = "店铺相关接口")
@RequestMapping("/user/shop")
自定义spring创建类时的名称,否则会默认以类名命名,有同名的类时会提示无法创建报错
(2)创建RedisTemplate对象并添加@Autowired注解自动注入->可以直接创建RedisTemplate对象
(3)创建getStatus方法,并添加注解
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/status")
@ApiOperation("获取店铺营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("设置店铺的营业状态为: {}",status==1?"营业中":"打烊中");
return Result.success(status);
}
将店铺的营业状态存储到Redis数据库中后, 可以直接调用RedisTemplate,从Redis中获取到存储的数据->即店铺营业状态