目录

一、前端环境搭建

二、后端环境搭建

1、导入后端代码框架

2、导入数据库

3、连接数据库

4、运行后端代码

5、nginx - 前端发送的请求如何请求到后端服务的?

(1)nginx反向代理的配置方式

(2)nginx负载均衡的配置方式

三、完善登录功能 - MD5加密

四、Swagger - 接口文档工具集

五、员工模块开发

1、新增员工模块 - POST接口

(1)代码开发

为什么要在Service层进行数据类型转换?

(2)功能测试

如何在接口文档加入jwt令牌?

(3)代码完善

【1】bug1 - 录入的用户名已经存在

【2】bug2 - 创建人id和修改人id为固定值

ThreadLocal 线程局部变量

获取当前登录员工id

2、员工分页查询模块 - GET接口

(1)代码开发

PageHelper是什么?

PageHelper是如何传递页码和页数的?

(2)功能测试

(3)代码完善

扩展Spring Mvc消息转换器进行日期格式化(java格式序列化为json格式)

3、启用禁用员工账号模块 - Post接口

(1)代码开发

(2)功能测试

4、根据id查询员工模块 - GET接口

(1)代码开发

(2)功能测试

5、编辑员工信息模块 - POST接口

(1)代码开发

六、分类管理模块开发(自主开发练习)

1、新增分类模块 - POST接口

(1)代码开发

2、分类分页查询模块 - GET接口

(1)代码开发

3、根据id删除分类模块 - DELETE接口

(1)代码开发

4、修改分类模块 - PUT接口

(1)代码开发

5、启用禁用分类模块 - POST接口

(1)代码开发

6、根据类型查询分类模块 - GET接口

(1)代码开发

七、三层架构模板

1、Controller层

2、Service层

(1)接口

(2)实现类

3、Mapper层

4、Mybatis文件


一、前端环境搭建

因为该项目主要学习后端开发,因此我们需要先配置前端环境

在黑马程序员公众号获取前端资料,将nginx文件复制到非中文路径的文件夹中,再双击打开

在网页网址栏输入localhost,回车自动跳转出外卖系统的前端页面,访问端口号为80

二、后端环境搭建

1、导入后端代码框架

2、导入数据库

在navicat打开sql文件,运行代码,生成数据库表

3、连接数据库

4、运行后端代码

注意更改java版本

运行后端代码,在网页登录,成功进入后台系统

5、nginx - 前端发送的请求如何请求到后端服务的?

nginx 反向代理:前端发送的动态请求由 nginx 转发到后端服务器

nginx 反向代理的好处:

  • 提高访问速度
  • 进行负载均衡(把大量的请求按照指定的方式均衡的分配给集群中的每台服务器)
  • 保证后端服务安全

(1)nginx反向代理的配置方式

配置在 nginx -> conf -> nginx.conf 文件中

(2)nginx负载均衡的配置方式

三、完善登录功能 - MD5加密

1、修改数据库中明文密码,改为MD5加密后的密文

2、修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对

这样在登录页面输入密码123456,密码由前端传入后端,在后端转化为123456对应的md5加密码,并与数据库中密码比对,成功登录

四、Swagger - 接口文档工具集

Swagger 是一套用于 设计、构建、文档化和使用 RESTful API 的开源工具集
它提供了一套标准化的方式,让开发者可以轻松地 定义 API 结构、生成交互式文档、测试 API 接口

1、导入依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>${knife4j}</version>
</dependency>

2、在配置类中导入knife4j相关配置,设置静态资源映射

    /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

3、访问测试

http://localhost:8080/doc.html

4、常用注解 - 方便注释

运行后,可以在Swagger文档中发现接口名称和属性都有了注释

五、员工模块开发

分为5个接口进行开发:

  • 新增员工
  • 员工分页查询
  • 启用禁用员工账号
  • 根据id查询员工信息
  • 编辑员工信息

1、新增员工模块 - POST接口

(1)代码开发

【1】controller层

@RequestBody 作用:将 HTTP 请求体中的 JSON/XML 数据,自动转换为一个 Java 对象。

    /**
     * 新增员工
     * @param employeeDTO
     * @return
     */
    @ApiOperation(value = "新增员工")
    @PostMapping()
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        log.info("新增员工:{}",employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }

【2】service层

注意service层分为接口及实现类

为什么要在Service层进行数据类型转换?

