【Java】苍穹外卖 Day02

苍穹外卖-day02

课程内容

  • 新增员工
  • 员工分页查询
  • 启用禁用员工账号
  • 编辑员工
  • 导入分类模块功能代码

**功能实现:**员工管理、菜品分类管理。

员工管理效果:

菜品分类管理效果:

1. 新增员工

1.1 需求分析和设计

1.1.1 产品原型

一般在做需求分析时,往往都是对照着产品原型进行分析,因为产品原型比较直观,便于我们理解业务。

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。

新增员工原型:

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

注意事项:

  1. 账号必须是唯一的
  2. 手机号为合法的11位手机号码
  3. 身份证号为合法的18位身份证号码
  4. 密码默认为123456
1.1.2 接口设计

找到资料–>项目接口文档–>苍穹外卖-管理端接口.html

image-20221111162912753 image-20221111162930483

明确新增员工接口的请求路径、请求方式、请求参数、返回数据

本项目约定:

  • 管理端发出的请求,统一使用**/admin**作为前缀。
  • 用户端发出的请求,统一使用**/user**作为前缀。
1.1.3 表设计

新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。

employee表结构:

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)姓名
usernamevarchar(32)用户名唯一
passwordvarchar(64)密码
phonevarchar(11)手机号
sexvarchar(2)性别
id_numbervarchar(18)身份证号
statusInt账号状态1正常 0锁定
create_timeDatetime创建时间
update_timedatetime最后修改时间
create_userbigint创建人id
update_userbigint最后修改人id

其中,employee表中的status字段已经设置了默认值1,表示状态正常。

image-20221111180159188

1.2 代码开发

1.2.1 设计DTO类

根据新增员工接口设计对应的DTO

前端传递参数列表:

image-20221111164002448

**思考:**是否可以使用对应的实体类来接收呢?

image-20221111164453341

**注意:**当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据

由于上述传入参数和实体类有较大差别,所以自定义DTO类。

进入sky-pojo模块,在com.sky.dto包下,已定义EmployeeDTO

package com.sky.dto;import lombok.Data;import java.io.Serializable;@Data
public class EmployeeDTO implements Serializable {private Long id;private String username;private String name;private String phone;private String sex;private String idNumber;}
1.2.2 Controller层

EmployeeController中创建新增员工方法

进入到sky-server模块中,在com.sky.controller.admin包下,在EmployeeController中创建新增员工方法,接收前端提交的参数。

	/*** 新增员工* @param employeeDTO* @return*/@PostMapping@ApiOperation("新增员工")public Result save(@RequestBody EmployeeDTO employeeDTO){log.info("新增员工:{}",employeeDTO);employeeService.save(employeeDTO);//该方法后续步骤会定义return Result.success();}

**注:**Result类定义了后端统一返回结果格式。

进入sky-common模块,在com.sky.result包下定义了Result.java

package com.sky.result;import lombok.Data;import java.io.Serializable;/*** 后端统一返回结果* @param <T>*/
@Data
public class Result<T> implements Serializable {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据public static <T> Result<T> success() {Result<T> result = new Result<T>();result.code = 1;return result;}public static <T> Result<T> success(T object) {Result<T> result = new Result<T>();result.data = object;result.code = 1;return result;}public static <T> Result<T> error(String msg) {Result result = new Result();result.msg = msg;result.code = 0;return result;}}
1.2.3 Service层接口

在EmployeeService接口中声明新增员工方法

进入到sky-server模块中,com.sky.server.EmployeeService

	/*** 新增员工* @param employeeDTO*/void save(EmployeeDTO employeeDTO);
1.2.4 Service层实现类

在EmployeeServiceImpl中实现新增员工方法

com.sky.server.impl.EmployeeServiceImpl中创建方法

	/*** 新增员工** @param employeeDTO*/public void save(EmployeeDTO employeeDTO) {Employee employee = new Employee();//对象属性拷贝BeanUtils.copyProperties(employeeDTO, employee);//设置账号的状态,默认正常状态 1表示正常 0表示锁定employee.setStatus(StatusConstant.ENABLE);//设置密码,默认密码123456employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));//设置当前记录的创建时间和修改时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录创建人id和修改人idemployee.setCreateUser(10L);//目前写个假数据,后期修改employee.setUpdateUser(10L);employeeMapper.insert(employee);//后续步骤定义}

在sky-common模块com.sky.constants包下已定义StatusConstant.java

package com.sky.constant;/*** 状态常量,启用或者禁用*/
public class StatusConstant {//启用public static final Integer ENABLE = 1;//禁用public static final Integer DISABLE = 0;
}
1.2.5 Mapper层

在EmployeeMapper中声明insert方法

com.sky.EmployeeMapper中添加方法

	/*** 插入员工数据* @param employee*/@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);

在application.yml中已开启驼峰命名,故id_number和idNumber可对应。

mybatis:configuration:#开启驼峰命名map-underscore-to-camel-case: true

1.3 功能测试

代码已经发开发完毕,对新增员工功能进行测试。

功能测试实现方式:

  • 通过接口文档测试
  • 通前后端联调测试

接下来我们使用上述两种方式分别测试。

1.3.1 接口文档测试

**启动服务:**访问http://localhost:8080/doc.html,进入新增员工接口

image-20221111184934153

json数据:

{"id": 0,"idNumber": "111222333444555666","name": "xiaozhi","phone": "13812344321","sex": "1","username": "小智"
}

响应码:401 报错

**通过断点调试:**进入到JwtTokenAdminInterceptor拦截器

 	/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}//1、从请求头中获取令牌 jwtProperties.getAdminTokenName()获取为tokenString token = request.getHeader(jwtProperties.getAdminTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}

