这个项目是基于springboot+mybatis-plus开发的,很适合新手入门的第一个小项目
主要是一些crud的操作,目的是让你了解开发的过程
在这之前我先是学了javaSE、javaWeb、Mysql、maven、git、SSM、springboot和mybatisplus
也了解了一点前端的h5、css、js、vue和elementui
我是全程跟着黑马学习,感兴趣的朋友也可以去看看黑马讲这个项目的视频,很清晰;
黑马程序员Java项目实战《瑞吉外卖》
项目后台和小程序部分功能已经做完
已全部上传,感兴趣的朋友们可以看看,也可以来问我要数据库文件
第一次做项目,做的不是很好,但是功能都能实现,可以跑通,仅供大家参考
github:https://github.com/yourTdd/REGGIE
gitee:https://gitee.com/liao-tao020415/reggie
在IDEA中创建一个springboot项目,以下是我的pom文件
yaml文件中主要是端口和数据库的设置,就不放出来了,感兴趣的朋友可以问我要.sql文件
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.5version>
<relativePath/>
parent>
<groupId>com.ltgroupId>
<artifactId>reggieartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.platformgroupId>
<artifactId>junit-platform-launcherartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<scope>compilescope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.20version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.23version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-coreartifactId>
<version>4.5.16version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-dysmsapiartifactId>
<version>1.1.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.4.5version>
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("员工信息修改成功!");
}
==> 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());
实体类
@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("修改分类信息成功成功");
}
<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("保存成功!");
}
后台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;
}
后台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;
}
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("修改失败");
}
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;
}