1)严守分层架构

  • Controller层:接收请求、处理响应,只关心如何与客户端交互,而不关心业务逻辑和数据库操作。因此,它接收的是DTO。
  • Service层:实现核心业务逻辑。它需要将接收到的数据(DTO)转换成符合数据库要求的格式(Entity)。
  • Mapper层的职责是与数据库交互,它只应该接收和返回Entity。
  • 如果不转换,直接使用DTO操作数据库,就相当于让业务层和持久层直接依赖了前端的数据结构,破坏了分层架构,导致层与层之间耦合严重。

2)安全性与数据完整性

  • 防止非法字段注入:前端传来的DTO可能包含一些你不希望写入数据库的字段。通过新建一个Entity并只拷贝你允许的字段,可以精确控制哪些数据能进入数据库。
  • Entity有DTO没有的字段:Entity通常有一些系统自动生成的字段,如主键id、创建时间create_time、更新时间update_time等。这些字段不应该由前端传入,而是由程序自动设置。如果你直接把DTO传给Mapper,这些必要字段会是null,导致数据库操作失败或数据不完整。
    /**
     * 新增员工
     * @param employeeDTO
     */    
    public void save(EmployeeDTO employeeDTO){

        // 类型转换,将DTO->Entity
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        //设置账号状态,默认状态1
        employee.setStatus(StatusConstant.ENABLE);

        //设置密码
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

        //设置当前记录的创建时间和修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());

        //当前记录的创建人id和修改人id
        // TODO 后期需要改为当前登录用户的id
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);

        employeeMapper.insert(employee);
    }

【3】mapper层