**报错原因:**由于JWT令牌校验失败,导致EmployeeController的save方法没有被调用

**解决方法:**调用员工登录接口获得一个合法的JWT令牌

使用admin用户登录获取令牌

image-20221111185649636

添加令牌:

将合法的JWT令牌添加到全局参数中

文档管理–>全局参数设置–>添加参数

image-20221111185901726

接口测试:

image-20221111190132481

其中,请求头部含有JWT令牌

image-20221111190248268

查看employee表:

image-20221111191624908

测试成功。

1.3.2 前后端联调测试

启动nginx,访问 http://localhost

登录–>员工管理–>添加员工

image-20221111192339271

保存后,查看employee表

image-20221111192446950

测试成功。

**注意:**由于开发阶段前端和后端是并行开发的,后端完成某个功能后,此时前端对应的功能可能还没有开发完成,
导致无法进行前后端联调测试。所以在开发阶段,后端测试主要以接口文档测试为主。

1.4 代码完善

目前,程序存在的问题主要有两个:

  • 录入的用户名已存,抛出的异常后没有处理
  • 新增员工时,创建人id和修改人id设置为固定值

接下来,我们对上述两个问题依次进行分析和解决。

1.4.1 问题一

**描述:**录入的用户名已存,抛出的异常后没有处理

分析:

新增username=zhangsan的用户,若employee表中之前已存在。

image-20221111193700895

后台报错信息:

image-20221111194049620

查看employee表结构:

image-20221111194131787

发现,username已经添加了唯一约束,不能重复。

解决:

通过全局异常处理器来处理。

