技术点 枚举 如果通过switch来一个个查找的话,会因为查找的信息没有而发生错误,但是通过枚举的话就可以判断查找的值是否存在,不存在的话就会方法报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public enum testEnum { MONDAY("星期一" ), TUESDAY("星期二" ), WEDNESDAY("星期三" ), THURSDAY("星期四" ), FRIDAY("星期五" ), SATURDAY("星期六" ), SUNDAY("星期日" ); private String desc; DayEnum(String desc){ this .desc = desc; } public String getDesc () { return desc; } }
创建的要是enum枚举类
在java类中用switch加上变量.getDesc()来获取
自定义注解 类型为@Interface
@Target - 指定使用目标
1 2 3 4 5 @Target(ElementType.METHOD) @Target(ElementType.TYPE) @Target(ElementType.PARAMETER) @Target({ElementType.METHOD, ElementType.TYPE})
@Retention - 指定保留策略
1 2 3 @Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.CLASS) @Retention(RetentionPolicy.RUNTIME)
其他元注解
1 2 3 @Documented @Inherited @Repeatable
注解属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public @interface MyAnnotation { int value () default 0 ; String name () default "" ; Color color () default Color.RED; String[] tags() default {}; NestedAnnotation nested () default @NestedAnnotation ; Class<?> clazz() default Object.class; }
反射
反射就是返回类里面的成员变量,构造方法,成员方法等等
获取class对象 Class.forName("全类名") (最常用)
全类名 = 包名 + 类名 (直接在类中右键类名复制就可以了)
Class clazz = Class.forname(“全类名”);
类名.class(更多的当成参数传递)
Class clazz = Student.class;
对象.getClass()(当已经有了这个类的对象时才能使用)
Student s = new Student();
Class clazz = s.getClass();
获取构造方法Constructor Class类中用于获取构造方法的方法
Constructor<?>[] getConstructors(): 返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors(): 返回所有构造方法对象的数组
Constructor<T> getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象(如果是空就返回空参的,要返回其他的就要加上对应参数类型+.class)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):返回单个构造方法对象
Constructor类中用于创建对象的方法
T newInstance(Object... initargs):根据指定的构造方法创建对象
setAccessible(boolean flag):设置为true,表示取消访问检查
Constructor类中用于操作构造方法的方法 :
T newInstance(Object... initargs):根据指定的构造方法创建对象实例。
void setAccessible(boolean flag):设置为true时,取消访问检查(用于访问私有构造方法)。
int getModifiers():获取构造方法的修饰符。
String getName():获取构造方法的名字(通常返回类名)。
Class<?>[] getParameterTypes():获取构造方法的形参类型数组。
成员变量(字段)Field Class类中用于获取成员变量的方法
Field[] getFields(): 返回所有公共成员变量对象的数组
Field[] getDeclaredFields(): 返回所有成员变量对象的数组
Field getField(String name):返回单个公共成员变量对象
Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于操作成员变量的方法
void set(Object obj, Object value):为指定对象的此成员变量赋值
Object get(Object obj):获取指定对象的此成员变量的值
Field类中用于操作字段的方法 :
int getModifiers():获取字段的修饰符(如public、private等)。
String getName():获取字段的名字。
Class<?> getType():获取字段的类型。
Object get(Object obj):获取指定对象中该字段的值。
void set(Object obj, Object value):设置指定对象中该字段的值。
void setAccessible(boolean flag):设置为true时,取消访问检查(用于访问私有字段)。
成员方法Method Class类中用于获取成员方法的方法 :
Method[] getMethods():返回所有公共成员方法对象的数组(包括继承的公共方法)。
Method[] getDeclaredMethods():返回所有成员方法对象的数组(包括私有方法,但不包括继承的方法)。
Method getMethod(String name, Class<?>... parameterTypes):返回指定名称和参数类型的公共方法对象。
Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回指定名称和参数类型的方法对象(包括私有方法)。
Method类中用于操作成员方法的方法 :
int getModifiers():获取方法的修饰符。
String getName():获取方法的名字。
Class<?>[] getParameterTypes():获取方法的形参类型数组。
Class<?> getReturnType():获取方法的返回值类型。
Class<?>[] getExceptionTypes():获取方法抛出的异常类型数组。
Annotation[] getAnnotations():获取方法上的注解数组。
Object invoke(Object obj, Object... args):运行(调用)指定对象上的方法,并返回结果。
invoke例子:
Student s = new Student();
m.invoke(s,“kfc”)
参数一:方法的调用者
参数二:调用方法时传递的实际参数
AOP 面向切面编程,也可理解为面向特定方法编程
步骤
1 2 3 4 5 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>3.5 .6 </version> </dependency>
1 2 3 @Aspect @Component public class ...
1 2 3 4 5 6 7 8 9 10 11 @Aspect @Component public class record111 { @Around("execution(* *..*.*(..))") public Object record111 (ProceedingJoinPoint pjp) throws Throwable { Object result = pjp.proceed(); return result; } }
result表示原始代码,在其上下部分可添加代码
在@Around中设定范围
核心概念
底层就是通过动态代理
先生成一个和目标对象同一接口的代理对象,复制方法,再把aop的代码放进去,最后在controller中注入service的就是代理对象,引用的方法也是代理对象中的
这样做就可以不污染源代码,还能实现各种操作,比如记录代码运行时间等等
通知类型
通知顺序
切入点表达式
主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
访问修饰符 :可省略(例如:public、protected)
包名.类名 :可省略(但不推荐)
throws 异常 :可省略(此处指方法声明上抛出的异常,并非实际运行时的异常)
\* :表示任意单个符号 ,可用于通配返回值、包名、类名、方法名,或任意类型的一个参数,也可用于通配包、类、方法名的一部分。
示例 :execution(* com.*.service.*.update*(*))
.. :表示任意多个连续的符号 ,可用于通配任意层级的包,或任意类型、任意个数的参数。
示例 :execution(* com.itheima.DeptService.*(..))
用于识别标识有特定注解的方法
这个其实是自定义注解
LogOpration需要自己创建一个自定义注解,方法上的@LogOpration就是调用的自定义注解
连接点 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
对于@Around通知,获取连接点信息只能使用 ProceedingJoinPoint。
对于其它四种通知,获取连接点信息只能使用JoinPoint,它是 ProceedingJoinPoint的父类型。
公共字段自动填充 因为在搭建项目的时候有很多重复的操作,且维护起来不方便,因此就要根据以上复习的技术点来搭建公共字段
枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.sky.enumeration;public enum OperationType { UPDATE, INSERT }
自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { OperationType value () ; }
AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 @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("开始进行数据填充" ); } }
定义切入点autoFillPointCut的范围,再把切入点给autoFill来进行操作
在mapper对应的方法上加上以下代码即可
1 2 @AutoFill(value = Ope@AutoFill(value = OperationType.INSERT) @AutoFill(value = OperationType.INSERT)rationType.UPDATE)
补充AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class);OperationType operationType = autoFill.value();
1 2 3 4 5 6 Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0 ){ return ; } Object object = args[0 ];
1 2 LocalDateTime now = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 if (operationType == OperationType.INSERT){ try { Method setUpdateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setCreateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); Method setCreateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); setCreateTime.invoke(object,now); setUpdateTime.invoke(object,now); setCreateUser.invoke(object,currentId); setUpdateUser.invoke(object,currentId); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException (e); } }else if (operationType == OperationType.UPDATE){ Method setUpdateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setUpdateTime.invoke(object,now); setUpdateUser.invoke(object,currentId); } }
菜品功能 新增菜品
先在application.yml中配置阿里云oss
1 2 3 4 5 alioss: endpoint: oss-cn-beijing.aliyuncs.com access-key-id: LTAI5tEnxippfj9dn2iynJBe access-key-secret: qY7L9R4516VLV8WjqXpzvYU89q9phy bucket-name: sky-take-out
根据接口文档的要求创建新的controller来进行文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Autowired private AliOssUtil aliOssUtil; public Result<String> upload (MultipartFile file) throws IOException { log.info("文件上传:{}" ,file); String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf("." )); String objectName = UUID.randomUUID().toString() + extension; aliOssUtil.upload(file.getBytes(),objectName); return Result.success(objectName); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Autowired private AliOssUtil aliOssUtil;@PostMapping("/upload") public Result<String> upload (MultipartFile file) throws IOException { log.info("文件上传:{}" , file.getOriginalFilename()); try { if (file.isEmpty()) { return Result.error("文件不能为空" ); } String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf("." )); String objectName = UUID.randomUUID().toString() + extension; String fileUrl = aliOssUtil.upload(file.getBytes(), objectName); log.info("文件上传成功,URL:{}" , fileUrl); return Result.success(fileUrl); } catch (Exception e) { log.error("文件上传失败" , e); return Result.error("上传失败:" + e.getMessage()); } }
controller
1 2 3 4 5 public Result addDish (@RequestBody DishDTO dishDTO) { log.info("新增菜品:{}" ,dishDTO); dishService.addDish(dishDTO); return Result.success(); }
service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override @Transactional public void addDish (DishDTO dishDTO) { Dish dish = new Dish (); BeanUtils.copyProperties(dishDTO,dish); dishMapper.addDish(dish); Long dishId = dish.getId(); List<DishFlavor> flavors = dishDTO.getFlavors(); if (flavors != null && flavors.size() > 0 ){ flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishId)); dishF.add(flavors); } }
mapper
1 2 3 4 5 6 7 8 <!-- 因为在菜品口味的时候需要菜品的id,所以这里要返回id--> <insert id="addDish" useGeneratedKeys="true" keyProperty="id" > insert into dish (name, category_id, description, price, image, status, create_time, update_time, create_user, update_user) values (#{name}, #{categoryId}, #{description},#{price}, #{image}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}) </insert>
1 2 3 4 5 6 <insert id="add" > insert into dish_flavor (name, dish_id,value) values <foreach collection="flavors" item="df" separator="," > (#{df.name}, #{df.dishId}, #{df.value}) </foreach> </insert>
页面查询 其他部分与之前都大差不差,只有一些小区别
service
1 Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDto);
这里的Page的泛型用了DishVO,因为相比Dish,DishVO中有变量category
mapper
1 2 3 4 5 6 7 8 9 10 11 12 13 select d.*,c.name as categoryName from dish d left outer join category c on d.category_id = c.id <where> <if test="name != null and name != ''" > and d.name like '%${name}%' </if > <if test="categoryId != null" > and d.category_id = #{categoryId} </if > <if test="status != null" > and d.status = #{status} </if > </where> order by d.update_time desc
删除菜品 业务规则
可以一次或批量删除菜品
起售中的菜品不能删除
被套餐关联的菜品不能删除
删除菜品后,关联的口味数据也要删除
service
这里的ListLong<Long>是将前端传回来的String通过RequestParam把ids根据逗号进行分割并把元素添加到list当中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Transactional @Override public void delete (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 = dishMapper.getSetmealIdsByDishIds(ids); if (setmealIds != null && setmealIds.size() > 0 ){ throw new DeletionNotAllowedException (MessageConstant.DISH_BE_RELATED_BY_SETMEAL); } dishMapper.delete(ids); dishF.deleteByDishId(ids); }
mapper
1 2 3 4 5 6 <delete id="delete" > delete from dish where id in <foreach collection="ids" item="id" separator="," open="(" close=")" > #{id} </foreach> </delete>
删除口味也大同小异
修改菜品 接口设计
根据id查询菜品
根据类型查询分类(已实现)
文件上传(已实现)
修改菜品
根据id查询菜品 service
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public DishVO searchById (Long id) { Dish byId = dishMapper.getById(id); List<DishFlavor> dishFlavors = dishF.getByDishId(id); DishVO dishVO = new DishVO (); BeanUtils.copyProperties(byId,dishVO); dishVO.setFlavors(dishFlavors); return dishVO; }
修改菜品 service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void update (DishDTO dishDTO) { Dish dish = new Dish (); BeanUtils.copyProperties(dishDTO,dish); dishMapper.update(dish); dishF.deleteByDishId(Collections.singletonList(dishDTO.getId())); List<DishFlavor> flavors = dishDTO.getFlavors(); if (flavors != null && flavors.size() > 0 ){ flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishDTO.getId())); dishF.add(flavors); } }
与之前都差不多