前言
以后这个项目所涉及的比较重要的技术点我都会将其单独写一篇文章,如果将这些技术点写在项目文章里面的话,不仅会不够细致,而且还会使项目结构看起来很乱,不够规整。这个项目文章以及项目所涉及的技术点我都放在了一个专栏里面,就是该项目的专栏。有关技术点的链接引用的文章都是我自己写的与这个项目相结合又对其扩展的。
公共字段自动填充
对于公共功能的实现,我们首先可能会想到 AOP
, 但如果你使用的是 mybatis-plus 会有效的简化这一功能的实现。
该项目使用的是 mybatis ,所以接下来我们会使用 AOP,java注解,反射 来完成 这一功能的实现。
有关这三者的基本使用可以参照以下文章:
AOP的基本使用;
java注解,在这片文章中包含了反射。
这两篇文章也是我写的文章,我将涉及到的技术点给提了出来,以后这个项目所涉及的比较重要的技术点我都会将其但多写一篇文章,如果将这些技术点写在项目文章里面的话,不仅会不够细致,而且还会使项目结构看起来很乱,不够规整。这个项目文章以及项目所涉及的技术点我都放在了一个专栏里面
在本项目中,该公共字段自动填充用于 增加 和 修改 数据
代码实例:
//定义枚举,用于指定是 增加 还是 修改 操作
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
//定义Java注解,作用与增加与修改数据的方法,
//通过反射获取注解,判断是哪种方法,从而实现相应的填充功能
//指定注解的作用范围
@Target(ElementType.METHOD)
//保留策略
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//定义注解成员,为枚举类型
OperationType value();
}
/**
* 自动填充注解的切面
*/
@Aspect
@Component // 需要将该类加入到IOC容器中
@Slf4j
public class AutoFillAspect {
//定义切点,这里使用注解的方式来定义切点,根据需要自行设置
@Pointcut("execution(public * com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointcut() {}
// 定义前置通知,使用在方法aspect()上注册的切入点
@Before("autoFillPointcut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始自动填充。。。");
//通过反射获取目标对象关联的注解值
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class);
OperationType value = autoFill.value();
//获取参数--实体对象
Object[] args = joinPoint.getArgs();
Object target = args[0];
if (target == null){
return;
}
//准备填充的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据填充类型进行字段填充
if (value == OperationType.INSERT){
try {
//通过反射填充创建人、创建时间,修改人,修改时间
Method setCreateTime = target.getClass().getMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = target.getClass().getMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = target.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = target.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//设置值
setUpdateUser.invoke(target, currentId);
setUpdateTime.invoke(target, now);
setCreateTime.invoke(target, now);
setCreateUser.invoke(target, currentId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (value == OperationType.UPDATE){
try {
//填充更新人、更新时间
Method setUpdateTime = target.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = target.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//设置值
setUpdateUser.invoke(target, currentId);
setUpdateTime.invoke(target, now);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 公共字段自动填充相关常量
*/
public class AutoFillConstant {
/**
* 实体类中的方法名称
*/
public static final String SET_CREATE_TIME = "setCreateTime";
public static final String SET_UPDATE_TIME = "setUpdateTime";
public static final String SET_CREATE_USER = "setCreateUser";
public static final String SET_UPDATE_USER = "setUpdateUser";
}
/**
* 添加员工
* @param employee
*/
@AutoFill(OperationType.INSERT)
@Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user)" +
"values " +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
void insert(Employee employee);
/**
* 修改用户信息
* @param employee
*/
@AutoFill(OperationType.UPDATE)
void update(Employee employee);
定义切入点时,不仅使用 execution 去定位方法,而且还要满足添加了 @AutoFill 注解的方法才能实现公共共字段填充功能。
文件的上传
这里的文件主要指 图片。
该功能的实现使用了阿里的:对象存储OSS
技术。
可见文章对象存储OSS
这部分的代码示例:
/**
* 通用Controller
*/
@Slf4j
@RestController
@RequestMapping("/admin/common")
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) {
log.info("文件上传: {}",file);
//todo 使用阿里对象存储OSS服务进行文件的储存,因为是练习项目这里就不开通该服务,将会在一篇文章中介绍该技术
{
//将文件上传到AliOss,阿里对象存储服务中
try {
//获取文件名
String originalFilename = file.getOriginalFilename();
//获取文件后缀
String substring = originalFilename.substring(originalFilename.lastIndexOf("."));
//生成新的文件名,两种方式
//String fileName = System.currentTimeMillis() + substring;
//使用uuid生成新的文件名
String objectName = UUID.randomUUID().toString() + substring;
//上传文件到阿里云OSS
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败: {}", e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
}
将上传文件的部分封装成了工具类,在上述链接文章中。
MultipartFile 是 spring 提供的接收文件的类
菜品分页查询
业务规则
- 根据页码展示菜品信息
- 每页展示10条数据
- 分页查询时可以根据需要输入的菜品名称,菜品分类,菜品状态进行查询
注意:返回给前端数据明显是dish菜品数据,但是在接口设计中,返回给前端的数据不仅含有dish数据还有菜品所在分类的分类名称categoryName,而dish表中没有categoryName字段,所以sql语句是 多表查询
分页查询采用pageHelper插件pageHelper
这里主要展示sql语句的编写:
<select id="pageQuery" resultType="com.sky.vo.DishVO">
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 concat('%',#{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
</select>
select d.*,c.name
as categoryName from dish d
left outer join category c
on d.category_id = c.id
d dish表的别名,c category表的别名,d.* dish表的所有字段,c.name category表的名称
删除菜品
业务规则
- 可以一次删除一个菜品,也可以批量删除
- 起售中的菜品不能删除
- 被套餐关联的菜品不能删除
- 删除菜品后,关联的口味数据也需要删除掉
一个功能中设计多张表。
前端传递数据是以菜品id组成的字符串,如:“1,2,3,4”,可用 @RequestParam 注解将其分装为List集合
/**
* 批量删除菜品
* @param ids
* @return
*/
@ApiOperation("批量删除菜品")
@DeleteMapping
public Result delete(@RequestParam List<Long> ids){ //String ids
log.info("批量删除菜品:{}",ids);
dishService.deleteBatch(ids);
return null;
}
service层:
/**
* 根据id批量删除菜品信息和对应的口味信息
* @param ids
*/
@Transactional
public void deleteBatch(List<Long> ids) {
//判断菜品是否能够被删除---菜品是否存在起售状态
ids.forEach(id -> {
Dish dish = dishMapper.getDishById(id);
if (dish.getStatus() == StatusConstant.ENABLE){
//菜品存在起售状态,不能删除
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
});
//判断菜品是否能够被删除---菜品是否与套餐相关联
//查询关联表,根据dishId查询setmealId。 select setmeal_id from setmeal_dish where dish_id in (1,2,3)
List<Long> setmealIds = setmealDishMapper.getSetmealByDishId(ids);
if (setmealIds != null && setmealIds.size() > 0){
//菜品与套餐关联,不能删除
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
//删除菜品信息
/*for (Long id : ids) {
dishMapper.deleteById(id);
//删除菜品对应的口味信息
dishFlavorMapper.deleteByDishId(id);
}*/
//删除菜品,口味优化 ---若数据很多将会对数据库造成压力---批量删除
//批量删除菜品 delete from dish where id in (1,2,3)
dishMapper.deleteByIds(ids);
//批量删除口味 delete from dish_flavor where id in (1,2,3)
dishFlavorMapper.deleteByDishIds(ids);
}
注意开启事务管理,MySQL的默认事务隔离级别是 读可重复
为减轻数据库压力,采用的是批量删除sql语句
//dish表
<delete id="deleteByIds">
delete from dish where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
//dish_flavor表
<delete id="deleteByDishIds">
delete from dish_flavor where dish_id in
<foreach collection="dishIds" item="dishId" separator="," close=")" open="(">
#{dishId}
</foreach>
</delete>
未完。
。。。。。。。。