进入到sky-server模块,com.sky.hander包下,GlobalExceptionHandler.java添加方法

	/*** 处理SQL异常* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){//Duplicate entry 'zhangsan' 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);}}

进入到sky-common模块,在MessageConstant.java添加

public static final String ALREADY_EXISTS = "已存在";

再次,接口测试:

image-20221111195521095
1.4.2 问题二

描述:新增员工时,创建人id和修改人id设置为固定值

分析:

	/*** 新增员工** @param employeeDTO*/public void save(EmployeeDTO employeeDTO) {Employee employee = new Employee();//................//当前设置的id为固定值10//employee.setCreateUser(10L);employee.setUpdateUser(10L);////.................................employeeMapper.insert(employee);//后续步骤定义}

解决:

通过某种方式动态获取当前登录员工的id。

image-20221111201922482

员工登录成功后会生成JWT令牌并响应给前端:

在sky-server模块

package com.sky.controller.admin;
/*** 员工管理*/
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;@Autowiredprivate JwtProperties jwtProperties;/*** 登录** @param employeeLoginDTO* @return*/@PostMapping("/login")@ApiOperation(value = "员工登录")public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {//.........//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();claims.put(JwtClaimsConstant.EMP_ID, employee.getId());String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);//............return Result.success(employeeLoginVO);}}

后续请求中,前端会携带JWT令牌,通过JWT令牌可以解析出当前登录员工id:

JwtTokenAdminInterceptor.java

package com.sky.interceptor;/*** jwt令牌校验的拦截器*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//..............//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}

**思考:**解析出登录员工id后,如何传递给Service的save方法?

通过ThreadLocal进行传递。

1.4.3 ThreadLocal

介绍:

ThreadLocal 并不是一个Thread,而是Thread的局部变量。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

常用方法:

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

对ThreadLocal有了一定认识后,接下来继续解决问题二

image-20221111212349365

初始工程中已经封装了 ThreadLocal 操作的工具类:

在sky-common模块

package com.sky.context;public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}

在拦截器中解析出当前登录员工id,并放入线程局部变量中:

在sky-server模块中,拦截器:

package com.sky.interceptor;/*** jwt令牌校验的拦截器*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//.............................//2、校验令牌try {//.................Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);/将用户id存储到ThreadLocalBaseContext.setCurrentId(empId);//3、通过,放行return true;} catch (Exception ex) {//......................}}
}

在Service中获取线程局部变量中的值:

	/*** 新增员工** @param employeeDTO*/public void save(EmployeeDTO employeeDTO) {//.............................//设置当前记录创建人id和修改人idemployee.setCreateUser(BaseContext.getCurrentId());//目前写个假数据,后期修改employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.insert(employee);}

测试:使用admin(id=1)用户登录后添加一条记录

image-20221111214044510

查看employee表记录

image-20221111214354053

1.5 代码提交

点击提交:

image-20221112092139583

提交过程中,出现提示:

image-20221112092239578

继续push:

image-20221112092332837

推送成功:

image-20221112092427252

2. 员工分页查询

2.1 需求分析和设计

2.1.1 产品原型

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中, 除了分页条件以外,还有一个查询条件 “员工姓名”。

查询员工原型:

image-20221111215309289

业务规则

  • 根据页码展示员工信息
  • 每页展示10条数据
  • 分页查询时可以根据需要,输入员工姓名进行查询
2.1.2 接口设计

找到资料–>项目接口文档–>苍穹外卖-管理端接口.html

image-20221111220031113image-20221111220041965

注意事项:

  • 请求参数类型为Query,不是json格式提交,在路径后直接拼接。/admin/employee/page?name=zhangsan
  • 返回数据中records数组中使用Employee实体类对属性进行封装。

2.2 代码开发

2.2.1 设计DTO类

根据请求参数进行封装,在sky-pojo模块中

package com.sky.dto;import lombok.Data;import java.io.Serializable;@Data
public class EmployeePageQueryDTO implements Serializable {//员工姓名private String name;//页码private int page;//每页显示记录数private int pageSize;}
2.2.2 封装PageResult

后面所有的分页查询,统一都封装为PageResult对象。

在sky-common模块

package com.sky.result;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.List;/*** 封装分页查询结果*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {private long total; //总记录数private List records; //当前页数据集合}

员工信息分页查询后端返回的对象类型为: Result

package com.sky.result;import lombok.Data;import java.io.Serializable;/*** 后端统一返回结果* @param <T>*/
@Data
public class Result<T> implements Serializable {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据public static <T> Result<T> success() {Result<T> result = new Result<T>();result.code = 1;return result;}public static <T> Result<T> success(T object) {Result<T> result = new Result<T>();result.data = object;result.code = 1;return result;}public static <T> Result<T> error(String msg) {Result result = new Result();result.msg = msg;result.code = 0;return result;}}
2.2.3 Controller层

在sky-server模块中,com.sky.controller.admin.EmployeeController中添加分页查询方法。

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

在EmployeeService接口中声明pageQuery方法:

	/*** 分页查询* @param employeePageQueryDTO* @return*/PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
2.2.5 Service层实现类

在EmployeeServiceImpl中实现pageQuery方法:

	/*** 分页查询** @param employeePageQueryDTO* @return*/public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {// select * from employee limit 0,10//开始分页查询PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);//后续定义long total = page.getTotal();List<Employee> records = page.getResult();return new PageResult(total, records);}

**注意:**此处使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。底层基于 mybatis 的拦截器实现。

故在pom.xml文中添加依赖(初始工程已添加)

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>${pagehelper}</version>
</dependency>
2.2.6 Mapper层

在 EmployeeMapper 中声明 pageQuery 方法:

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

在 src/main/resources/mapper/EmployeeMapper.xml 中编写SQL:

<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>

2.3 功能测试

可以通过接口文档进行测试,也可以进行前后端联调测试。

接下来使用两种方式分别测试:

2.3.1 接口文档测试

**重启服务:**访问http://localhost:8080/doc.html,进入员工分页查询

image-20221112101848657

响应结果:

image-20221112101946022
2.3.2 前后端联调测试

点击员工管理

image-20221112102441810

输入员工姓名为zhangsan

image-20221112102540938

不难发现,最后操作时间格式不清晰,在代码完善中解决。

image-20221112102745437

2.4 代码完善

**问题描述:**操作时间字段显示有问题。

image-20221112103235539

解决方式:

1). 方式一

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

image-20221112103501581

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

2). 方式二(推荐 )

在WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理

	/*** 扩展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);}

添加后,再次测试

image-20221112104305608

时间格式定义,sky-common模块中

package com.sky.json;public class JacksonObjectMapper extends ObjectMapper {//.......public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";//.......}
}

2.5 代码提交

image-20221112105011214

后续步骤和新增员工代码提交一致,不再赘述。

3. 启用禁用员工账号

3.1 需求分析与设计

3.1.1 产品原型

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 “禁用”,如果员工账号状态为已禁用,则按钮显示为"启用"。

启禁用员工原型:

image-20221112112359233

业务规则:

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

image-20221112112728333image-20221112112739680

1). 路径参数携带状态值。

2). 同时,把id传递过去,明确对哪个用户进行操作。

3). 返回数据code状态是必须,其它是非必须。

3.2 代码开发

3.2.1 Controller层

在sky-server模块中,根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:

	/*** 启用禁用员工账号* @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("启用禁用员工账号")public Result startOrStop(@PathVariable Integer status,Long id){log.info("启用禁用员工账号:{},{}",status,id);employeeService.startOrStop(status,id);//后绪步骤定义return Result.success();}
3.2.2 Service层接口

在 EmployeeService 接口中声明启用禁用员工账号的业务方法:

	/*** 启用禁用员工账号* @param status* @param id*/void startOrStop(Integer status, Long id);
3.2.3 Service层实现类

在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法:

	/*** 启用禁用员工账号** @param status* @param id*/public void startOrStop(Integer status, Long id) {Employee employee = Employee.builder().status(status).id(id).build();employeeMapper.update(employee);}
3.2.4 Mapper层

在 EmployeeMapper 接口中声明 update 方法:

	/*** 根据主键动态修改属性* @param employee*/void update(Employee employee);

在 EmployeeMapper.xml 中编写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>

3.3 功能测试

3.3.1 接口文档测试

**测试前,**查询employee表中员工账号状态

image-20221112143142457

开始测试

image-20221112143316357

测试完毕后,再次查询员工账号状态

image-20221112143428676
3.3.2 前后端联调测试

测试前:

image-20221112143552246

点击启用:

image-20221112143655318

3.4 代码提交

image-20221112143827631

后续步骤和上述功能代码提交一致,不再赘述。

4. 编辑员工

4.1 需求分析与设计

4.1.1 产品原型

在员工管理列表页面点击 “编辑” 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 “保存” 按钮完成编辑操作。

员工列表原型:

image-20221112144731759

修改页面原型

注:点击修改时,数据应该正常回显到修改页面。

image-20221112144842825
4.1.2 接口设计

根据上述原型图分析,编辑员工功能涉及到两个接口:

  • 根据id查询员工信息
  • 编辑员工信息

1). 根据id查询员工信息

image-20221112145607939 image-20221112145619775

2). 编辑员工信息

image-20221112145643769 image-20221112145659035

注:因为是修改功能,请求方式可设置为PUT。

4.2 代码开发

4.2.1 回显员工信息功能

1). Controller层

