一、公共字段自动填充
业务表中的公共字段:
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint |
问题:代码冗余,不便于后期维护。
解决方法:(枚举、注解、AOP、反射)
- 自定义注解AutoFill,用于表示需要进行公共字段自动填充的方法;
package com.sky.enumeration; /** * 数据库操作类型 */ public enum OperationType { /** * 更新操作 */ UPDATE, /** * 插入操作 */ INSERT }
package com.sky.annotation; import com.sky.enumeration.OperationType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理 */ @Target(ElementType.METHOD) // 注解作用范围 @Retention(RetentionPolicy.RUNTIME) // 注解生效时间 public @interface AutoFill { // 数据库操作类型:UPDATE INSERT OperationType value(); }
- 自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射位公共字段赋值;
package com.sky.aspect; import com.sky.annotation.AutoFill; import com.sky.constant.AutoFillConstant; import com.sky.context.BaseContext; import com.sky.enumeration.OperationType; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.time.LocalDate; import java.time.LocalDateTime; /** * 自定义切面,实现公共字段自动填充处理逻辑 */ @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) throws NoSuchMethodException { log.info("开始进行公共字段自动填充……"); // 获取到当前被拦截的方法上的数据库操作类型 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 方法签名对象 AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);// 获取方法上的注解对象 OperationType operationType = autoFill.value(); // 获得数据库操作类型 // 获取到当前被拦截的方法的参数——实体对象 Object[] args = joinPoint.getArgs(); if(args == null || args.length == 0) { return; } Object entity = args[0]; // 约定实体对象放在参数的第一位 // 准备赋值的数据 LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); // 根据当前不同的操作类型,位对应的属性通过反射来赋值 if(operationType == OperationType.INSERT) { try{ // 为四个公共字段赋值 Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDate.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) { // 为两个公共字段赋值 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(); } } } }
- 在Mapper的方法上加上AutoFill注解。
有关AOP的介绍:AOP基础-CSDN博客package com.sky.mapper; import com.github.pagehelper.Page; import com.sky.annotation.AutoFill; import com.sky.dto.EmployeePageQueryDTO; import com.sky.entity.Employee; import com.sky.enumeration.OperationType; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface EmployeeMapper { /** * 根据用户名查询员工 * @param username * @return */ @Select("select * from employee where username = #{username}") Employee getByUsername(String username); /** * 插入员工数据 * @param employee */ @Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) " + "values" + "(#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})") @AutoFill(value = OperationType.INSERT) void insert(Employee employee); /** * 员工的分页查询 * @param employeePageQueryDTO * @return */ Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO); /** * 根据逐渐动态修改属性 * @param employee */ @AutoFill(value = OperationType.UPDATE) void update(Employee employee); /** * 根据id查询员工信息 * @param id * @return */ @Select("select * from employee where id = #{id}") Employee getById(Long id); }
二、阿里云OSS
1. 注册阿里云账号,并完成实名认证
2. 充值(可以不用做)
3. 开通OSS
登录阿里云官网,点击右上角的控制台。
将鼠标移至产品,找到并单击对象存储OSS,找到OSS产品详情页面。在OSS产品详情页中的单击立即开通。
开通服务后,在OSS产品详情页面单机管理控制台直接进入OSS管理控制台界面,然年后单击左侧的对象存储OSS菜单进入OSS管理控制台页面。
4. 创建存储空间Bucket
新建Bucket,名为xxx,读写权限为 公共读
5. OSS快速入门
(1)创建测试工程,引入依赖
<!--阿里云OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.sdk.oss}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb-api}</version>
</dependency>
(2)新建类
package com.sky.utils;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
/**
* 文件上传
*
* @param bytes
* @param objectName
* @return
*/
public String upload(byte[] bytes, String objectName) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 创建PutObject请求。
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
//文件访问路径规则 https://BucketName.Endpoint/ObjectName
StringBuilder stringBuilder = new StringBuilder("https://");
stringBuilder
.append(bucketName)
.append(".")
.append(endpoint)
.append("/")
.append(objectName);
log.info("文件上传到:{}", stringBuilder.toString());
return stringBuilder.toString();
}
}
6. 获取AccessKeyId,写到配置文件中。
sky:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
host: localhost
port: 3306
database: sky_take_out
username: root
password: "123456"
alioss:
endpoint: ******
access-key-id: ******
access-key-secret: ******
bucket-name: ******
#填入自己的access-key
7. 使用文件上传功能
package com.sky.controller.admin;
import com.sky.constant.MessageConstant;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file) {
log.info("文件上传:{}", file);
try {
// 原始文件名
String originalFilename = file.getOriginalFilename();
// 截取原始文件名的后缀
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
// UUID 构建新文件名称
String objectName = UUID.randomUUID().toString() + extension;
// 文件的请求路径
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败:{}", e);
// throw new RuntimeException(e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
三、批量删除菜品的优化代码存在的问题
解决:SQL语句了少了个 "in"
四、注意点
1. 在DishController中update()方法的注解不要写成@PostMapping,注意是@PutMapping。
/**
* 修改菜品
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
log.info("修改菜品:{}", dishDTO);
dishService.updateWithFlavor(dishDTO);
return Result.success();
}
五、菜品起售停售功能实现
1. DishController
/**
* 菜品起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("菜品起售停售")
public Result<String> startOrStop(@PathVariable Integer status, Long id) {
dishService.startOrStop(status, id);
return Result.success();
}
2. DishService
/**
* 菜品起售停售
* @param status
* @param id
* @return
*/
void startOrStop(Integer status, Long id);
3. DishServiceImpl
/**
* 菜品起售停售
* @param status
* @param id
* @return
*/
@Override
public void startOrStop(Integer status, Long id) {
Dish dish = Dish.builder()
.id(id)
.status(status)
.build();
dishMapper.update(dish);
if(status == StatusConstant.DISABLE) {
// 如果是停售操作,还需要将包含当前菜品的套餐也停售
List<Long> dishIds = new ArrayList<>();
dishIds.add(id);
// sql: select setmeal_id from setmeal_dish where dish_id in (?, ?, ?)
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);
if(setmealIds != null && setmealIds.size() > 0) {
for (Long setmealId : setmealIds) {
Setmeal setmeal = Setmeal.builder()
.id(setmealId)
.status(StatusConstant.DISABLE)
.build();
setmealMapper.update(setmeal);
}
}
}
}
4. SetmealMapper
/**
* 根据id修改套餐
* @param setmeal
*/
@AutoFill(OperationType.UPDATE)
void update(Setmeal setmeal);
5. SetmealMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealMapper">
<update id="update" parameterType="Setmeal">
update setmeal
<set>
<if test="name != null">
name = #{name},
</if>
<if test="categoryId != null">
category_id = #{categoryId},
</if>
<if test="price != null">
price = #{price},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="description != null">
description = #{description},
</if>
<if test="image != null">
image = #{image},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="updateUser != null">
update_user = #{updateUser}
</if>
</set>
where id = #{id}
</update>
</mapper>