瑞吉外卖项目
这个项目是基于springboot+mybatis-plus开发的,很适合新手入门的第一个小项目
主要是一些crud的操作,目的是让你了解开发的过程
在这之前我先是学了javaSE、javaWeb、Mysql、maven、git、SSM、springboot和mybatisplus
也了解了一点前端的h5、css、js、vue和elementui
我是全程跟着黑马学习,感兴趣的朋友也可以去看看黑马讲这个项目的视频,很清晰;
项目后台和小程序部分功能已经做完
已全部上传,感兴趣的朋友们可以看看,也可以来问我要数据库文件
第一次做项目,做的不是很好,但是功能都能实现,可以跑通,仅供大家参考
github:https://github.com/yourTdd/REGGIE
gitee:https://gitee.com/liao-tao020415/reggie
后台业务开发
项目构建
-
在IDEA中创建一个springboot项目,以下是我的pom文件
-
yaml文件中主要是端口和数据库的设置,就不放出来了,感兴趣的朋友可以问我要.sql文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> </parent> <groupId>com.lt</groupId> <artifactId>reggie</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.23</version> </dependency> <!--阿里云短信服务--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.16</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <version>1.1.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.4.5</version> </plugin> </plugins> </build> </project>
数据库设计
该项目一共有11张表,对应着11个实体类
登陆/退出功能
后台系统登录功能
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
// 1、将页面提交的密码进行md5加密处理
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
// 2、根据页面提交的用户名查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
// 3、如果没有查询到就返回登录失败结构
if (emp == null){
return R.error("登录失败!");
}
// 4、密码比对
if (!emp.getPassword().equals(password)){
return R.error("登录失败!");
}
// 5、查看员工状态,是否禁用
if (emp.getStatus() == 0){
return R.error("账号已禁用!");
}
// 6、登录成功 将员工id存入session并返回登录结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
后台系统退出功能
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功!");
}
后台系统首页构成和效果展示方式
右侧会随左侧menu选中不同而进行页面的切换
完善登录功能
问题:如果用户不登陆,直接访问系统首页面也可以正常访问
- 只有登录成功之后才可以访问,没有登录则跳转到登录页面
-
创建自定义过滤器
/** * 检查用户是否已经完成登录 */ @WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*") public class LoginCheckFilter implements Filter { // 路径匹配器,支持通配符 public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; // 1、获取本次请求的uri String requestURI = request.getRequestURI(); // 2、不处理的请求 String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**" }; // 3、是否需要处理 boolean check = check(urls, requestURI); // 4、如果不需要处理,直接放行 if (check) { filterChain.doFilter(request, response); return; } // 5、判断登录状态,如果已登录,直接放行 if (request.getSession().getAttribute("employee") != null) { filterChain.doFilter(request, response); return; } // 6、如果未登录则返回未登录结果,通过输出流的方式向客户端页面响应数据 response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); return; } /** * 路径匹配,检查本次请求是否需要放行 * * @param urls * @param requestURI * @return */ public boolean check(String[] urls, String requestURI) { for (String url : urls) { boolean match = PATH_MATCHER.match(url, requestURI); if (match) { return true; } } return false; } }
员工管理业务
基本结构
- 员工模型
-
实体类Emloyee
@Data public class Employee implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String username; private String name; private String password; private String phone; private String sex; private String idNumber; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; private Long createUser; private Long updateUser; }
-
Mapper接口
@Mapper public interface EmployeeMapper extends BaseMapper<Employee> { }
-
业务层接口及实现类
public interface EmployeeService extends IService<Employee> { }
@Service public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService { }
-
EmloyeeController
@Slf4j @RestController @RequestMapping("/employee") public class EmployeeController{ @Autowired private EmployeeService employeeService; }
新增员工
对username添加了唯一约束
在controller中添加新增方法
/**
* 新增员工
* @param employee
* @return
*/
@PostMapping
public R<String> add(@RequestBody Employee employee,HttpServletRequest request){
log.info("新增员工,员工信息:"+employee);
// 设置初始密码123456,进行md5加密
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
// 获得当前登录用户的id
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
employeeService.save(employee);
return R.success("新增员工成功");
}
全局异常处理
为解决username唯一约束,不能新增重复的员工
新建一个统一异常捕获
/**
* 全局异常捕获
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException e){
log.error(e.getMessage());
if (e.getMessage().contains("Duplicate entry")){
String[] split = e.getMessage().split(" ");
String msg = split[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误");
}
}
员工信息分页查询
-
配置mybaatis-plus的分页插件
/** * 配置MP的分页插件 */ @Configuration public class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } }
-
编写分页查询代码
/** * 员工信息分页查询 * @param page * @param pageSize * @param name * @return */ @GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ log.info("page={},pageSize={},name={}",page,pageSize,name); // 构造分页构造器 Page pageInfo = new Page(page,pageSize); // 构造条件构造器 LambdaQueryWrapper<Employee> qw = new LambdaQueryWrapper(); // 添加过滤条件 qw.like(StringUtils.isNotEmpty(name),Employee::getName,name); // 添加排序条件 qw.orderByDesc(Employee::getUpdateTime); // 执行查询 employeeService.page(pageInfo,qw); return R.success(pageInfo); } }
启用/禁用员工账号
员工状态为禁用的不能登录系统,只有管理员可以对其他普通用户进行启用、禁用操作
普通用户登陆系统后,启用/禁用按钮不显示
</el-button>
<el-button
type="text"
size="small"
class="delBut non"
@click="statusHandle(scope.row)"
v-if="user === 'admin'"
>
{{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>
修改员工信息,可以修改状态,也适用于编辑员工信息
/**
* 根据id修改员工信息
* @param employee
* @return
*/
@PutMapping
public R<String> update(@RequestBody Employee employee, HttpServletRequest request){
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(empId);
employeeService.updateById(employee);
return R.success("员工信息修改成功!");
}
丢失精度问题
-
分页查询时id
-
禁用请求时id
==> Preparing: UPDATE employee SET status=?, update_time=?, update_user=? WHERE id=?
==> Parameters: 0(Integer), 2022-07-16T09:04:56.277(LocalDateTime), 1(Long), 1548105591044567000(Long)
<== Updates: 0
JS只能精确16位,JS对Long型数字进行了处理,导致请求的id与数据库不一致
-
在服务端给页面响应json数据时进行处理,将long型转换位String类型
添加对象转换器JacksonObjectMapper, 添加一系列的序列化器,其中也包含了Long->String
public class JacksonObjectMapper extends ObjectMapper { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; public JacksonObjectMapper() { super(); //收到未知属性时不报异常 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); //反序列化时,属性不存在的兼容处理 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleModule simpleModule = new SimpleModule() .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))) .addSerializer(BigInteger.class, ToStringSerializer.instance) // 将Long型转换为String .addSerializer(Long.class, ToStringSerializer.instance) .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); //注册功能模块 例如,可以添加自定义序列化器和反序列化器 this.registerModule(simpleModule); } }
-
在WebMvcConfig中扩展SpringMVC的消息转换器,使用对象转换器进行object->json转换
/** * 扩展mvc框架的消息转换器 * @param converters */ @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 创建消息转换器对象 MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); // 设置对象转换器 messageConverter.setObjectMapper(new JacksonObjectMapper()); // 将消息转换器对象追加到mvc框架的转换器集合中 converters.add(0,messageConverter);// 放到 0 位置优先使用 }
-
禁用成功
编辑员工信息
-
根据id查询员工信息将其反显到编辑页面
/** * 根据id查询员工信息 * @param id * @return */ @GetMapping("/{id}") public R<Employee> getById(@PathVariable Long id){ Employee employee = employeeService.getById(id); if (employee!=null){ return R.success(employee); } return R.error("没有查询到该员工信息"); }
-
再通过修改方法进行修改,与上面修改员工状态一致
/** * 根据id修改员工信息 * @param employee * @return */ @PutMapping public R<String> update(@RequestBody Employee employee, HttpServletRequest request){ Long empId = (Long) request.getSession().getAttribute("employee"); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(empId); employeeService.updateById(employee); return R.success("员工信息修改成功!"); }
分类管理业务
公共字段自动填充
-
在实体类emloyee的公共字段上增加@TableField注解
@TableField(fill = FieldFill.INSERT) // 插入时填充 private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时填充 private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser;
-
创建自定义元数据对象处理器实现MetaObjectHandler接口
@Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { /** * 插入操作,自动填充 * @param metaObject */ @Override public void insertFill(MetaObject metaObject) { log.info("公共字段自动填充[insert]"); metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("createUser", new Long(1)); metaObject.setValue("updateUser", new Long(1)); } /** * 更新操作,自动填充 * @param metaObject */ @Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充[update]"); Long id = Thread.currentThread().getId(); log.info("线程id为: {}",id); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("updateUser", new Long(1)); } }
功能完善
-
上述公共字段自动填充时,不能获取到当前登录用户的id,创建人和修改人都模拟了一个id
又因为,Filter、Controller中的update方法、处理器中的updateFill方法都处于一个线程
-
ThreadLocal是Thread的局部变量
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果
只有在线程内才能获取到对应的值,线程外则不能访问
-
编写工具类封装ThreadLocal,在filter中调用其设置当前用户id,在handler中来获取(这两者处于同一线程)
-
编写工具类BaseContext
/** * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id */ public class BaseContext { private static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void setCurrentId(Long id){ threadLocal.set(id); } public static Long getCurrentId(){ return threadLocal.get(); } }
-
在LoginCheckFilter中设置id
Long empId = (Long) request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId);
-
在MyMetaObjectHandler中获取id
之前此处只填入了new Long(1),将其替换为BaseContext.getCurrentId()
metaObject.setValue("createUser", BaseContext.getCurrentId()); metaObject.setValue("updateUser", BaseContext.getCurrentId());
-
新增分类
- 分类模型
- name字段唯一约束
-
实体类
@Data public class Category implements Serializable { private static final long serialVersionUID = 1L; private Long id; //类型 1 菜品分类 2 套餐分类 private Integer type; //分类名称 private String name; //顺序 private Integer sort; //创建时间 @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; //更新时间 @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; //创建人 @TableField(fill = FieldFill.INSERT) private Long createUser; //修改人 @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; }
-
CategoryMapper接口
@Mapper public interface CategoryMapper extends BaseMapper<Category> { }
-
业务层接口及实现类
public interface CategoryService extends IService<Category> { }
@Service public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService{ }
-
CategoryController
/** * 分类管理 */ @RestController @RequestMapping("/category") @Slf4j public class CategoryController { @Autowired private CategoryService categoryService; @PostMapping public R<String> add(@RequestBody Category category){ log.info("category:{}",category); categoryService.save(category); return R.success("新增分类成功!"); } }
分类信息分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
// 分页构造器
Page<Category> pageInfo = new Page<>(page,pageSize);
// 条件构造器
LambdaQueryWrapper<Category> qw = new LambdaQueryWrapper();
// 添加排序条件
qw.orderByAsc(Category::getSort);
// 进行分页查询
categoryService.page(pageInfo,qw);
return R.success(pageInfo);
}
删除分类
分类关联了菜品和套餐,不能随意删除,需要判断
-
在CategoryService中编写remove方法,并在其实现类中作出判断
@Autowired private DishService dishService; @Autowired private SetmealService setmealService; /** * 根据id删除分类,删除之前需要判断 * @param id */ @Override public void remove(Long id) { LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>(); // 添加查询条件 dishLambdaQueryWrapper.eq(Dish::getCategoryId,id); int count1 = dishService.count(dishLambdaQueryWrapper); // 查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常 if (count1 > 0){ // 已经关联菜品,抛出一个业务异常 throw new CustomException("当前分类下关联了菜品,不能删除"); } // 查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常 LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>(); setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id); int count2 = setmealService.count(); if (count2 > 0){ // 已经关联套餐,抛出一个业务异常 throw new CustomException("当前分类下关联了套餐,不能删除"); } // 正常删除分类 super.removeById(id); }
-
创建自定义异常,并用全局异常处理器捕获异常
/** * 自定义业务异常类 */ public class CustomException extends RuntimeException{ public CustomException(String message){ super(message); } }
-
在GlobalExceptionHandler中添加处理方法
/** * 异常处理方法 * @return */ @ExceptionHandler(CustomException.class) public R<String> exceptionHandler(CustomException e){ log.error(e.getMessage()); return R.error(e.getMessage()); }
-
修改分类
/**
* 根据id修改分类
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("修改分类信息:{}",category);
categoryService.updateById(category);
return R.success("修改分类信息成功成功");
}
菜品管理业务
文件上传下载
- 添加upload页面,负责文件上传
<el-upload class="avatar-uploader"
action="/common/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeUpload"
ref="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
-
编写文件上传的控制器
@RestController @RequestMapping("/common") public class CommonController { @Value("${reggie.path}") private String basePath; /** * 文件上传 * @param file * @return */ @PostMapping("/upload") public R<String> upload(MultipartFile file) { // file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会被删除 // 原始文件名 String originalFilename = file.getOriginalFilename(); // 切最后一个点 得到后缀 String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); // 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖 String fileName = UUID.randomUUID().toString() + suffix; // 创建一个目录对象 File dir = new File(basePath); // 判断当前目录是否存在 if (!dir.exists()){ // 目录不存在,需要创建 dir.mkdirs(); } try { // 将临时文件转存到指定位置 file.transferTo(new File(basePath + fileName)); } catch (IOException e) { e.printStackTrace(); } return R.success(fileName); } }
-
文件下载
/** * 文件下载 * @param name * @param response */ @GetMapping("/download") public void download(String name, HttpServletResponse response){ // 输入流,读取文件内容 try { FileInputStream fileInputStream = new FileInputStream(new File(basePath + name)); // 输出流,写回浏览器 ServletOutputStream outputStream = response.getOutputStream(); // 设置文件类型 response.setContentType("image/jpeg"); int len = 0; byte[] bytes = new byte[1024]; while ((len = fileInputStream.read(bytes)) != -1){ outputStream.write(bytes,0,len); outputStream.flush(); } outputStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } }
新增菜品
数据模型
- 菜品
-
菜品口味
-
创建实体类Dish和DishFlavor
@Data public class Dish implements Serializable { private static final long serialVersionUID = 1L; private Long id; //菜品名称 private String name; //菜品分类id private Long categoryId; //菜品价格 private BigDecimal price; //商品码 private String code; //图片 private String image; //描述信息 private String description; //0 停售 1 起售 private Integer status; //顺序 private Integer sort; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; //是否删除 private Integer isDeleted; }
@Data public class DishFlavor implements Serializable { private static final long serialVersionUID = 1L; private Long id; //菜品id private Long dishId; //口味名称 private String name; //口味数据list private String value; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; //是否删除 private Integer isDeleted; }
新增菜品界面
-
发送查询菜品分类请求
-
保存菜品的同时保存对应的口味数据
/** * 根据条件查询分类数据 * @param category * @return */ @GetMapping("/list") public R<List<Category>> type(Category category){ LambdaQueryWrapper<Category> qw = new LambdaQueryWrapper(); qw.eq(category.getType()!=null,Category::getType,category.getType()); qw.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(qw); return R.success(list); }
-
-
创建DishDTO封装菜品口味及其他数据
@Data public class DishDto extends Dish { private List<DishFlavor> flavors = new ArrayList<>(); private String categoryName; private Integer copies; }
-
在controller中编写新增代码
/** * 新增菜品 * @param dishDto * @return */ @PostMapping public R<String> save(@RequestBody DishDto dishDto){ log.info(dishDto.toString()); dishService.save(dishDto); return R.success("添加菜品成功!"); }
分页查询
分页查询页面中需要显示菜品分类名称,而dish表中只有分类id的字段
则需要用到dishDTO来封装分类名称
page方法中传入的name一起作用于按照名字查询
/**
* 菜品信息分页查询
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
Page<Dish> pageInfo = new Page<>(page, pageSize);
Page<DishDto> dishDtoPage = new Page<>();
LambdaQueryWrapper<Dish> lqw = new LambdaQueryWrapper();
lqw.like(name!=null,Dish::getName,name);
lqw.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
dishService.page(pageInfo,lqw);
// 对象拷贝
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((i) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(i, dishDto);
Long categoryId = i.getCategoryId();// 分类id
// 根据id查询分类对象
Category category = categoryService.getById(categoryId);
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
删除/批量删除
/**
* 删除和批量删除
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete(@RequestParam(value = "ids")List<Long> ids){
dishService.removeByIds(ids);
return R.success("删除菜品成功!");
}
停售/启售
/**
* 批量 启售/停售
* @param stauts
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> status(@PathVariable("status") String stauts,@RequestParam List<Long> ids){
LambdaUpdateWrapper<Dish> uw = new LambdaUpdateWrapper();
uw.in(Dish::getId,ids).set(Dish::getStatus,stauts.equals("0")?"0":"1");
dishService.update(uw);
return R.success("操作成功!");
}
修改菜品
-
先要查询菜品分类,在新增菜品中已经写过可以用同一个
@GetMapping("/list") public R<List<Category>> type(Category category){ LambdaQueryWrapper<Category> qw = new LambdaQueryWrapper(); qw.eq(category.getType()!=null,Category::getType,category.getType()); qw.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(qw); return R.success(list); }
-
根据id查询菜品信息和对应口味信息返显到页面
查询菜品信息和对应口味信息要借助DishDTO,其中封装了二者
在Service中编写方法
/** * 根据id来查询菜品的信息和对应的口味信息 * @param id * @return */ @Override public DishDto getByIdWithFlavor(Long id) { // 菜品基本信息 Dish dish = this.getById(id); DishDto dishDto = new DishDto(); BeanUtils.copyProperties(dish,dishDto); // 对应的口味,从dish_flavor中查 LambdaQueryWrapper<DishFlavor> qw = new LambdaQueryWrapper<>(); qw.eq(DishFlavor::getDishId,dish.getId()); List<DishFlavor> flavors = dishFlavorService.list(qw); dishDto.setFlavors(flavors); return dishDto; }
最后在controller中响应
/** * 根据id来查询菜品信息和口味信息 * @param id * @return */ @GetMapping("/{id}") public R<DishDto> getById(@PathVariable Long id){ DishDto dishDto = dishService.getByIdWithFlavor(id); return R.success(dishDto); }
-
提交
-
Service
/** * 更新菜品信息,同时更新对应的口味信息 * @param dishDto */ @Transactional public void updateWithFlavor(DishDto dishDto) { // 更新dish表基本信息 this.updateById(dishDto); // 清理当前菜品对应的口味数据 dish_flavor表的delete操作 LambdaQueryWrapper<DishFlavor> qw = new LambdaQueryWrapper<>(); qw.eq(DishFlavor::getDishId,dishDto.getId()); dishFlavorService.remove(qw); // 添加当前提交过来的口味数据 dish_flavor表的insert操作 List<DishFlavor> flavors = dishDto.getFlavors(); flavors = flavors.stream().map((i)->{ i.setDishId(dishDto.getId()); return i; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
-
Controller
/** * 修改菜品 * @param dishDto * @return */ @PutMapping public R<String> update(@RequestBody DishDto dishDto){ dishService.updateWithFlavor(dishDto); return R.success("保存成功!"); }
-
套餐管理业务
套餐信息分页查询
- 页面向后台发送ajax请求
-
后台controller接收请求给出响应(既可以接收分页,也可以接收条件查询name)
/** * 分页查询和条件查询 * @param page * @param pageSize * @param name * @return */ @GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ Page<SetmealDto> setmealDtoPage = setmealService.page(page, pageSize, name); return R.success(setmealDtoPage); }
-
业务逻辑处理在Service中
因为套餐信息和对应套餐分类名称categoryName不在一张表中
创建SetmealDto封装信息
@Data public class SetmealDto extends Setmeal { private List<SetmealDish> setmealDishes; private String categoryName; }
/** * 分页查询套餐信息及其对应套餐分类名称 * @param page * @param pageSize * @param name * @return */ @Override public Page<SetmealDto> page(int page,int pageSize,String name) { Page<Setmeal> pageinfo = new Page<>(); Page<SetmealDto> setmealDtoPage = new Page<>(); LambdaQueryWrapper<Setmeal> qw = new LambdaQueryWrapper<>(); qw.eq(name!=null,Setmeal::getName,name); qw.orderByDesc(Setmeal::getUpdateTime); this.page(pageinfo,qw); BeanUtils.copyProperties(pageinfo,setmealDtoPage,"records"); List<Setmeal> records = pageinfo.getRecords(); List<SetmealDto> list = records.stream().map((i) -> { SetmealDto setmealDto = new SetmealDto(); BeanUtils.copyProperties(i, setmealDto); Long categoryId = i.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null) { setmealDto.setCategoryName(category.getName()); } return setmealDto; }).collect(Collectors.toList()); setmealDtoPage.setRecords(list); return setmealDtoPage; }
新增套餐
- 页面向后台发送ajax请求
-
后台controller接收请求并给出响应
-
/category/list
/** * 根据条件查询分类数据 * @param category * @return */ @GetMapping("/list") public R<List<Category>> type(Category category){ LambdaQueryWrapper<Category> qw = new LambdaQueryWrapper(); qw.eq(category.getType()!=null,Category::getType,category.getType()); qw.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(qw); return R.success(list); }
-
/dish/list
/** * 根据分类id查询菜品 * @param dish * @return */ @GetMapping("/list") public R<List<Dish>> list(Dish dish){ List<Dish> dishes = dishService.get(dish); return R.success(dishes); }
-
-
根据id查询菜品Service
/** * 根据分类id查询菜品 * @param dish * @return */ @GetMapping("/list") public R<List<Dish>> list(Dish dish){ List<Dish> dishes = dishService.get(dish); return R.success(dishes); }
-
保存
- 后台发送请求
-
controller给出响应
/** * 新增套餐,同时需要保存套餐和菜品的关联关系 * @param setmealDto * @return */ @PostMapping public R<String> save(@RequestBody SetmealDto setmealDto){ boolean flag = setmealService.saveWithDish(setmealDto); return flag?R.success("新增成功!"):R.error("新增失败!"); }
-
Service实现业务
/** * 新增套餐,同时需要保存套餐和菜品的关联关系 * @param setmealDto * @return */ @Override @Transactional public boolean saveWithDish(SetmealDto setmealDto) { // 保存套餐的基本信息 boolean flag1 = this.save(setmealDto); // 保存套餐和菜品的关联信息 List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); List<SetmealDish> dishes = setmealDishes.stream().map((i) -> { i.setSetmealId(setmealDto.getId()); return i; }).collect(Collectors.toList()); boolean flag2 = setmealDishService.saveBatch(dishes); return flag1&&flag2; }
修改套餐
- 页面给后台发送ajax请求获取该套餐信息
-
controller接收请求响应信息返回
/** * 查询详情 * @param id * @return */ @GetMapping("/{id}") public R<SetmealDto> getById(@PathVariable Long id){ SetmealDto setmealDto = setmealService.getWithDishById(id); return R.success(setmealDto); }
-
service处理业务
/** * 查询详情 * @param id * @return */ @Override public SetmealDto getWithDishById(Long id) { SetmealDto setmealDto = new SetmealDto(); Setmeal setmeal = this.getById(id); BeanUtils.copyProperties(setmeal,setmealDto); LambdaQueryWrapper<SetmealDish> qw = new LambdaQueryWrapper<>(); qw.eq(id!=null,SetmealDish::getSetmealId,id); List<SetmealDish> list = setmealDishService.list(qw); setmealDto.setSetmealDishes(list); return setmealDto; }
-
页面发送保存请求
-
在service中处理业务,在controller接收并响应
-
service
/** * 修改套餐信息,并保存到关联信息 * @return */ @Override @Transactional public boolean updateWithDish(SetmealDto setmealDto) { // 修改套餐信息 boolean flag1 = this.updateById(setmealDto); // 修改关联信息 LambdaQueryWrapper<SetmealDish> qw = new LambdaQueryWrapper<>(); qw.eq(SetmealDish::getSetmealId,setmealDto.getId()); setmealDishService.remove(qw); // 把套餐里的菜品和套餐的id关联 List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); setmealDishes.stream().map((i)->{ i.setSetmealId(setmealDto.getId()); return i; }).collect(Collectors.toList()); boolean flag2 = setmealDishService.saveBatch(setmealDishes); return flag1&&flag2; }
-
controller
/** * 保存修改 * @param setmealDto * @return */ @PutMapping public R<String> update(@RequestBody SetmealDto setmealDto){ boolean flag = setmealService.updateWithDish(setmealDto); return flag?R.success("修改成功!"):R.error("修改失败"); }
-
删除套餐
- 页面给后台发送ajax请求
-
controller接收请求并给出响应
/** * 删除套餐的同时删除对应的关联信息 * @param ids * @return */ @DeleteMapping public R<String> delete(@RequestParam List ids){ boolean flag = setmealService.deleteWithDish(ids); return flag?R.success("删除成功!"):R.error("删除失败"); }
-
Service处理业务
/** * 删除套餐的同时删除对应的关联信息 * @param ids * @return */ @Override @Transactional public boolean deleteWithDish(List<Long> ids) { boolean flag1 = this.removeByIds(ids); LambdaQueryWrapper<SetmealDish> qw = new LambdaQueryWrapper<>(); qw.in(SetmealDish::getSetmealId,ids); boolean flag2 = setmealDishService.remove(qw); return flag1&&flag2; }
启售/停售
-
页面向后台发送ajax请求
-
停售
-
启售
-
-
controller接收请求并给出响应
/** * 更改状态 * @param status * @param ids * @return */ @PostMapping("/status/{status}") public R<String> status(@PathVariable String status, @RequestParam List ids){ boolean b = setmealService.updateStatus(status, ids); return b?R.success("更新状态成功!"):R.error("更新失败!"); }
-
Service处理业务
/** * 更新状态 * @param status * @param ids */ @Override public boolean updateStatus(String status, List ids) { LambdaUpdateWrapper<Setmeal> uw = new LambdaUpdateWrapper(); uw.in(Setmeal::getId,ids).set(Setmeal::getStatus,status.equals("0")?"0":"1"); boolean flag = this.update(uw); return flag; }
最终效果
-
员工管理页面
-
分类管理页面
-
菜品管理页面
-
套餐管理页面
-
订单明细页面
-
小程序页面