在 EmployeeController 中创建 getById 方法:

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

2). Service层接口

在 EmployeeService 接口中声明 getById 方法:

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

3). Service层实现类

在 EmployeeServiceImpl 中实现 getById 方法:

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

4). Mapper层

在 EmployeeMapper 接口中声明 getById 方法:

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

1). Controller层

在 EmployeeController 中创建 update 方法:

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

2). Service层接口

在 EmployeeService 接口中声明 update 方法:

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

3). Service层实现类

在 EmployeeServiceImpl 中实现 update 方法:

 	/*** 编辑员工信息** @param employeeDTO*/public void update(EmployeeDTO employeeDTO) {Employee employee = new Employee();BeanUtils.copyProperties(employeeDTO, employee);employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.update(employee);}

在实现启用禁用员工账号功能时,已实现employeeMapper.update(employee),在此不需写Mapper层代码。

4.3 功能测试

4.3.1 接口文档测试

分别测试根据id查询员工信息编辑员工信息两个接口

1). 根据id查询员工信息

查询employee表中的数据,以id=4的记录为例

image-20221112154253995

开始测试

image-20221112154411245

获取到了id=4的相关员工信息

2). 编辑员工信息

修改id=4的员工信息,namezhangsan改为张三丰username张三改为zhangsanfeng

