该系列将记录一份完整的实战项目的完成过程,该篇属于第三天
案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容
该篇我们将完成以下内容:
公共字段自动填充
新添分类
分类信息分页查询
删除分类
修改分类
我们的功能开发一般分为三个阶段
前面我们已经完成了后台系统的员工系统的开发,在新增或修改员工时需要填写创建时间创建人修改时间修改人等繁杂信息
而且这些属性基本在后续的菜品,套餐中都有所体现,我们把这些字段称为公共字段,所以我们希望采用一种统一的方法来设置:
我们先来简单介绍一下流程:
// 属性包括有INSERT,UPDATE,INSERT_UPDATE
@TableField(fill = FieldFill.属性)
package com.qiuluo.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
// 记得设置为配置类
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 添加时自动设置
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
}
/**
* 修改时自动设置
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
}
}
首先我们为实体类的待修改属性添加上注解:
package com.qiuluo.reggie.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
private String idNumber;
private Integer status;
@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;
}
在common文件夹下创建新的MyMetaObjectHandler处理器:
package com.qiuluo.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 添加时自动设置
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("修改元数据");
// 我们可以在这里统一设置公共字段的值
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
// 但是关于修改或创建人我们无法设置,因为我们无法得知目前是谁修改,这里暂时用Long(1)代替
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));
}
}
之后,再将我们的服务层中相关设置属性代码去掉即可
在上面我们已经完成基本的公共字段的设置,但是我们会注意到我们无法设置相关人的信息
因为我们在之前的服务层中采用Request来获得当前Session下的保存的员工id,但目前我们无法获得Request
但是我们可以采用线程的单一性来获得当前线程下存储的内容
每次客户端发送的每次http请求,对应的服务器都会分配一个新的线程来处理,在处理过程中设计到下面类方法都属于一个相同的线程:
验证方法可以采用获得并比较当前线程:
// 通过该方法获得当前线程的id
long id = Thread.currentThread().getId();
// 以日志的形式输出即可
log.info("当前线程id:" + id);
正常情况下我们会得到三个线程相同的id
那么我们就可以利用线程相同的原理,在当前线程中直接存储id,再在MyMetaObjectHandler中获得id
我们主要采用ThreadLocal,我们简单介绍一下ThreadLocal:
其中Thread Local主要只有两个方法:
// 设置当前线程的线程局部变量的值
public void set(T value);
// 返回当前线程所对应的局部变量的值
public T get();
下面我们演示相关步骤:
package com.qiuluo.reggie.common;
/**
* 基于ThreadLocal的工具类,用于保存用户id
*/
public class BaseContext {
// 设置一个ThreadLocal,存储Long型(id)
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
// 存储方法,调用set
public static void setCurrentId(Long id){
threadLocal.set(id);
}
// 获得方法,调用get
public static Long getCurrentId(){
return threadLocal.get();
}
}
package com.qiuluo.reggie.filter;
import com.alibaba.fastjson.JSON;
import com.qiuluo.reggie.common.BaseContext;
import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}",requestURI);
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
boolean check = check(urls, requestURI);
if(check){
log.info("本次请求{}不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
if(request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
log.info("线程id" + Thread.currentThread().getId());
// 注意这里:我们直接使用工具类的方法来设置线程局部变量
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
response.getWriter().write(JSON.toJSONString(Result.error("NOTLOGIN")));
return;
}
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
package com.qiuluo.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 添加时自动设置
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("修改元数据");
log.info("线程id" + Thread.currentThread().getId());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
// 我们调用工具类方法来使用ThreadLocal获得当前id并赋值
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
/**
* 修改时自动设置
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}
测试方法很简单,我们直接在主页中点击新建或修改方法,到数据库内部查看修改时间或修改人即可
我们的功能开发一般分为三个阶段
根据需求,我们需要设置分类管理信息
分类信息存放在一张表中,分为为菜品分类和套餐分类,我们用数据库的type属性来区分两种分类
我们来到前端,分别点击菜品分类和套餐分类的创建,F12查看传输数据就会发现:

点击菜品分类,这里不仅传输了我们页面书写的name和sort,还额外传递了type属性表示是菜品分类
因而我们只需要将数据传入即可,不用分心设置是菜品分类还是套餐分类,直接书写代码即可
因为是新的实体类,我们需要重新构造一系列domain,mapper等内容
package com.qiuluo.reggie.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 分类
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//类型 1 菜品分类 2 套餐分类
private Integer type;
//分类名称
private String name;
//顺序
private Integer sort;
//创建时间
@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;
}
package com.qiuluo.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiuluo.reggie.domain.Category;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
package com.qiuluo.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
public interface CategoryService extends IService<Category> {
}
package com.qiuluo.reggie.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.CustomException;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.Setmeal;
import com.qiuluo.reggie.mapper.CategoryMapper;
import com.qiuluo.reggie.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
package com.qiuluo.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
// 自动装配
@Autowired
private CategoryServiceImpl categoryService;
// 查看前端url请求,为post类型,后面没有跟网页设置
@PostMapping
public Result<String> save(@RequestBody Category category){
// 创建时间/人,修改时间/人均已统一设置,我们只需要将数据保存进数据库即可
categoryService.save(category);
return Result.success("新增成功");
}
}
来到主页面,新添后去数据库查看相关信息即可
我们的功能开发一般分为三个阶段
我们需要将数据展现到网页中,同时防止数据过多拥挤,我们采用分页查询的方法给出数据:

我们在之前的员工分类中已经完成了分页插件的创建,所以这次我们只需要刷新页面查看url以及数据即可:

直接实现相关代码即可
我们直接在CategoryController中实现方法即可:
package com.qiuluo.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryServiceImpl categoryService;
/**
* 分页方法
*/
@GetMapping("/page")
public Result<Page> page(int page,int pageSize){
// 创建Page,并载入参数
Page pageImpl = new Page(page,pageSize);
categoryService.page(pageImpl);
// 我们不需要做匹配,但是我们需要按照sort的数值比对来进行排列,所以依旧需要创建LambdaQueryWrapper匹配器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByAsc(Category::getSort);
// 调用分页方法
categoryService.page(pageImpl,queryWrapper);
// 数据返回
return Result.success(pageImpl);
}
}
我们直接打开页面,刷新页面,有数据出现即可
我们的功能开发一般分为三个阶段
我们点击页面后,可以查看后面有一个删除操作,点击后我们会删除该套餐:

但是请注意当当前套餐中有相关菜品时,我们如果删除,那么菜品将无法显示,所以我们还需要设置条件当该套餐中出现菜品时无法删除

我们的套餐中的菜品信息并非存储在套餐数据库中,而是存储在Dish和Setmeal数据表中:

所以我们需要创建这两者的基本信息,并在Category的业务层中修改默认代码,创建一个符合我们要求的方法
首先我们将创建Dish和Setmeal的基本信息,下面不做配置,这里仅作简单罗列(和之前配置完全相同):
接下来我们来介绍代码的正式实现:
package com.qiuluo.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
public interface CategoryService extends IService<Category> {
// 书写我们需要的方法
public Result<String> remove(Long id);
}
package com.qiuluo.reggie.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.CustomException;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.Setmeal;
import com.qiuluo.reggie.mapper.CategoryMapper;
import com.qiuluo.reggie.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
// 我们需要使用到DishServiceImpl,SetmealServiceImpl来查询是否有菜品相连,自动装配即可
@Autowired
private DishServiceImpl dishService;
@Autowired
private SetmealServiceImpl setmealService;
// 实现方法
public Result<String> remove(Long id){
// 判断是否有菜品相连
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
if (count1 > 0){
// 如果有菜品相连,我们先抛出业务异常,这里暂时不实现,我们在后面实现
}
// 判断是否有套餐相连
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if (count2 > 0){
// 如果有套餐相连,我们先抛出业务异常,这里暂时不实现,我们在后面实现
}
// 均无相连,采用父类的根据id删除方法,并返回成功信息
super.removeById(id);
return Result.success("成功删除");
}
}
package com.qiuluo.reggie.common;
/**
* 自定义业务异常类
* 注意:自定义异常都需要继承RuntimeException
*/
public class CustomException extends RuntimeException{
public CustomException(String message){
super(message);
}
}
package com.qiuluo.reggie.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* 全局异常处理
* @ControllerAdvice 来书写需要修改异常的注解类(该类中包含以下注解)
* @ResponseBody 因为返回数据为JSON数据,需要进行格式转换
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理异常
* @ExceptionHandler 来书写需要修改的异常
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if (ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";
return Result.error(msg);
}
return Result.error("未知错误");
}
/**
* 处理自定义异常
* @ExceptionHandler 来书写需要修改的异常
* @return
*/
@ExceptionHandler(CustomException.class)
public Result<String> CustomExceptionHandler(CustomException ex){
// 我们直接获得异常中携带的信息并返回即可
log.error(ex.getMessage());
return Result.error(ex.getMessage());
}
}
package com.qiuluo.reggie.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.CustomException;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.Setmeal;
import com.qiuluo.reggie.mapper.CategoryMapper;
import com.qiuluo.reggie.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishServiceImpl dishService;
@Autowired
private SetmealServiceImpl setmealService;
// 实现方法
public Result<String> remove(Long id){
// 判断是否有菜品相连
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count1 = dishService.count(dishLambdaQueryWrapper);
if (count1 > 0){
// 抛出业务异常
throw new CustomException("已有菜品关联,无法删除!");
}
// 判断是否有套餐相连
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if (count2 > 0){
// 抛出业务异常
throw new CustomException("已有套餐关联,无法删除!");
}
super.removeById(id);
return Result.success("成功删除");
}
}
package com.qiuluo.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryServiceImpl categoryService;
// 这里需要注意:前端传来的id名称为ids(资料中带有的),参数搞错了是无法匹配的
@DeleteMapping
public Result<String> delete(Long ids){
categoryService.remove(ids);
return Result.success("删除成功");
}
}
回到主页面,点击一个自己创建的分类的删除键,分类消失
回到主页面,点击一个系统创建的分类的删除键,分类存在并弹出弹框显示已有关联无法删除
我们的功能开发一般分为三个阶段
我们打开修改界面,点击修改后查看相关url以及参数即可
url如下:

参数如下:

我们会发现修改分类实际是根据id来修改分类,其中传递的参数实际上是一个Category实现类
那么我们直接书写代码即可
我们直接在服务层书写代码:
package com.qiuluo.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryServiceImpl categoryService;
@PutMapping
public Result<String> update(@RequestBody Category category){
categoryService.updateById(category);
return Result.success("修改成功");
}
}
我们直接回到主页面,点击修改,来到后台查看数据实现即可
在这里我们会点出该项目目前容易出错的位置
在公共字段自动填充的部分,我们为了使用ThreadLocal从而创建了相对的工具类
我们的工具类就是为了便捷操作而使用的,我们为了使用相关的参数但同时多次不用创建实体而直接使用工具类
例如我们的ThreadLocal的工具类中:
package com.qiuluo.reggie.common;
// 我们直接创建了实体threadLocal,那么我们调用该实体时就不用多次创建实体
// 同时我们给出了该实体的封装方法并设置为静态方法,那么我们就可以直接调用该工具类的静态方法来实现实体的方法
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();
}
}
我们平时遇到的异常都是产生错误由系统抛出的异常
只有真实的项目中我们才会需要创建自己定义的异常,难点在于异常的创建格式以及将异常加入异常处理器中
首先我们需要创建自定义异常:
package com.qiuluo.reggie.common;
/**
* 自定义业务异常类
* 注意:一定要继承RuntimeException
* 继承RuntimeException之后我们才能直接抛出该异常
*/
public class CustomException extends RuntimeException{
// 内部直接书写一个构造方法,因为我们抛出异常时都是直接new一个新的异常(手动书写)
public CustomException(String message){
// 存储一个简单的反馈信息
super(message);
}
}
再之后我们需要将该异常加入到异常处理器中:
package com.qiuluo.reggie.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* 全局异常处理
* @ControllerAdvice 来书写需要修改异常的注解类(该类中包含以下注解)
* @ResponseBody 因为返回数据为JSON数据,需要进行格式转换
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理自定义异常
* @ExceptionHandler 来书写需要修改的异常
* @return
*/
@ExceptionHandler(CustomException.class)
public Result<String> CustomExceptionHandler(CustomException ex){
// 我们不需要做过多处理,我们只是将大部分需要抛出异常的部分整合起来在这里统一处理
return Result.error(ex.getMessage());
}
}
该篇内容到这里就结束了,希望能为你带来帮助~
该文章属于学习内容,具体参考B站黑马程序员的Java项目实战《瑞吉外卖》
这里附上视频链接:业务开发Day3-01-本章内容介绍_哔哩哔哩_bilibili
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195
我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正
我有两个文本文件,master.txt和926.txt。如果926.txt中有一行不在master.txt中,我想写入一个新文件notinbook.txt。我写了我能想到的最好的东西,但考虑到我是一个糟糕的/新手程序员,它失败了。这是我的东西g=File.new("notinbook.txt","w")File.open("926.txt","r")do|f|while(line=f.gets)x=line.chompifFile.open("master.txt","w")do|h|endwhile(line=h.gets)ifline.chomp!=xputslineendende
我一直在尝试使用nanoc用于生成静态网站。我需要组织一个复杂的排列页面,我想让我的内容保持干燥。包含或合并的概念在nanoc系统中如何运作?我已阅读文档,但似乎找不到我想要的内容。例如:我如何获取两个部分内容项并将它们合并到一个新的内容项中。在staticmatic您可以在您的页面中执行以下操作。=partial('partials/shared/navigation')类似的约定在nanoc中如何运作? 最佳答案 这里是nanoc的作者。在nanoc中,部分是布局。因此,您可以拥有layouts/partials/shared/
我安装了ruby、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom