【SpringBoot3+Mybatis】小程序和后台管理系统 员工/分类/菜品/套餐管理 上传文件 CRUD总结 LocalThread 事务管理 原型分析接口设计

11 篇文章 0 订阅
10 篇文章 0 订阅


一、项目介绍&Github

线上点单小程序和后台管理系统 后端,Github项目地址:springboot-order-out
1
1. 管理端

模块描述
登录/退出内部员工必须登录后,才可以访问系统管理后台
员工管理管理员可以在系统后台对员工信息进行管理,包含查询、新增、编辑、禁用等功能
分类管理主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护, 包含查询、新增、修改、删除等功能
菜品管理主要维护各个分类下的菜品信息,包含查询、新增、修改、删除、启售、停售等功能
套餐管理主要维护当前餐厅中的套餐信息,包含查询、新增、修改、删除、启售、停售等功能
订单管理主要维护用户在移动端下的订单信息,包含查询、取消、派送、完成,以及订单报表下载等功能
数据统计主要完成对餐厅的各类数据统计,如营业额、用户数量、订单等

2. 用户端

模块描述
登录/退出用户需要通过微信授权后登录使用小程序进行点餐
点餐-菜单在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择
点餐-购物车用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能
订单支付用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付
个人信息在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据

二、技术选型

1

1). 用户层

本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序。

2). 网关层

Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。

3). 应用层

SpringBoot: 快速构建Spring项目, 采用 “约定优于配置” 的思想, 简化Spring项目的配置开发。

SpringMVC:SpringMVC是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,可以无缝集成。

Spring Task: 由Spring提供的定时任务框架。

httpclient: 主要实现了对http请求的发送。

Spring Cache: 由Spring提供的数据缓存框架

JWT: 用于对应用程序上的用户进行身份验证的标记。

阿里云OSS: 对象存储服务,在项目中主要存储文件,如图片等。

Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。

POI: 封装了对Excel表格的常用操作。

WebSocket: 一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。

4). 数据层

MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。

Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。

Mybatis: 本项目持久层将会使用Mybatis开发。

pagehelper: 分页插件。

spring data redis: 简化java代码操作Redis的API。

5). 工具

git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。

maven: 项目构建工具。

junit:单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。

postman: 接口测工具,模拟用户发起的各类HTTP请求,获取对应的响应结果。

三、开发环境搭建

后端模块:

序号名称说明
1sky-take-outmaven父工程,统一管理依赖版本,聚合其他子模块
2sky-common子模块,存放公共类,例如:工具类、常量类、异常类等
3sky-pojo子模块,存放实体类、VO、DTO等
4sky-server子模块,后端服务,存放配置文件、Controller、Service、Mapper等
  • sky-common: 模块中存放的是一些公共类,可以供其他模块使用
名称说明
constant存放相关常量类
context存放上下文类
enumeration项目的枚举类存储
exception存放自定义异常类
json处理json转换的类
properties存放SpringBoot相关的配置属性类
result返回结果类的封装
utils常用工具类
  • sky-pojo: 模块中存放的是一些 entity、DTO、VO
名称说明
Entity实体,通常和数据库中的表对应
DTO数据传输对象,通常用于程序中各层之间传递数据
VO视图对象,为前端展示数据提供的对象
POJO普通Java对象,只有属性和对应的getter和setter
  • sky-server: 模块中存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等
名称说明
config存放配置类
controller存放controller类
interceptor存放拦截器类
mapper存放mapper接口
service存放service类
SkyApplication启动类
  • 数据库:
序号表名中文名
1employee员工表
2category分类表
3dish菜品表
4dish_flavor菜品口味表
5setmeal套餐表
6setmeal_dish套餐菜品关系表
7user用户表
8address_book地址表
9shopping_cart购物车表
10orders订单表
11order_detail订单明细表

项目接口文档

四、员工管理

4.1 新增员工

原型:
1
接口设计:
1

① sql

sql 不要写错,插入字段和values属性一一对应,否则无法插入数据。

    /**
     * 插入员工数据
     * @param employee
     * @return
     */
    @Insert("insert into employee(name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status)" +
            " VALUES" +
            "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status}) ")
    void insert(Employee employee);

