瑞吉外卖业务开发(1)https://developer.aliyun.com/article/1530401
代码开发
执行过程
- 页面发送ajax请求,将新增员工中输入的数据信息以json格式发送到服务端
- 服务端Controller接收页面提交的数据并调用Service将数据进行保存
- Service调用Mapper操作数据库,保存数据
前端:员工管理页面点击添加员工按钮,会为我们切换到添加员工那个页面(iframe的方式)
@PostMapping public R<String> save(@RequestBody Employee employee,HttpServletRequest request){ log.info("新增员工.员工信息{}" ,employee.toString()); employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes())); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); //获得当前登录用户的id Long empID= (Long)request.getSession().getAttribute("employee"); employee.setCreateUser(empID); employee.setUpdateUser(empID); employeeService.save(employee); return R.success("新增员工成功"); }
异常处理
前面的程序还存在一个问题,就是当我们在新增员工时输入的账号已经存在,由于employee表中对该字段加入了唯一约束,此时程序会抛出异常:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'zhangsan’for key ‘idx_username’
此时需要我们的程序进行异常捕获,通常有两种处理方式:
1、在Controller方法中加入try、catch进行异常捕获
2、使用异常处理器进行全局异常捕获
一般来说对每个异常都进行单独捕获未免太麻烦,所以用异常处理器进行全局异常捕获
代码
/** * 全局异常捕获 */ @ControllerAdvice(annotations = {RestController.class, Controller.class}) //@ResponseBody的作用其实是将java对象转为json格式的数据。 @ResponseBody @Slf4j public class GlobalExceptionHandler { /** * 进行异常处理 * @return */ @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHander(SQLIntegrityConstraintViolationException ex){ log.error(ex.getMessage()); return R.error("失败了"); } }
测试
完善异常处理器
使异常处理器在捕获字段名重复这个异常的时候传回的错误信息可以包含重复的字段值
public R<String> exceptionHander(SQLIntegrityConstraintViolationException ex){ log.error(ex.getMessage()); if (ex.getMessage().contains("Duplicate entry")){ String[] split = ex.getMessage().split(" "); return R.error(split[2]+"已存在"); } return R.error("失败了"); }
总结
1、根据产品原型明确业务需求
2、重点分析数据的流转过程和数据格式
3、通过debug断点调试跟踪程序执行过程
七,员工信息分页查询
需求分析
系统中的员工很多时,如果在一个页面全部显示会很乱,不便于查看,所以一般系统中都会以分页的形式来展示列表数据
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
- 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端
- 服务端Controller接收页面提交的数据并调用Service查询数据
- Service调用Mapper操作数据库,查询分页数据
- Controller将查询到的分页数据响应给页面
- 页面接收到分页数据并通过ElementUI的Table组件展示到页面上
前端:
created是系统刚开始就执行的方法
设置MabitsPlus的配置
@Configuration public class MybatisPlusConfig { public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mybatisPlusInterceptor; } }
Controller
/** * 分页查询 * @param page * @param pageSize * @param name * @return */ @GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ log.info("page ={},pageSize={},name={}",page,pageSize,name); //构造分页构造器 Page pageInfo = new Page(page, pageSize); //构造条件构造器 LambdaQueryWrapper<Employee> queryWrapper= new LambdaQueryWrapper<>(); // 添加过滤条件 queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name ); //排序条件 queryWrapper.orderByDesc(Employee::getUpdateTime); //执行查询 employeeService.page(pageInfo,queryWrapper); //自动给我们的page封装好了 return R.success(pageInfo); }
分页请求发送的几个地方
- 刚初始化页面
- 查询
- 点击分页插件
[!important]
返回的R里面的data是一种名叫Page的数据结构,这个数据结构里面自动封装了EmpLoyee数组
八, 禁用员工账号
代码开发
- 页面发送ajax请求,将参数(id,status)提交到服务端
- 服务端Controller接收页面提交的数据,并将调用Service更新数据
- Service调用Mapper操控服务器
初步代码
@PutMapping public R<String> update(@RequestBody Employee employee ){ log.info(employee.toString()); return null; }
当点击禁用,传送来的信息
功能测试
没有报错,但是功能没有实现
观察控制台输出sql
和数据库里面的id并不一致
代码修复
原因是js对数据处理的时候丢失了精度,导致提交结果和数据库中的id不一致
如何解决这个问题
我们可以在服务端给页面响应JSON数据时进行处理,将long型统一转换成String字符串
具体实现步骤:
1)提供对象转换器Jackson0bjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)
2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行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_TIME_FORMAT = "HH:mm:ss"; public JacksonObjectMapper() { super(); //收到未知属性时不报异常 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); //反序列化时,属性不存在的兼容处理 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleModule simpleModule = new SimpleModule() .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))) .addSerializer(BigInteger.class, ToStringSerializer.instance) .addSerializer(Long.class, ToStringSerializer.instance) .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); //注册功能模块 例如,可以添加自定义序列化器和反序列化器 this.registerModule(simpleModule); } }
/** * 扩展mvc消息转换器 * @param converters */ @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { //创建消息转换器对象 MappingJackson2CborHttpMessageConverter converter = new MappingJackson2CborHttpMessageConverter(); //创建对象转换器底层使用Jackson将 java对象转换成json converter.setObjectMapper(new JacksonObjectMapper()); //将上面的消息转换器将对象追加到mvc框架的转换器集合中 converters.add(0,converter); }
九,编辑员工信息
需求分析
管理员可对于用户信息进行编辑
代码开发
1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
2、在add.html页面获取url中的参数[员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
@GetMapping("/{id}") public R<Employee> getByid(@PathVariable Long id){ Employee employee = employeeService.getById(id); if (employee!=null){ return R.success(employee); } return R.error("没有查询到对应员工信息"); }
[!note]
之所以可以完成修改,其实点击完成调用了我们之前写的通用的update方法,只要是根据id修改的表里某个字段都可以进行修改
公共字段自动填充
问题分析
前面我们完成了后台管理系统的员工管理功能开发,在新增员工时需要设置创建时间,创建人,修改时间,修改人等信息,在编辑员工时需要设置修改时间和修改人等信息.这些字段属于公共字段,也就是表中很多元素都有这些字段,如下:
能不能对于这些公共字段在某个地方统一处理,来简化开发呢答案就是使用Mybatis Plus提供的公共字段自动填充功能。
代码实现
MybatisPlus公共字段自动填充,也就是插入或者更新的时候为指定字段设置指定的值,使用它的好处是可以统一为这些字段进行处理,避免了代码重复.
实现步骤
- 在实体类的属性上加入@TableField的注解,指定自动填充的策略
- 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHander接口
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE ) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser;
public class MyMetaObjecthandler implements MetaObjectHandler { /** * 插入操作自动填充 * @param metaObject */ @Override public void insertFill(MetaObject metaObject) { metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime",LocalDateTime.now()); metaObject.setValue("createUser",new Long(1)); metaObject.setValue("updateUser",new Long(1)); } /** * 更新操作自动填充 * @param metaObject */ @Override public void updateFill(MetaObject metaObject) { metaObject.setValue("updateTime",LocalDateTime.now()); metaObject.setValue("updateUser",new Long(1)); } }
功能完善
前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决,就是我们在自动填充createUser和updateUser时设置的用户id是固定值,现在我们需要改造成动态获取当前登录用户的id。
有的同学可能想到,用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?
注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id
可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id) :
long id = Thread.currentThread().getId() ; log.info("线程id:{0}",id);
执行员工编辑功能进行验证,通过观察控制太输出可以发现,一次请求对应的线程id是相同
什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id )。
实现步骤
- 编写BaseContext工具类,基于ThreadLocal封装的工具类
- 在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
- 在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
[!note]
用设置静态方法的方式把ThreadLocal封装到工具类,然后这样ThreadLocal方法就可以直接调用
/** * 基于ThreadLocal封装的工具类用于保存和获取当期用户的登录id */public class BaseContext { private static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void setCurrentId(Long id){ threadLocal.set(id); } public static Long getCurrentId(){ return threadLocal.get(); } }
在Filter中把用户id放入当前线程的线程局部变量的值(用户id)以便于之后使用
同时修改之前公共字段自动填充的内容
metaObject.setValue("createUser",BaseContext.getCurrentId()); metaObject.setValue("updateUser",BaseContext.getCurrentId());
新增分类
需求分析
后台管理系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类,当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台中添加一个套餐时需要选择一个餐品分类,在移动端也会按照菜品分类或者餐品分类来展示
我们可以在后台分别添加菜品分类和套餐分类
数据模型
需要注意分类的名称是唯一的
瑞吉外卖业务开发(3)https://developer.aliyun.com/article/1530407