image-20221112155001414

查看employee表数据

image-20221112155029547

4.3.2 前后端联调测试

进入到员工列表查询

image-20221112155206712

对员工姓名为杰克的员工数据修改,点击修改,数据已回显

image-20221112155430652

修改后,点击保存

image-20221112155559298

4.4 代码提交

image-20221112155735984

后续步骤和上述功能代码提交一致,不再赘述。

5. 导入分类模块功能代码

5.1 需求分析与设计

5.1.1 产品原型

后台系统中可以管理分类信息,分类包括两种类型,分别是 菜品分类套餐分类

先来分析菜品分类相关功能。

**新增菜品分类:**当我们在后台系统中添加菜品时需要选择一个菜品分类,在移动端也会按照菜品分类来展示对应的菜品。

**菜品分类分页查询:**系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

**根据id删除菜品分类:**在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。

**修改菜品分类:**在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作。

**启用禁用菜品分类:**在分类管理列表页面,可以对某个分类进行启用或者禁用操作。

**分类类型查询:**当点击分类类型下拉框时,从数据库中查询所有的菜品分类数据进行展示。

分类管理原型:

image-20221112160907419

业务规则:

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

根据上述原型图分析,菜品分类模块共涉及6个接口。

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

接下来,详细地分析每个接口。

找到资料–>项目接口文档–>苍穹外卖-管理端接口.html

1). 新增分类

image-20221112163011291 image-20221112163044547

2). 分类分页查询

image-20221112163209383image-20221112163233212

3). 根据id删除分类

image-20221112163323554

4). 修改分类

image-20221112163424457 image-20221112163445296

5). 启用禁用分类

image-20221112163557247 image-20221112163622896

6). 根据类型查询分类

image-20221112163806318 image-20221112163839168

5.1.3 表设计

category表结构:

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)分类名称唯一
typeint分类类型1菜品分类 2套餐分类
sortint排序字段用于分类数据的排序
statusint状态1启用 0禁用
create_timedatetime创建时间
update_timedatetime最后修改时间
create_userbigint创建人id
update_userbigint最后修改人id

5.2 代码导入

导入资料中的分类管理模块功能代码即可

image-20221112164259732

可按照mapper–>service–>controller依次导入,这样代码不会显示相应的报错。

进入到sky-server模块中

5.2.1 Mapper层

DishMapper.java

package com.sky.mapper;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface DishMapper {/*** 根据分类id查询菜品数量* @param categoryId* @return*/@Select("select count(id) from dish where category_id = #{categoryId}")Integer countByCategoryId(Long categoryId);}

SetmealMapper.java

package com.sky.mapper;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface SetmealMapper {/*** 根据分类id查询套餐的数量* @param id* @return*/@Select("select count(id) from setmeal where category_id = #{categoryId}")Integer countByCategoryId(Long id);}

CategoryMapper.java