插入sql语法:insert into table_name (a1,a2,a3……)  value (#{a1},#{a2},#{a3},……)

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

(2)功能测试

【1】接口文档测试

首先打开接口文档,选择新增员工接口 - 调试,输入数据,点击发送

我们会看到响应码401,这是因为代码中含有jwt令牌拦截器,因此需要在请求中加入jwt令牌

利用在拦截器处添加断点进行debug,再次点击发送后,在代码debug界面按F8(跳到下一步)

我们发现token为null,再继续按F8就会发现跳转到返回报错码401

如何在接口文档加入jwt令牌?

① 首先在员工登录接口发送一个登录请求,可以获取到一个jwt令牌,将其复制

② 在文档管理 - 全局参数测试中添加token参数(注意参数名称要和拦截器中令牌名称保持一致)

③ 再次在新增员工接口进行调试,点击发送,并在service层加入断点,F8不断下一步,我们就能看到数据被成功添加

在数据库表user刷新,也能看到数据新增成功

【3】前后端联调

在前端进行新增员工操作,刷新数据库发现成功更新,说明这个功能OK啦!

(3)代码完善

【1】bug1 - 录入的用户名已经存在

如果出现身份证重复,我们需要进行异常处理

    /**
     * 处理SQL异常
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //Duplicate entry 'rongye' 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);
        }else {
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

再次测试,成功获取 " 用户名已存在 " 报错信息

【2】bug2 - 创建人id和修改人id为固定值

前端请求中会携带jwt令牌,jwt令牌中可以解析出当前登录员工id

解析出登录员工id后,如何将id传入service层的save方法?

ThreadLocal 线程局部变量

        ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。因为每一次请求就是一个线程,因此我们可以通过ThreadLocal方法获取当前登录员工id。


ThreadLocal常用方法:

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

在common代码文件夹下已经配置好ThreadLocal常用方法

获取当前登录员工id

首先在jwt 拦截器中获取从jwt令牌解析出的当前登录员工id,并设置为当前线程的局部变量(setCurrentId)

BaseContext.setCurrentId(empId);

接着在EmployeeService中获取当前登录员工id(setCurrentId)

再添加一条数据,发现创建人id和修改人id动态获取功能成功开发!

2、员工分页查询模块 - GET接口

(1)代码开发

【1】controller层

分页查询返回的对象类型为Result<PageResult>

Result类包含code、msg、data

PageResult类包括total、records

    @ApiOperation(value = "员工分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询,参数为:()",employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

【2】service层

PageHelper是什么?

PageHelper分页插件,实现分页查询sql语句快速拼接

  1. 设置分页参数:在执行数据库查询之前,调用 PageHelper.startPage(int pageNum 第几页, int pageSize 每页多少条数据) 方法。
  2. 拦截查询:紧接着执行正常的查询方法,PageHelper 会拦截这个查询。
  3. 自动改造SQL

           改造原SQL:在原SQL语句后面自动加上分页关键字(例如为 MySQL 加上 LIMIT)。

           执行计数SQL:自动执行一条 SELECT COUNT(1) FROM ... 的语句来查询总记录数。

  4. 封装结果:将分页查询到的当前页数据和总记录数等信息,封装到一个强大的 PageInfo 对象中返回。
    /**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());

        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
        
        // 因为从mapper层获取的是Page格式数据,而service层需要返回PageResult格式,因此需要处理一下
        long total = page.getTotal();
        List<Employee> records = page.getResult();
        
        return new PageResult(total,records);
    }
PageHelper是如何传递页码和页数的?

ThreadLocal 就像是一个全局公告板,但这个公告板是每个线程独享的,别人看不到。

  1. 调用 PageHelper.startPage(2, 10) 时,它就把页码 2 和页数 10 写到了当前线程专属的公告板上。

  2. 当 MyBatis 执行 SQL 时,PageHelper 这个“拦截员”会主动去查看当前线程的公告板上有没有分页信息。

  3. 如果发现有(即第2页,每页10条),它就立刻根据这个信息去改造SQL(比如为 MySQL 加上 LIMIT 10, 10)。

  4. 为了保证不会影响下一个请求,它会清理掉当前线程公告板上的信息

【3】mapper层

    /**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

【4】mybatis文件

这里我们用mybatis动态sql,因此我们需要先在settings - plugins中下载MybatisX插件,这样可以智能生成xml语句,更加便利

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

(2)功能测试

【1】接口文档测试

        如果出现401错误,这是因为我们在application.yml中设置了jwt令牌过期时间,此时正好令牌过期,因此我们可以重新调试员工登录接口获取新jwt令牌,再将接口文档中全局变量更新一下。

【2】前后端联调

刷新页面,发现分页查询功能成功实现!

(3)代码完善

扩展Spring Mvc消息转换器进行日期格式化(java格式序列化为json格式)

我们通过在 WebMvcConfiguration 中扩展Spring Mvc消息转换器进行日期格式化,该功能代码为模板代码

    /**
     * 扩展Spring Mvc框架的消息转换器
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转化器……");
        // 创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        // 需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为Json格式
        converter.setObjectMapper(new JacksonObjectMapper());
        // 将自己的消息转换器加入容器
        converters.add(0,converter);
    }

其中将Java格式序列化为Json格式使用到对象映射器,也是提前配置好的,简单了解

  •  对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
  • 将JSON解析为Java对象的过程称为 【从JSON反序列化Java对象】
  • 从Java对象生成JSON的过程称为 【序列化Java对象到JSON】
/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
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_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    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(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);
    }
}

重新启动后端代码,发现分页查询中日期成功格式化!

3、启用禁用员工账号模块 - Post接口

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

(1)代码开发

【1】controller层

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

【2】service层

为什么传入mapper层的参数是Employee类?

因为你需要根据id修改该用户的所有信息

而Employee实体类又有@Builder注解,可以更加便利地构造设置实体类

    /**
     * 启用禁用员工账号
     */
    public void startOrStop(Integer status,Long id){
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .build();
        // 请给我创建一个 Employee 对象,并把它的 id 设为这个值,status 设为那个值,其他的属性用默认的就好,创建好了之后交给 employee 变量

        employeeMapper.update(employee);

    }

【3】mapper层

    /**
     * 根据主键动态更新状态
     * @param employee
     */
    void update(Employee employee);

【4】mybatis文件

    <update id="update" parameterType="Employee">
        update sky_take_out.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>

(2)功能测试

【1】接口文档测试

【2】前后端联调

4、根据id查询员工模块 - GET接口

(1)代码开发

【1】controller层

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @ApiOperation(value = "根据id查询员工信息")
    @GetMapping("/{id}")
    public Result<Employee> getById(@PathVariable Long id) {
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }

【2】service层

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    public Employee getById(Long id){
        Employee employee = employeeMapper.getById(id);
        employee.setPassword("*****");
        return employee;
    }

【3】mapper层

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @Select("select * from sky_take_out.employee where id = #{id}")
    Employee getById(Long id);

(2)功能测试

若点击修改界面,可以显示该用户信息即为成功!

5、编辑员工信息模块 - POST接口

(1)代码开发

【1】controller层

    /**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    @ApiOperation(value = "编辑员工信息")
    @PutMapping
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        log.info("编辑员工信息:{}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }

【2】service层

    /**
     * 编辑员工信息
     * @param employeeDTO
     */
    public void update(EmployeeDTO employeeDTO){
        // 我们可以复用xml中的update方法,但xml中update数据类型为Employee,因此我们需要进行类型转换
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }

因为编辑员工信息即更新员工信息,可以复用之前在xml文件中开发的update方法,因此无需写mapper层

六、分类管理模块开发(自主开发练习)

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

分为6个接口进行开发:

  1. 新增分类
  2. 分类分页查询
  3. 根据id删除分类
  4. 修改分类
  5. 启用禁用分类 
  6. 根据类型查询分类

1、新增分类模块 - POST接口

(1)代码开发

【1】controller层

    /**
     * 新增分类
     * @param categoryDTO
     * @return
     */
    @ApiOperation(value = "新增分类")
    @PostMapping()
    public Result save(@RequestBody CategoryDTO categoryDTO){
        log.info("新增分类:{}",categoryDTO);
        categoryService.save(categoryDTO);
        return Result.success();
    }

【2】service层

    /**
     * 新增分类
     * @param categoryDTO
     */
    public void save(CategoryDTO categoryDTO){
        // 类型转换,将DTO->Entity
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO,category);

        //设置菜品状态,默认为1
        category.setStatus(StatusConstant.DISABLE);

        //设置创建及修改时间
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());

        //设置创建人和修改人
        category.setCreateUser(BaseContext.getCurrentId());
        category.setUpdateUser(BaseContext.getCurrentId());

        categoryMapper.insert(category);
    }