yml文件中设置驼峰命名所以插入字段中不需要起别名
1

② 对象拷贝 DTO 与 Entity

DTO是根据请求参数建立的实体类,在与原实体有较大差别时候,另外建立一个实体,用来接收请求参数

使用DTO接收前端请求数据,之后 再用Entity实体类封装,使用对象拷贝,剩下的数据手动SET设置。
1

③ 异常捕获与处理

当录入的用户名已存在,抛出了异常,但是没有处理

1
1

全局异常处理 GlobalExceptionHandler
添加对报错异常的处理方法:
查看是否包含特定异常信息,再截取报错消息,返回给前端显示。

    /**
     * 重复数据 Duplicate entry 异常
     *
     * @param ex SQLIntegrityConstraintViolationException
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
        log.error("异常信息:{}", ex.getMessage());
        //Duplicate entry 'dougwake' for key 'employee.idx_username'
        String message = ex.getMessage();
        if (message.contains("Duplicate entry")) {
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username + MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        }
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }

捕获异常,并且处理:
1

④ 动态获取当前登录者Id

新增员工时,创建人id和修改人id设置为固定值
如何动态获取当前登录员工的Id?

  • 新增员工时,需要确定操作者的ID:(当前登录Id)
    1
  • jwt,生成使用流程:1
  • 用户登录,生成token,把用户信息(Id)也存放进去,然后响应给前端
    1
  • 除登录请求外,其他请求都要进入拦截器。
  • 所以,在拦截器解析token中拿到这次登录的用户Id存储,由service层获取Id,赋值给属性:
    1
  • 如何存储,以及怎样传递给service层的save方法使用?
    • 通过ThreadLocal进行传递。
    • 项目使用工具类BaseContext
    • 1
BaseContext.setCurrentId(empId); //存入id
Long currentId = BaseContext.getCurrentId();//获取Id

1

⑤ ThreadLocal

介绍:

  • ThreadLocal 并不是一个Thread,而是Thread的局部变量。
  • ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
    • 举例:点击一次新增用户的功能,即发送一次请求,这就是一个线程。
    • 再点击一次,就是再次发送一次请求,又是一个线程。
    • 每次发送请求中每个线程都是独立的,都有独立的存储空间。

常用方法:

  • public void set(T value) 设置当前线程的线程局部变量的值
  • public T get() 返回当前线程所对应的线程局部变量的值
  • public void remove() 移除当前线程的线程局部变量

1
注意:客户端发送的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求

测试:

//测试执行一次请求中,当前线程ID
System.out.println(Thread.currentThread().getId());

两次请求的线程Id是不同的:
1

4.2 员工分页查询

原型:
1
接口:
1

① 请求参数实体 与 响应数据实体

EmployeePageQueryDTO 实体用来接收请求参数:
1

PageResult 实体用于返回数据:
1

② controller层

  • get请求方式 不需要 @RequestBody
  • 返回的对象类型为:Result<PageResult>
    1

③ service层 使用pageHelper分页

  • pageHelper 底层是拦截器
  • 拼接limit
    /**
     * 员工分页查询
     * @param employeePageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
        Page<Employee> employeePage = employeeMapper.pageQuery(employeePageQueryDTO);

        long total = employeePage.getTotal();//获取总数
        List<Employee> records = employeePage.getResult();//获取员工数据

        return new PageResult(total,records);
    }

④ 模糊查询sql 使用 concat()

模糊查询

    <select id="pageQuery" resultType="com.sky.entity.Employee">
        select * from employee
        <where>
            <if test="name != null and name!=''">
                and name like concat('%',#{name},'%')
            </if>
        </where>
        order by create_time desc
    </select>

④ 时间格式问题

1
解决方式一:

  • 在属性上加上注解,对日期进行格式化

  • 1

  • 但这种方式,需要在每个时间属性上都要加上该注解,使用较麻烦,不能全局处理。

    //@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

解决方式二:

  • 在springmvc配置类WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理
	/**
     * 扩展Spring MVC框架的消息转化器
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器...");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转化器加入容器中
        //0表示优先使用自己的消息转化器
        converters.add(0,converter);
    }

时间格式定义,sky-common模块中的JacksonObjectMapper():对象映射器,工具类,固定写法
1

结果:
1

4.3 启用、禁用员工账号

原型:
1
业务规则:

  • 可以对状态为“启用” 的员工账号进行“禁用”操作
  • 可以对状态为“禁用”的员工账号进行“启用”操作
  • 状态为“禁用”的员工账号不能登录系统

接口:
1

① Result 工具类使用

  • 查询的情况 : 加上泛型,实体类
  • 非查询:不需要加泛型

controller,非查询,更新操作,所以不用加泛型实体

    /**
     * 启用、禁用员工账号
     * @return
     */
    @ApiOperation("启用、禁用员工账号")
    @PostMapping("status/{status}")
    public Result startOrStop(@PathVariable Integer status,Long id){
        log.info("启用、禁用员工账号:{},{}",status,id);
        employeeService.startOrStop(status,id);
        return Result.success();
    }

