【项目实战 Day1 - 2】springboot + vue 苍穹外卖系统(员工模块 + 分类模块 完结)
苍穹外卖项目开发训练day1-2内容,包含前后端环境搭建、Swagger接口文档配置、【员工管理模块】以及【分类管理模块】的代码开发内容,保姆级笔记教程!
目录
扩展Spring Mvc消息转换器进行日期格式化(java格式序列化为json格式)
一、前端环境搭建
因为该项目主要学习后端开发,因此我们需要先配置前端环境
在黑马程序员公众号获取前端资料,将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语句快速拼接
- 设置分页参数:在执行数据库查询之前,调用 PageHelper.startPage(int pageNum 第几页, int pageSize 每页多少条数据) 方法。
- 拦截查询:紧接着执行正常的查询方法,PageHelper 会拦截这个查询。
- 自动改造SQL:
改造原SQL:在原SQL语句后面自动加上分页关键字(例如为 MySQL 加上 LIMIT)。
执行计数SQL:自动执行一条 SELECT COUNT(1) FROM ... 的语句来查询总记录数。
- 封装结果:将分页查询到的当前页数据和总记录数等信息,封装到一个强大的 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 就像是一个全局公告板,但这个公告板是每个线程独享的,别人看不到。
调用
PageHelper.startPage(2, 10)
时,它就把页码2
和页数10
写到了当前线程专属的公告板上。当 MyBatis 执行 SQL 时,PageHelper 这个“拦截员”会主动去查看当前线程的公告板上有没有分页信息。
如果发现有(即第2页,每页10条),它就立刻根据这个信息去改造SQL(比如为 MySQL 加上
LIMIT 10, 10
)。为了保证不会影响下一个请求,它会清理掉当前线程公告板上的信息。
【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个接口进行开发:
- 新增分类
- 分类分页查询
- 根据id删除分类
- 修改分类
- 启用禁用分类
- 根据类型查询分类
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>

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