package com.sky.mapper;import com.github.pagehelper.Page;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface CategoryMapper {/*** 插入数据* @param category*/@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +" VALUES" +" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")void insert(Category category);/*** 分页查询* @param categoryPageQueryDTO* @return*/Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);/*** 根据id删除分类* @param id*/@Delete("delete from category where id = #{id}")void deleteById(Long id);/*** 根据id修改分类* @param category*/void update(Category category);/*** 根据类型查询分类* @param type* @return*/List<Category> list(Integer type);
}

CategoryMapper.xml,进入到resources/mapper目录下

<?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.CategoryMapper"><select id="pageQuery" resultType="com.sky.entity.Category">select * from category<where><if test="name != null and name != ''">and name like concat('%',#{name},'%')</if><if test="type != null">and type = #{type}</if></where>order by sort asc , create_time desc</select><update id="update" parameterType="Category">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><select id="list" resultType="Category">select * from categorywhere status = 1<if test="type != null">and type = #{type}</if>order by sort asc,create_time desc</select>
</mapper>
5.2.2 Service层

CategoryService.java

package com.sky.service;import com.sky.dto.CategoryDTO;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import com.sky.result.PageResult;
import java.util.List;public interface CategoryService {/*** 新增分类* @param categoryDTO*/void save(CategoryDTO categoryDTO);/*** 分页查询* @param categoryPageQueryDTO* @return*/PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);/*** 根据id删除分类* @param id*/void deleteById(Long id);/*** 修改分类* @param categoryDTO*/void update(CategoryDTO categoryDTO);/*** 启用、禁用分类* @param status* @param id*/void startOrStop(Integer status, Long id);/*** 根据类型查询分类* @param type* @return*/List<Category> list(Integer type);
}

EmployeeServiceImpl.java