② 创建对象的不同方式 build

方式一:原始
正常创建对象:

        // 原始方法构建对象
        Employee employee = new Employee();
        employee.setStatus(status);
        employee.setId(id);

方式二:build

        //使用build的方法构建对象
        Employee employee = Employee.builder()
                .id(id)
                .status(status)
                .build();

需要在被构建的类上 加@build注解
1

③ 更新操作

通过创建实体对象,set里面的值,再传递实体对象:

    /**
     * 启用、禁用员工账号
     *
     * @param status 状态
     * @param id     根据ID修改状态
     */
    @Override
    public void startOrStop(Integer status, Long id) {
        //update emp set status=? where id=?
        //可以写成动态sql语句,根据Id修改不同的属性字段
        
        //使用build的方法构建对象
        Employee employee = Employee.builder()
                .id(id)
                .status(status)
                .build();

        employeeMapper.update(employee);
    }

④ 动态SQL

来自不同的方法 (修改不同值) 调用,但是使用同一个sql语句。

    /**
     * 通用sql
     * 根据Id修改员工信息
     * @param employee
     */
    void update(Employee employee);

更新操作 update,通用sql

    <update id="update" parameterType="employee">
        update employee
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="username != null">username = #{username},</if>
            <if test="password != null">password = #{password},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="idNumber != null">id_Number = #{idNumber},</if>
            <if test="updateTime != null">update_Time = #{updateTime},</if>
            <if test="updateUser != null">update_User = #{updateUser},</if>
            <if test="status != null">status = #{status},</if>
        </set>
        where
        id = #{id}
    </update>

4.4 编辑员工

编辑员工信息(根据Id查询员工实现回显数据,根据回显数据修改员工信息)

原型:
1
接口:
编辑员工功能涉及到两个接口:

① 编辑员工需要设置修改人与时间

    /**
     * 编辑员工信息
     * @param employeeDTO
     */
    @Override
    public void update(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);
        //设置修改人和修改时间
        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }

使用之前定义的update通用动态sql,根据Id修改员工信息

② 编辑员工请求的参数

可以用EmployeeDTO接收,再对象拷贝

五、分类管理

原型:
1
业务规则:

  • 分类名称必须是唯一的
  • 分类按照类型可以分为菜品分类和套餐分类
  • 新添加的分类状态默认为“禁用”

接口:
菜品分类模块共涉及6个接口。

  • 新增分类
  • 分类分页查询
  • 根据id删除分类
  • 修改分类
  • 启用禁用分类
  • 根据类型查询分类

数据库表: ( category 表)
1

5.1 新增分类

接口根据页面原型:
请求参数包括: post
1
新建菜品分类/新建套餐分类,1菜品分类 2套餐分类,前端根据点的按钮不同,会自动返回1/2
1

5.2 分类分页查询

Get 请求: /admin/category/page
1

条件:
1
1

5.3 启用、禁用分类

  • Path: /admin/category/status/{status}
  • Method: POST

update动态sql

    <update id="update">
        update category
        <set>
            <if test="type != null">
                type = #{type},
            </if>
            <if test="name != null">
                name = #{name},
            </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.4 根据类型查询分类

  • Path: /admin/category/list
  • Method: GET
    <select id="list" resultType="com.sky.entity.Category">
        select * from category
        where status = 1
        <if test="type != null">
            and type = #{type}
        </if>
        order by sort asc,create_time desc
    </select>