【3】mapper层

    /**
     * 新增分类
     * @param category
     */
    @Insert("insert into sky_take_out.category (type, name, sort, status, create_time, update_time, create_user, update_user) " +
            "value"+
    "(#{type},#{name},#{sort},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void insert(Category category);

2、分类分页查询模块 - GET接口

(1)代码开发

【1】controller层

分页查询返回的对象类型为Result<PageResult>

Result类包含code、msg、data

PageResult类包括total、records

    /**
     * 分类分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    @ApiOperation(value = "分类分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(CategoryPageQueryDTO categoryPageQueryDTO){
        log.info("分类分页查询,参数为:{}",categoryPageQueryDTO);
        PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);
        return Result.success(pageResult);
    }

【2】service层

  • PageHelper.startPage():这是MyBatis分页插件的核心方法,它会在线程上下文中设置分页参数,接下来的第一个查询操作会自动应用这些分页参数。
  • Page<Category>:这是PageHelper返回的分页结果对象,包含了:
    • 分页信息(当前页、每页大小、总页数、总记录数等)
    • 查询结果数据列表
    /**
     * 分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO){

        // 使用PageHelper分页插件启动分页功能
        // 参数1:当前页码(从categoryPageQueryDTO中获取)
        // 参数2:每页显示记录数(从categoryPageQueryDTO中获取)
        PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize());

        Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO);

        // 因为从mapper层获取的是Page格式数据,而service层需要返回PageResult格式,因此需要处理一下
        Long total = page.getTotal();
        List<Category> records = page.getResult();

        return new PageResult(total,records);
    }

【3】mapper层

    /**
     * 分页查询
     * @param categoryPageQueryDTO
     * @return
     */
    Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);

【4】mybatis文件

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

3、根据id删除分类模块 - DELETE接口

要求

  • 删除分类时,需要验证当前分类下是否关联菜品,若关联菜品,则不能删除
  • 删除分类时,需要验证当前分类下是否关联套餐,若关联套餐,则不能删除

(1)代码开发

【1】controller层

    /**
     * 根据id删除分类
     * @param id
     * @return
     */
    @ApiOperation(value = "根据id删除分类")
    @DeleteMapping()
    public Result delete(Long id){
        log.info("删除分类:{}",id);
        categoryService.deleteById(id);
        return Result.success();
    }

