苍穹外卖技术点

完善登录功能

1 员工表中的密码是明文存储,安全性太低

解决办法

  • 对前端提交的明文密码进行MD5加密后再与数据库中的密码进行对比。
  • 利用SPring框架自带的工具类DigestUtils
password= DigestUtils.md5DigestAsHex(password.getBytes());

2 新增员工时,若录入的用户名已存在,抛出异常没有处理。

分析原因和解决方法

  • 创建员工表时,规定用户名是唯一的。
  • 若录入的用户名已存在,则会抛异常
  • 通过全局异常处理器来统一捕获此问题引发的SQL异常:SQLIntegrityConstraintViolationException
@ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
        //Duplicate entry 'lisi' for key 'employee.idx_username'
        String message = ex.getMessage();
        if(message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username=split[2];
            String msg=username+ MessageConstant.ALREADY_EXIST;
            return Result.error(msg);
        }else {
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

3 如何通过某种方式动态获取当前登录用户的id


解决方案

  • 使用ThreadLocal存储登陆用户的id
  • 封装ThreadLocal为一个工具类,利用get和set方法存值和取值
public class BaseContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

}

4 后端给前端响应的数据格式存在问题(日期格式)

注意:常用方式二

5 删除表数据时,先判断要删除数据是否关联了其他表数据(逻辑外键关系导致)。

原因分析及解决方案

  • 对于分类表,它关联了套餐表和菜品表。关系为:一个菜品分类可能包含多个菜品,一个套餐分类包含多个套餐。具有一对多关系,分类表是1方,而菜品表和套餐表是多的一方。在多的一方存有分类表的外键,以此建立逻辑外键关系。
  • 所以在删除某条分类数据时,要先判断该条分类所关联的菜品和套餐是否存在。
  • 若存在,则抛出业务异常(当下分类下有菜品/套餐),若不存在,则执行删除操作

通用注意事项

1 在修改表数据时,需要更新update_Time,update_User字段。

业务表中的公共字段

出现的问题: 代码冗余,不便于后期维护
1 create_Time,create_User,update_Time,update_Time,
解决办法

  • 自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法。
/**
 * 自定义注解,用于标识某个方法需要进行功能字段填充处理
 */
//表示该注解只能加在方法上
@Target(ElementType.METHOD)
//指定注解的保留时间
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //指定一个属性,用于指定当前数据库操作类型: UPDATE  INSERT
    //可以通过枚举方式指定
    OperationType value();
}

  • 自定义切面类AutoFillAspect,同意拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点:对哪些类的哪些方法进行拦截
     */
    //切入点表达式(对加有自定义AutoFill注解的com.sky.mapper下所有类的所有方法进行拦截)
    @Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {
    }

    /**
     * 前置通知,在通知中进行公共字段填充
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        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, 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) {
            //为两个公共字段赋值
            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注解
 /**
     * 修改分类
     * @param category
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

技术点:枚举、注解、AOP、反射
枚举:

/**
 * 数据库操作类型
 */
public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT

}

事务注解

时机:对于一个业务逻辑,需要涉及多张表的操作,则需在当前业务逻辑方法上加上事务注解,保证原子性。
准备:在启动类上声明@EnableTransactionManagement ,以开启注解方式的事务管理
例子:添加菜品数据时,既要操作菜品表,也要操作口味表。(向菜品表插入1条数据,向口味表插入多条数据)

删除菜品

业务规则
1 可以一次删除一个菜品,也可以批量删除菜品
2 起售中的菜品不能删除
3 被套餐关联的菜品不能删除
4 删除菜品后,关联的口味数据也需要删除掉
注意

  • 在后端controller层接受前端发送的请求参数ids时,可用如下注解@RequestParam进行参数解析,并将前端字符串ids以逗号分隔封装成list集合
/**
     *批量删除菜品
     */
    @DeleteMapping
    @ApiOperation("批量删除菜品")
    public Result delete(@RequestParam List<Long> ids){
        log.info("批量删除菜品:{}",ids);
        dishService.delete(ids);
        return Result.success();
    }
  • 加事务注解

菜品起售、禁售状态更改

注意:
如果是停售操作,还需要将包含当前菜品的套餐也停售

遇到的问题分析总结:在书写多表连接的SQL语句时,表示每个字段时不要漏掉别名.

重点:
若对多表添加了别名,则对每个字段的表示,需要添上相应的表名别名,否则会因多个表拥有相同字段而查询失败。只有在每个字段上标注表名别名,才能具体指代是哪张表的哪个字段。
示例

 <select id="pageQuery" resultType="com.sky.vo.SetmealVO">
        select s.*,c.name as categoryName from setmeal s left outer join category c on s.category_id = c.id
        <where>
            <if test="name!=null">and s.name like concat('%',#{name},'%')</if>
            <if test="status!=null">and s.status =#{status}</if>
            <if test="categoryId!=null">and s.category_id =#{categoryId}</if>
        </where>
        order by s.create_time desc
    </select>

Redis缓存注意点

  • 当处理管理端发送的增删改请求时,如果只对数据库进行相应的增删改操作,则会导致数据库和缓存数据不一致的问题。
    解决办法: 当处理管理端发送的增删改请求时,要同时删除缓存中的数据。
posted @ 2025-03-27 18:25  南宫隐痕  阅读(101)  评论(0)    收藏  举报