用于其他模块使用,根据类型查询旗下分类品项

5.5 修改分类

put 请求 @RequestBody,使用动态sql update

5.6 根据id删除分类

  • 删除分类前,需要判断分类下是否有菜品和套餐,有就报异常不能删除。
  • 涉及到其他表的查询
    /**
     * 根据id删除分类
     *
     * @param id
     */
    @Override
    public void deleteById(Long id) {
        Integer count = dishMapper.countByCategoryId(id);

        if (count != null && count > 0) {
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
        }

        count=setmealMapper.countByCategoryId(id);
        if (count != null && count > 0){
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
        }

        categoryMapper.deleteById(id);
    }

六、公共字段自动填充

问题:两个类都出现要修改或新增时,设置时间/人的属性(公共字段)
1
使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

6.1 实现步骤:

  1. 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

    • 自定义注解用于指定方法,区别于execution用于指定某包下的类
    • 只有在某包下的类,并且用注解指定的方法,执行切面方法
  2. 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

  3. 在 Mapper 的方法上加入 @AutoFill 注解

6.2 代码实现:

自定义注解:

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

切面类:

  1. 获取对象方法签名(定义)
  2. 拿到方法上的注解
  3. 拿到方法参数 (实体类)
  4. 根据注解参数定义的不同操作,对方法参数进行 反射赋值