【2】service层

        在删除分类前,我们需要在service层验证该分类下是否存在菜品/套餐,这就需要调用【根据分类id查询旗下菜品数量】和【根据分类id查询旗下套餐数量】的mapper层

    /**
     * 根据id删除分类
     * @param id
     */
    public void deleteById(Long id){

        // 如果当前分类关联菜品,则抛出异常,不能删除
        Integer cnt = dishMapper.countByCategoryId(id);

        if(cnt > 0){
            // 若当前分类关联了菜品,则抛出【当前分类下有菜品无法删除】异常
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
        }

        // 如果当前分类关联套餐,则抛出异常,不能删除
        cnt = setmealMapper.countByCategoryId(id);

        if(cnt > 0){
            // 若当前分类关联了套餐,则抛出【当前分类下有套餐无法删除】异常
            throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
        }

        categoryMapper.deleteById(id);
    }

【3】mapper层

1)DishMapper - 根据分类id查询菜品数量

在dish菜品表我们可以看到菜品id为46、47、48的菜品对应分类id11,因此我们想要【统计该分类下的菜品数量】,只需要在dish表中,根据【上层传入的分类id】查询该分类id下的菜品数量即可

    /**
     * 根据分类id查询菜品数量
     * @param categoryId
     * @return
     */
    @Select("select count(id) from sky_take_out.dish where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);

2)SetmealMapper - 根据分类id查询关联套餐数量

同理

    /**
     * 根据分类id查询关联套餐数量
     * @param categoryId
     * @return
     */
    @Select("select count(id) from sky_take_out.setmeal where category_id = #{categoryId}")
    Integer countByCategoryId(Long categoryId);

3)CategoryMapper

    /**
     * 根据id删除分类
     * @param id
     */
    @Delete("delete from sky_take_out.category where id = #{id}")
    void deleteById(Long id);

4、修改分类模块 - PUT接口

(1)代码开发

【1】controller层

    /**
     * 编辑分类信息
     * @param categoryDTO
     * @return
     */
    @ApiOperation(value = "编辑分类信息")
    @PutMapping
    public Result update(@RequestBody CategoryDTO categoryDTO){
        log.info("编辑分类信息:{}",categoryDTO);
        categoryService.update(categoryDTO);
        return Result.success();
    }

【2】service层

  • 编辑分类信息时,需要设置【更改人】和【更改时间】
  • 用xml中update语句时,注意xml数据类型为实体类,因此需要将DTO类转换为实体类
    public void update(CategoryDTO categoryDTO){
        // 我们可以复用xml中的update方法,但xml中update数据类型为Category,因此我们需要进行类型转换
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO,category);

        category.setUpdateTime(LocalDateTime.now());
        category.setUpdateUser(BaseContext.getCurrentId());

        categoryMapper.update(category);
    }

【3】mapper层

    /**
     * 编辑分类信息
     * @param category
     */
    void update(Category category);

【4】mybatis文件

    <update id="update" parameterType="Category">
        update sky_take_out.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、启用禁用分类模块 - POST接口

(1)代码开发

【1】controller层

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

【2】service层

启用禁用分类的实质就是设定好category实体类的内容(特别是状态status),然后用update函数更新

    /**
     * 启用禁用分类
     * @param status
     * @param id
     */
    public void startOrStop(Integer status,Long id){
        // 启用禁用分类的实质就是设定好category实体类的内容,然后用update函数更新
        Category category = Category.builder()
                .status(status)
                .id(id)
                .updateTime(LocalDateTime.now())
                .updateUser(BaseContext.getCurrentId())
                .build();
        categoryMapper.update(category);
    }

6、根据类型查询分类模块 - GET接口

注意!这个功能用接口文档是可以测试成功的,但是在前端无法成功测试,看弹幕说是前端接口出现问题,暂时没有解决

(1)代码开发

【1】controller层

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    @ApiOperation(value = "根据类型查询分类")
    @GetMapping("/list")
    public Result<List<Category>> list(Integer type){
        List<Category> list = categoryService.list(type);
        return Result.success(list);
    }

【2】service层

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    public List<Category> list(Integer type)
    {
        return categoryMapper.list(type);
    }

【3】mapper层

    /**
     * 根据类型查询分类
     * @param type
     * @return
     */
    List<Category> list(Integer type);

【4】mybatis文件

    <select id="list" resultType="Category">
        select * from sky_take_out.category
        where status = 1
        <if test="type != null">
            and type = #{type}
        </if>
        # 按sort字段升序,create_time降序排列
        order by sort asc,create_time desc 
    </select>

分类模块开发结束~撒花!

七、三层架构模板

参考员工管理模块

1、Controller层

/**
 * 员工管理
 */
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @ApiOperation(value = "员工登录")
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

    /**
     * 退出
     *
     * @return
     */
    @ApiOperation(value = "员工退出")
    @PostMapping("/logout")
    public Result<String> logout() {
        return Result.success();
    }

    /**
     * 新增员工
     * @param employeeDTO
     * @return
     */
    @ApiOperation(value = "新增员工")
    @PostMapping()
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        log.info("新增员工:{}",employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }

    /**
     * 员工分页查询
     * @param employeePageQueryDTO
     * @return
     */
    @ApiOperation(value = "员工分页查询")
    @GetMapping("/page")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询,参数为:()",employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

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

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @ApiOperation(value = "根据id查询员工信息")
    @GetMapping("/{id}")
    public Result<Employee> getById(@PathVariable Long id) {
        Employee employee = employeeService.getById(id);
        return Result.success(employee);
    }

    /**
     * 编辑员工信息
     * @param employeeDTO
     * @return
     */
    @ApiOperation(value = "编辑员工信息")
    @PutMapping
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        log.info("编辑员工信息:{}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }

}

2、Service层

注意:实现类要加上注解@Service

(1)接口

public interface EmployeeService {

    /**
     * 员工登录
     * @param employeeLoginDTO
     * @return
     */
    Employee login(EmployeeLoginDTO employeeLoginDTO);

    /**
     * 新增员工
     * @param employeeDTO
     */
    public void save(EmployeeDTO employeeDTO);

    /**
     * 员工分页查询
     * @param employeePageQueryDTO
     * @return
     */
    PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

    /**
     * 启用禁用员工账号
     * @param status
     * @param id
     */
    void startOrStop(Integer status, Long id);

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    Employee getById(Long id);

    /**
     * 编辑员工信息
     * @param employeeDTO
     */
    void update(EmployeeDTO employeeDTO);
}

(2)实现类

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 员工登录
     *
     * @param employeeLoginDTO
     * @return
     */
    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);

        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        //密码比对
        // TODO 后期需要进行md5加密,然后再进行比对
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //3、返回实体对象
        return employee;
    }

    /**
     * 新增员工
     * @param employeeDTO
     */
    public void save(EmployeeDTO employeeDTO){
        // 类型转换,将DTO->Entity
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        //设置账号状态,默认状态1
        employee.setStatus(StatusConstant.ENABLE);

        //设置密码
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

        //设置当前记录的创建时间和修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());

        //当前记录的创建人id和修改人id
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.insert(employee);
    }

    /**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());

        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);

        // 因为从mapper层获取的是Page格式数据,而service层需要返回PageResult格式,因此需要处理一下
        long total = page.getTotal();
        List<Employee> records = page.getResult();

        return new PageResult(total,records);
    }

    /**
     * 启用禁用员工账号
     */
    public void startOrStop(Integer status,Long id){
        // 请给我创建一个 Employee 对象,并把它的 id 设为这个值,status 设为那个值,其他的属性用默认的就好,创建好了之后交给 employee 变量
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .build();

        employeeMapper.update(employee);
    }

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    public Employee getById(Long id){
        Employee employee = employeeMapper.getById(id);
        employee.setPassword("*****");
        return employee;
    }

    /**
     * 编辑员工信息
     * @param employeeDTO
     */
    public void update(EmployeeDTO employeeDTO){
        // 我们可以复用xml中的update方法,但xml中update数据类型为Employee,因此我们需要进行类型转换
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);
    }

}

3、Mapper层

注意:mapper层为interface接口!要加上注解@Mapper

@Mapper
public interface EmployeeMapper {

    /**
     * 根据用户名查询员工
     * @param username
     * @return
     */
    @Select("select * from employee where username = #{username}")
    Employee getByUsername(String username);

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

    /**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

    /**
     * 根据主键动态更新状态
     * @param employee
     */
    void update(Employee employee);

    /**
     * 根据id查询员工信息
     * @param id
     * @return
     */
    @Select("select * from sky_take_out.employee where id = #{id}")
    Employee getById(Long id);
}

4、Mybatis文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.EmployeeMapper">

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

    <update id="update" parameterType="Employee">
        update sky_take_out.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>
</mapper>
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