package com.sky.service.impl;import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.PasswordConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.entity.Employee;
import com.sky.exception.AccountLockedException;
import com.sky.exception.AccountNotFoundException;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.result.PageResult;
import com.sky.service.EmployeeService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.time.LocalDateTime;
import java.util.List;@Service
public class EmployeeServiceImpl implements EmployeeService {@Autowiredprivate 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) {Employee employee = new Employee();//对象属性拷贝BeanUtils.copyProperties(employeeDTO, employee);//设置账号的状态,默认正常状态 1表示正常 0表示锁定employee.setStatus(StatusConstant.ENABLE);//设置密码,默认密码123456employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));//设置当前记录的创建时间和修改时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录创建人id和修改人idemployee.setCreateUser(BaseContext.getCurrentId());//目前写个假数据,后期修改employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.insert(employee);}/*** 分页查询** @param employeePageQueryDTO* @return*/public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {// select * from employee limit 0,10//开始分页查询PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);long total = page.getTotal();List<Employee> records = page.getResult();return new PageResult(total, records);}/*** 启用禁用员工账号** @param status* @param id*/public void startOrStop(Integer status, Long id) {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) {Employee employee = new Employee();BeanUtils.copyProperties(employeeDTO, employee);employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.update(employee);}}
5.2.3 Controller层

CategoryController.java

package com.sky.controller.admin;import com.sky.dto.CategoryDTO;
import com.sky.dto.CategoryPageQueryDTO;
import com.sky.entity.Category;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.CategoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;/*** 分类管理*/
@RestController
@RequestMapping("/admin/category")
@Api(tags = "分类相关接口")
@Slf4j
public class CategoryController {@Autowiredprivate CategoryService categoryService;/*** 新增分类* @param categoryDTO* @return*/@PostMapping@ApiOperation("新增分类")public Result<String> save(@RequestBody CategoryDTO categoryDTO){log.info("新增分类:{}", categoryDTO);categoryService.save(categoryDTO);return Result.success();}/*** 分类分页查询* @param categoryPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("分类分页查询")public Result<PageResult> page(CategoryPageQueryDTO categoryPageQueryDTO){log.info("分页查询:{}", categoryPageQueryDTO);PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);return Result.success(pageResult);}/*** 删除分类* @param id* @return*/@DeleteMapping@ApiOperation("删除分类")public Result<String> deleteById(Long id){log.info("删除分类:{}", id);categoryService.deleteById(id);return Result.success();}/*** 修改分类* @param categoryDTO* @return*/@PutMapping@ApiOperation("修改分类")public Result<String> update(@RequestBody CategoryDTO categoryDTO){categoryService.update(categoryDTO);return Result.success();}/*** 启用、禁用分类* @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("启用禁用分类")public Result<String> startOrStop(@PathVariable("status") Integer status, Long id){categoryService.startOrStop(status,id);return Result.success();}/*** 根据类型查询分类* @param type* @return*/@GetMapping("/list")@ApiOperation("根据类型查询分类")public Result<List<Category>> list(Integer type){List<Category> list = categoryService.list(type);return Result.success(list);}
}

全部导入完毕后,进行编译

image-20221112170742400

5.3 功能测试

重启服务,访问http://localhost:80,进入分类管理

分页查询:

image-20221112171252992

分类类型:

image-20221112171402210

启用禁用:

image-20221112171604991

点击禁用

image-20221112171700774

修改:

回显

image-20221112172117834

修改后

image-20221112172150709

新增:

image-20221112172356093

点击确定,查询列表

image-20221112172439370

删除:

image-20221112172525216

删除后,查询分类列表

image-20221112172611754

删除成功

5.4 代码提交

image-20221112173205048

后续步骤和上述功能代码提交一致,不再赘述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2778697.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

VR和AR傻傻分不清,一句话给你讲明白。

不说废话&#xff0c;直接说结论&#xff0c;虚拟现实&#xff08;Virtual Reality&#xff0c;VR&#xff09;和增强现实&#xff08;Augmented Reality&#xff0c;AR&#xff09;。如果现实是A&#xff0c;虚拟是B&#xff0c;那么VRB&#xff0c;ARAB&#xff0c;就这简单&…

算法学习——LeetCode力扣栈与队列篇1

算法学习——LeetCode力扣栈与队列篇1 232. 用栈实现队列 232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; 描述 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQu…

【MySQL】数据库的基础——数据库的介绍、MySQL的介绍和架构、SQL分类、MySQL的基本使用、MySQL的存储引擎

文章目录 MySQL1. 数据库的介绍1.2 主流数据库 2. MySQL的介绍2.1 MySQL架构2.2 SQL分类2.3 MySQL的基本使用2.4 MySQL存储引擎 MySQL 1. 数据库的介绍 数据库&#xff08;Database&#xff0c;简称DB&#xff09;是按照数据结构来组织、存储和管理数据的仓库。它是长期存储在计…

elasticsearch下载及可视化工具下载使用

elasticsearch下载及配置、启动 一、下载 Download Elasticsearch | Elastic 二、启动 双击bat即可。 出现如下说明启动成功&#xff1a; 访问测试&#xff1a; 三、注意 &#xff08;1&#xff09;因为es启动默认端口是&#xff1a;9200,所以需要检查此端口是否被占用。…

C#在窗体正中输出文字以及输出文字的画刷使用

为了在窗体正中输出文字&#xff0c;需要获得输出文字区域的宽和高&#xff0c;这使用MeasureString方法&#xff0c;方法返回值为Size类型&#xff1b; 然后计算输出的起点的x和y坐标&#xff0c;就可以输出了&#xff1b; using System; using System.Collections.Generic; …

js中bind、call、apply 区别(如何实现)

文章目录 一、作用二、区别applycallbind小结 三、实现 一、作用 call、apply、bind作用是改变函数执行时的上下文&#xff0c;简而言之就是改变函数运行时的this指向 那么什么情况下需要改变this的指向呢&#xff1f;下面举个例子 var name "lucy"; var obj {n…

每日五道java面试题之java基础篇(五)

第一题. final、finally、finalize 的区别&#xff1f; final ⽤于修饰变量、⽅法和类&#xff1a;final 修饰的类不可被继承&#xff1b;修饰的⽅法不可被重写&#xff1b;修饰的变量不可变。finally 作为异常处理的⼀部分&#xff0c;它只能在 try/catch 语句中&#xff0c;…

Java外卖小程序管理系统

技术架构&#xff1a; springboot ssm mysql redis 有需要该项目的小伙伴可以私信我你的Q。 功能描述&#xff1a; 商品管理&#xff1a;新增商品、所有商品 菜单管理&#xff1a;菜单管理、菜单分类 订单管理&#xff1a;订单总览&#xff08;包括未付款、已付款、已…

linux进程(进程地址空间)

目录 前言&#xff1a; 正文&#xff1a; 1.验证地址空间 2.地址空间是指物理空间吗 3.linux内核的地址空间 4进程访问地址 4.1早期程序寻址 4.2进程地址空间到物理内存的映射 4.3解释同一变量产生不同值 5虚拟地址空间的意义 5.1保护物理内存 5.2进程管理和内…

[论文总结] 深度学习在农业领域应用论文笔记12

文章目录 1. 3D-ZeF: A 3D Zebrafish Tracking Benchmark Dataset (CVPR, 2020)摘要背景相关研究所提出的数据集方法和结果个人总结 2. Automated flower classification over a large number of classes (Computer Vision, Graphics & Image Processing, 2008)摘要背景分割…

前端JavaScript篇之对象创建的方式有哪些?

目录 对象创建的方式有哪些&#xff1f;1. 工厂模式&#xff1a;2. 构造函数模式&#xff1a;3. 原型模式&#xff1a;4. 混合模式&#xff1a;5. 动态原型模式&#xff1a;6. 寄生构造函数模式&#xff1a;7. 字面量方式&#xff1a; 对象创建的方式有哪些&#xff1f; JavaS…

春晚魔术和约瑟夫问题

春晚的魔术实际上是一个约瑟夫问题&#xff0c;最终的结果是魔术开始时确定的几个变量确定好的&#xff0c;扑克牌只是道具和障眼法。网上一查这个问题发现颇有历史渊源&#xff0c;17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事&#xff1a;15个教徒和15 个…

房屋租赁系统的Java实战开发之旅

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

WebSocket 通信流程,注解和Spring实现WebSocket ,实战多人聊天室系统

一、前言 实现即时通信常见的有四种方式-分别是&#xff1a;轮询、长轮询(comet)、长连接(SSE)、WebSocket。 ①短轮询 很多网站为了实现推送技术&#xff0c;所用的技术都是轮询。轮询是在特定的的时间间隔&#xff08;如每1秒&#xff09;&#xff0c;由客户端浏览器对服务…

机器学习2--逻辑回归(案列)

糖尿病数据线性回归预测 import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_diabetes diabetesload_diabetes() datadiabetes[data] targetdiabetes[target] feature_namesdiabetes[feature_names] data.shape df …

2024刘谦春晚第二个扑克牌魔术

前言 就是刚才看春晚感觉这个很神奇&#xff0c;虽然第一个咱模仿不过来&#xff0c;第二个全国人民这么多人&#xff0c;包括全场观众都有成功&#xff0c;这肯定是不需要什么技术&#xff0c;那我觉得这个肯定就是数学了&#xff0c;于是我就胡乱分析一通。 正文 首先准备…

Mysql Day04

mysql体系结构 连接层服务层引擎层&#xff08;索引&#xff09;存储层 存储引擎 存储引擎是基于表建立的&#xff0c;默认是innoDB show create table tb; 查看当前数据库支持的存储引擎 show engines; InnoDB 特点 DML&#xff08;数据增删改&#xff09;遵循ACID模…

MongoDB的分片集群(二) :mongodb4.x分片集群离线搭建开启安全认证

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 相关文章&#xff1a; MongoDB的分片集群(一) : 基础知识 在《MongoDB的分片集群(一) : 基础知识》中梳理了分片集群的基础知识…

opencv计算机视觉

树莓派主机的无键盘解决 进入控制面板&#xff0c;更改适配器设置&#xff0c;WIFI属性&#xff0c;勾选 1.将网线两头分别接入树莓派和笔记本的网线接口 2.在无线连接属性那里勾选允许其他用户连接 3.运行cmd使用arp -a查看树莓派ip地址&#xff0c;或者使用ipscanner查看 cmd…

JavaIO读取C101.txt文件

一、split分割带空格的字符串&#xff08;四种方法及其区别&#xff09; 参考&#xff1a;https://blog.csdn.net/yezonghui/article/details/106455940 String str "a b c d";String[] arr1 str.split(" "); //仅分割一个空格 String[] arr2 str…