package com.sky.aspect;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {
    }

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        //可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型

        //方法签名对象(方法的定义 -> void xxx(xx xx);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        System.out.println(signature);//void com.sky.mapper.EmployeeMapper.update(Employee)

        //获得方法上的注解对象 @AutoFill(value=xxx)
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        System.out.println(autoFill);//@com.sky.annotation.AutoFill(UPDATE)

        //拿到注解@AutoFill(value=xxx) 里面的value数值
        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 {
                //为4个公共字段赋值
                Method setCreateTime = entity.getClass()
                        .getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass()
                        .getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass()
                        .getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass()
                        .getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射为对象属性赋值
                setCreateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        } 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 (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Mapper的update/insert方法都加上注解 @AutoFill ,注释service方法中之前设置的公共字段赋值代码。

同时,将业务层为公共字段赋值的代码注释掉。
1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。
2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。

七、菜品管理

7.1 新增菜品

7.1.1 原型分析&&接口设计&&数据库表

1
1

7.1.2 上传文件

1
controller:

/**
 * 通用接口
 */
@RestController
@Slf4j
@Api(tags = "通过接口")
@RequestMapping("/admin/common/") //注意复制过来不要有空格
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件上传
     * @param file
     * @return
     * @throws IOException
     */
    @ApiOperation("文件上传")
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传:{}",file);
        try {
            // UUID设置上传文件名
            String originalFilename = file.getOriginalFilename();
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            String fileName = UUID.randomUUID()+extension;

            String filePath = aliOssUtil.upload(file.getBytes(),fileName);
            return Result.success(filePath);
        } catch (IOException e) {
            log.error("文件上传失败:{}",e);
        }
        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

上传文件的接口测试只能用 Postman 或是 前后端联调,swagger不能用。

7.1.3 新增菜品

1

  • 涉及到多表增删改操作 service层要加上@Transactional 开启事务管理
  • @AutoFill注解不要乱加,注意看实体类中是否需要加这个注解
  • 1
  • service:菜品表 与 口味表 一对多关系
    1
  • dishmapper.xml设置 :useGeneratedKeys 用于返回主键数据
    1
  • 批量插入 使用<foreach collection=
<mapper namespace="com.sky.mapper.DishFlavorMapper">
    <insert id="insertBatch">
        insert into dish_flavor(dish_id, name, value)
        values
            <foreach collection="flavors" item="df" separator=",">
                (#{df.dishId},#{df.name},#{df.value})
            </foreach>
    </insert>
</mapper>

7.2 菜品分页查询

7.2.1 原型

1

7.2.1 接口设计

1
1

7.2.2 代码实现

dishmapper.xml :

  • 可以先在MySQL图形化工具里面,先写完sql运行,再复制到IDEA填充修改
  • <if test="categoryId!=null and categoryId!=''">and d.category_id=#{categoryId}
    • 中的 test=“xxx” 是表示属性名字,不要写成数据库的列名
    <select id="pageQuery" resultType="com.sky.entity.Dish">
        SELECT d.*,c.`name` AS categoryName FROM dish d LEFT JOIN category c ON d.category_id = c.id
        <where>
            <if test="name!=null and name!=''">and d.name like concat('%',#{name},'%')</if>
            <if test="status!=null and status!=''">and d.status=#{status}</if>
            <if test="categoryId!=null and categoryId!=''">and d.category_id=#{categoryId}</if>
        </where>
    </select>

dish 表中有 name
category 表中 也有 name
所以 category的name 起别名

7.3 删除菜品

原型:
1
数据库设计:
1
controller :
1
service,xml
1

7.3 修改菜品

1

八、套餐管理

8.1 需求

完成套餐管理模块所有业务功能,包括:

  • 新增套餐
  • 套餐分页查询
  • 删除套餐
  • 修改套餐
  • 起售停售套餐
    1

要求:

  1. 根据产品原型进行需求分析,分析出业务规则
  2. 设计接口
  3. 梳理表之间的关系(分类表、菜品表、套餐表、口味表、套餐菜品关系表)
  4. 根据接口设计进行代码实现
  5. 分别通过swagger接口文档和前后端联调进行功能测试

8.2 新增套餐

8.2.1 原型分析

1

8.2.2 接口设计

8.2.3 代码实现

九、CRUD 小结

  • 新增 post
  • 修改 post/put
  • 查询 get
  • 删除 delete

9.1 Create 新增

9.1.1 概述

1

9.1.2 单表/多表新增

  1. 单表新增
  2. 多表新增
  • Controller:
    1
  • Service:
    1

多表插入需要注意:另外一张表插入
1

  • Mapper:
    1

9.2 Read

  1. 查询 都是 GET请求方式
  2. 响应返回数据是 前端需要的实体对象

9.2.1 分页查询

接口分析设计:
1

  1. controller
    1
  2. service
    1

分页菜品使用:Page<DishVO> DishVO实体类 进行分页返回数据

  1. mapper
    1

9.2.2 根据Id查询XX

  1. 请求:GetMapper
  2. 请求路径:/{id}
  3. 路径参数@PathVariable
    1

9.2.3 根据类型查询XX

  1. 同一类型的进行罗列,使用List集合存储
  2. 请求:Get
  3. 请求路径:/list
  4. 简单参数 无需注解
    1

9.3 Update 更新/修改

9.3.1 根据Id修改XX

需求
点击启用禁用按钮完成修改

接口设计

  • 请求方式:post
  • 请求路径:admin/emp/XX/{XX}
  • XX使用路径参数注解@PathVariable
  • Id 为简单参数 不需要注解
    1
    使用动态SQL
    1

9.3.2 修改单表

接口设计:

  • 请求方式:put
  • 请求路径:admin/emp
  • 参数注解:接收JSON实体 用@RequestBody

前端修改页面传递,更改后的JSON对象,实体类封装传递后端数据库
1

9.3.3 修改多表

接口设计:

  • 请求方式:put
  • 请求路径:admin/emp
  • 参数注解:接收JSON实体 用@RequestBody

controller与单表修改一致,只是在service业务层 需要对另一张表进行更新
1

9.4 Delete 删除

9.4.1 单表删除

接口设计:

  • 请求方式:DELETE
  • 请求路径:admin/emp
  • 根据Id删除 Id为简单参数 无需注解

删除前要做判断,根据业务规则
1

9.4.2 多表删除

1
接口设计:

  • 请求方式:DELETE
  • 请求路径:admin/xxx
  • 参数:@RequestParam List<Long> ids
    1

9.5 注意点

  • 多表增删改 在service层开启事务管理@Transactional(rollbackFor = Exception.class)
  • 修改/更新某些数据,可以直接删除原数据,插入新数据的方式,只是对外显示更新操作

总结

下一期:

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道格维克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值