苍穹外卖用到的所有注解:
@Target:用来限制注解的使用范围,以防止误用。
常见的 @Target
注解的使用示例:
@Target(ElementType.TYPE)
:该注解只能用于类或接口上。@Target(ElementType.METHOD)
:该注解只能用于方法上。@Target(ElementType.FIELD)
:该注解只能用于字段上。@Target(ElementType.PARAMETER)
:该注解只能用于参数上。
@Retention:
注解可以用于类、方法、字段等元素上,用于指定该元素上的注释应该如何处理。它有一个属性 value
,用于指定注释的保留时间。该属性的取值可以为以下三个值之一:
RetentionPolicy.SOURCE
:注释只在源代码中保留,编译时会被丢弃。这是默认值。RetentionPolicy.CLASS
:注释在编译时被保留,并在运行时被丢弃。RetentionPolicy.RUNTIME
:注释在运行时被保留,可以通过反射等方式进行访问。
@Aspect:
注解用于标记一个类作为切面类,这样的类可以包含一些通知(advice)和方法,这些通知和方法能够定义在何时、何地以及如何执行某些代码。
@Component:
用于将普通Java类声明为Bean的注解,从而不需要手动注册和配置每个Bean。
@Pointcut:
用于定义切点
@Before:
用于标记在执行某个测试方法之前应该执行的代码块。
@RequestBody:
它用于处理来自 HTTP 请求体的 JSON 数据,只能用于处理 POST 和 PUT 请求。
@Transactional:
保证事务的一致性,比如新增菜品时,菜品添加进了数据库,而菜品口味因为异常而没有添加进,处理起来就比较麻烦,加入此注解后,有一个没添加进数据库,全部都不会被添加进数据库
@RequestParam:
是Spring MVC中用于处理HTTP请求参数的重要注解,它可以将请求中的参数值绑定到Java方法参数上,使开发者能够更方便地处理和操作请求数据。
@PathVariable:
用于从请求的URL中提取参数值并传递给处理程序方法的参数。要是/user/id这种,而不是/user?id
公共字段自动填充
使用注解:
@Target,@Retention,@Aspect,@Component,@Pointcut,@Before
流程分析:
1.先创建一个自定义的注解,加入@Target和Retention注解,再调用含有插入和修改的方法
2.再创建一个自动注入类,加入@Aspect和@Component,在方法里用@Pointcut定义切点,并创建一个@Before方法
3.在@Before方法里先获取到当前被拦截的方法上的数据库操作类型,再获取到当前被拦截的方法的参数--实体对象,再准备赋值的数据,最后根据当前不同的操作类型,为对应的属性通过反射来赋值
4.利用上面AOP思想创好自动注入方法后,在Mapper里给需要的新增和修改方法添加我们自己创建的自动注入注解
实际操作:
注解代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
切面切点代码 :
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
@Pointcut("execution(* com.sky.mapper.*.*(..)) &&
@annotation(com.sky.annotation.AutoFill)"
)
public void autoFillPointCut() {
}
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始进行公共字段自动填充...");
//1.获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//2.获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
Object entity = args[0];
//3.准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//4.根据当前不同的操作类型,为对应的属性通过反射来赋值
if (operationType == OperationType.INSERT) {
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity, now);
setCreateUser.invoke(entity, currentId);
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (operationType == OperationType.UPDATE) {
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity, now);
setUpdateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Swagger:
请求路径:localhost:8080/doc.html
使用方式:
1.导入Knife4j的maven坐标
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
2.在配置类中加入Knife4j的相关配置
@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;
}
3.设置静态资源映射,否则接口文档页面无法访问
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
常用注解:
登录接口 :
流程分析:
1.controller层正常操作,参数是自己写的DTO.
2.实现层首先需要把账号和密码取出来,然后通过名字去查数据库
3.先判断数据库中是否有这个账号,再判断密码是否正确,最后判断账号状态是否处于禁用
4,登录成功生成jwt令牌
用到的注解:@RequestBody
实际操作:
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);
}
password = DigestUtils.md5DigestAsHex(password.getBytes());
//密码比对
//进行md5加密后,然后再进行比对
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;
}
退出接口:
流程分析:
直接连上接口后,return就行
用到的注解:无
实际操作:
@PostMapping("/logout")
public Result<String> logout() {
return Result.success();
}
新增接口:
普通新增:
流程分析:
1.先创建一个DTO,因为前端只传过来几个值,直接用Employee会增大数据的传输量
2.再在实现类里面创建sava方法,在方法里new一个Employee,把DTO的值拷贝过去,并设置好状态和密码(记得加密),因为放进数据库需要Employee里面的完整数据,所以不能直接传DTO进去
3.调用Mapper层的insert方法,要自己写insert语句
用到的注解:@RequestBody
实际操作:
实现类代码:
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
employee.setStatus(StatusConstant.ENABLE);
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
// employee.setCreateTime(LocalDateTime.now());
// employee.setUpdateTime(LocalDateTime.now());
// employee.setCreateUser(BaseContext.getCurrentId());
// employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.insert(employee);
}
多表新增
注意:多表操作需要加上事务注解
流程分析:
(1).和普通新增一样的操作,不过在实现类里还需要用DTO把没有的那张表get出来,并进行foeach来给每一个数据添加id(这个id在第一个表的sql语句中添加useGeneratedKeys为ture,keyProperty为id,才可以取得).
(2).调用Mapper层的insert方法,要自己写insert语句,不同表是不同的Mapper层
用到的注解:@Transactional,@RequestBody
实际操作:
实现层代码
@Transactional
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
dishMapper.insert(dish);
//这个id要在sql语句上加useGeneratedKeys,keyProperty才能获取到
Long dishId = dish.getId();
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0 ){
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
dishFlavorMapper.insertBatch(flavors);
}
}
sql语句
单条数据
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into sky_take_out.dish (name, category_id, price, image, description, create_time, update_time, create_user, update_user,status)
values
(#{name},#{categoryId},#{price},#{image},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
</insert>
多条数据
<insert id="insertBatch">
insert into sky_take_out.dish_flavor(dish_id, name, value) VALUES
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
删除接口:
流程分析:
(1).用List<Long>来当参数接收多个id(注意不是/是?所以不能用@Requestbody)
(2).用增强for遍历每一个id.把本类方法查询数据库,判断状态是否是起售状态,是报异常
(3).在需要判断是否跟其他表有关联时,需创建一个新的Mapper,并在这个Mapper的xml里用foreach遍历id查询(collection是要遍历的集合,item是集合中的每一个元素,separator是元素间的符号,open是集合开始的符号,close是集合结束的符号).判断查询出来的结果是否为空以及是否大于0,是报异常
(4).用增强for遍历每一个id并调用本类以及与其关联的表的删除方法,删除菜品以及关联口味
(5).最后加上事务管理注解
用到的注解:@RequestParam
实际操作:
实现层代码:
@Transactional
public void removeById(List<Long> ids) {
//判断状态
for (Long id : ids){
Dish dish = dishMapper.getById(id);
if (dish.getStatus()== StatusConstant.ENABLE){
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
}
//判断是否关联
List<Long> setmealIds = setmealDishMapper.dishBySetmealId(ids);
if (setmealIds != null && setmealIds.size()>0){
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
//删除菜品以及关联口味
for (Long id :ids){
dishMapper.removeById(id);
dishFlavorMapper.removeById(id);
}
查询是否有关联的xml代码:
<select id="dishBySetmealId" resultType="java.lang.Long">
select setmeal_id from sky_take_out.setmeal_dish where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</select>
修改接口:
普通修改
流程分析:
1.首先明确是页面传一个值还是一堆值过来,一个值的参数就是那个值加当前id,一堆值就需要创一个适合的DTO来当参数
2.都需要new一个和数据库表对应的实体类,因为最后需要sql语句来操作这张表
3.一个值的直接set值,setId,一堆值的先把DTO的数据拷贝到new出来的实体类中,再在实现层setDTO没有的数据,最后统一在mapper层进行sql语句修改
4.sql语句复杂的需要在xml文件中写
用到的注解:@RequestBody, @PathVariable
实际操作:
一堆值实现类代码
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
employeeMapper.update(employee);
}
多表修改
流程分析:
1.参数是DTO来接受前端多表数据,在实现层我们需要先创一个包含本表的实体类,把DTO中的值拷贝过去,用新的实体类调用本类方法修改普通数据
2.再删除原本数据的另一张表的数据,例如菜品删除口味数据(用dishFlavorMapper的批量删除方法)
3.最后像多表新增一样把我们修改的口味数据添加上去,给每一个口味设上id,用dishFlavorMapper的批量新增方法把数据新增上去
用到的注解:@RequestBody
实际操作:
public void update(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish);
dishMapper.update(dish);
//删除原先口味表数据
dishFlavorMapper.removeById(dishDTO.getId());
//添加我们新修改的口味表数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishDTO.getId());
});
dishFlavorMapper.insertBatch(flavors);
}
}
查询接口:
流程分析:
1.先判断是回显数据还是分页查询,回显直接用id查询就行
2.回显需要创一个VO,包含原本实体类所有信息以及要多表回显的实体类,在实现类里查询本类信息(写sql语句),再查询另一张表的信息(在另一个Mapper里写sql语句),最后把本类信息拷贝到VO中,并给VO的新值设置好口味信息
3.分页查询先创建一个有页码,每页记录数以及搜索姓名的DTO当参数(分页查询不需要@Requesbody),然后用PageHelper.startPage把页码和每页记录数传过去,并调用mapper层方法,sql是在xml里面写的带有搜索姓名的查询全部语句
4.如果是多表分页查询,那就是在实现层里的Page对象是新创建的VO,VO包含了另一张表的数据
5.最后把page里面的total和result拿出来并传回给controller层
用到的注解:@PathVariable
实际操作:
回显查询实现层代码
@Override
public DishVO getById(Long id) {
//查询原本信息
Dish byId = dishMapper.getById(id);
//查询口味信息
List<DishFlavor> dishFlavors = dishFlavorMapper.getDishFlavor(id);
//把原本信息拷贝到有口味信息的VO上
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(byId,dishVO);
//设置好口味信息
dishVO.setFlavors(dishFlavors);
return dishVO;
}
单表分页查询实现层代码
public PageResult list(EmployeePageQueryDTO queryDTO) {
PageHelper.startPage(queryDTO.getPage(), queryDTO.getPageSize());
Page<Employee> page = employeeMapper.list(queryDTO);
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total, records);
}
多表分页查询实现层代码
@Override
public PageResult page(DishPageQueryDTO queryDTO) {
PageHelper.startPage(queryDTO.getPage(), queryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageInfo(queryDTO);
return new PageResult(page.getTotal(), page.getResult());
}
xml层sql代码
<select id="list" 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>