5--SpringBoot项目中菜品管理 详解(一)

news2024/11/14 9:07:44

目录

公共字段自动填充

问题分析

实现思路

代码开发

步骤一

步骤二

功能测试

新增菜品

需求分析和设计

代码开发

文件上传接口

功能测试


公共字段自动填充

问题分析

后台系统的员工管理功能菜品分类功能的开发,在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段。

需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

答案是可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

实现思路

在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

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

代码开发

步骤一

自定义注解 AutoFill

进入到sky-server模块,创建com.sky.annotation包。

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
    //枚举提前定义在sky-commom中
    OperationType value();
}

Java注解基础概念总结-CSDN博客

自定义注解之运行时注解(RetentionPolicy.RUNTIME)-CSDN博客

步骤二

自定义切面 AutoFillAspect

在sky-server模块,创建com.sky.aspect包。

package com.sky.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//自定义切面,实现公共字段自动填充处理逻辑
@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("开始进行公共字段自动填充");
    }

}
 //插入员工数据
    @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);
  //根据主键动态修改属性
    @AutoFill(value = OperationType.UPDATE)
    void update(Employee employee);

完善自定义切面 AutoFillAspect 的 autoFill 方法

//自定义切面,实现公共字段自动填充处理逻辑
@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("开始进行公共字段自动填充");

        //获取到当前被拦截的方法上的数据库操作类型
        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];//获取实体对象,默认实体对象放在第一位
        //用Object来接收参数,因为不确定实体类对象具体是哪一个

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射赋值
        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) {
                throw new RuntimeException(e);
            }
        } 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) {
                throw new RuntimeException(e);
            }
        }

    }

}

在文件sky-common--AutoFillConstant中提前定义常量,使用常量AutoFillConstant.SET_CREATE_TIME代表字段setCreateTime,可以防止书写错误,同时方便后期修改完善

在Mapper接口的方法上加入 AutoFill 注解

CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作

@Mapper
public interface CategoryMapper {

    /**
     * 插入数据
     * @param category
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Category category);

   

    /**
     * 根据id修改分类
     * @param category
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

    
}

同时,将业务层为公共字段赋值的代码注释掉。

1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。

2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。

功能测试

新增菜品

需求分析和设计

业务规则:

  • 菜品名称必须是唯一的

  • 菜品必须属于某个分类下,不能单独存在

  • 新增菜品时可以根据情况选择菜品的口味

  • 每个菜品必须对应一张图片

接口设计:

  • 根据类型查询分类(已完成)

  • 文件上传

  • 新增菜品

因为在新增菜品时,需要上传菜品对应的图片(文件),包括后绪其它功能也会使用到文件上传,故要实现通用的文件上传接口。

文件上传,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发抖音、发朋友圈都用到了文件上传功能。

实现文件上传服务,需要有存储的支持,那么我们的解决方案将以下几种:

  1. 直接将图片保存到服务的硬盘(springmvc中的文件上传)

    1. 优点:开发便捷,成本低

    2. 缺点:扩容困难

  2. 使用分布式文件系统进行存储

    1. 优点:容易实现扩容

    2. 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS,MinIO)

  3. 使用第三方的存储服务(例如OSS)

    1. 优点:开发简单,拥有强大功能,免维护

    2. 缺点:付费

在本项目选用阿里云的OSS服务进行文件存储。

代码开发

文件上传接口

定义OSS相关配置

在sky-server模块

application-dev.yml

sky:
  alioss:
    endpoint: oss-cn-hangzhou.aliyuncs.com
    access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
    access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
    bucket-name: sky-take-out

application.yml  

sky: 
 alioss:
    endpoint: ${sky.alioss.endpoint}
    access-key-id: ${sky.alioss.access-key-id}
    access-key-secret: ${sky.alioss.access-key-secret}
    bucket-name: ${sky.alioss.bucket-name}

读取OSS配置

在sky-common模块中,已定义

package com.sky.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

}

 生成OSS工具类对象

在sky-server模块

package com.sky.config;

import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类,用于创建AliOssUtil对象
 */
@Configuration
@Slf4j
public class OssConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

其中,AliOssUtil.java已在sky-common模块中定义

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();
    }
}

定义文件上传接口

在sky-server模块中定义接口

@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
    
    @Autowired
    private AliOssUtil aliOssUtil;

    @ApiOperation("文件上传")
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传:{}",file);

        try {
            //原始文件名
            String originalFilename = file.getOriginalFilename();
            //截取原始文件名的后缀 dfdd.png获取.后的字符串
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            //构造新文件名称
            String objectName = UUID.randomUUID().toString() + extension;
            
            //文件的请求路径
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            
            return  Result.success(filePath);
        } catch (IOException e) {
            log.error("文件上传失败,{}",e);
        }
        return null;


    }
}

新增菜品实现

 设计DTO类

在sky-pojo模块中

package com.sky.dto;

import com.sky.entity.DishFlavor;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@Data
public class DishDTO implements Serializable {

    private Long id;
    //菜品名称
    private String name;
    //菜品分类id
    private Long categoryId;
    //菜品价格
    private BigDecimal price;
    //图片
    private String image;
    //描述信息
    private String description;
    //0 停售 1 起售
    private Integer status;
    //口味
    private List<DishFlavor> flavors = new ArrayList<>();
}

Controller层

进入到sky-server模块

功能测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言特殊字符串函数和字符函数

特殊字符串函数 strtok(字符串切割函数) 重点&#xff1a;1.delimiters 参数是个字符串&#xff0c;定义了用作分割符的字符集合 2.第一个参数指定一个字符串&#xff0c;里面包含0个或者多个分隔符 3.strtok函数找到str中的分隔符&#xff0c;会把它改成\0&#xff0c;然后…

内衣洗衣机哪个牌子好用?五款业内口碑爆棚产品汇总

内衣裤洗衣机是一种非常实用的洗衣机&#xff0c;可以有效地保护内衣和贴身衣物的质量和卫生&#xff0c;相比于普通的家用大型洗衣机&#xff0c;内衣裤洗衣机在容量、洗涤方式、控制方式和价格等方面有很大的不同之处&#xff0c;如果您经常需要清洗内衣和贴身衣物&#xff0…

无人机蜂群作战会成为未来战争的主要形式吗,该如何反制呢?

无人机蜂群作战在未来战争中确实有可能成为一种重要的作战形式&#xff0c;但是否会成为“主要形式”则取决于多种因素&#xff0c;包括技术发展、战术创新、战略需求以及国际政治和军事格局的变化等。以下是对无人机蜂群作战及其反制措施的详细分析&#xff1a; 一、无人机蜂…

图神经网络(GNN)简单介绍

参考文章:A Gentle Introduction to Graph Neural Networks 仅作为自己学习的笔记 GNN应用领域&#xff1a; 芯片设计 场景分析与问题分析 推荐系统&#xff08;类似抖音&#xff09; 欺诈检测&#xff0c;风控相关 知识图谱 道路交通&#xff0c;动态流量预测 自动驾驶&…

程序员的得力助手:Kimi AI的实战体验引言

引言 作为一名程序员&#xff0c;我们经常需要处理大量信息&#xff0c;从代码调试到文档编写&#xff0c;再到团队协作&#xff0c;每一项任务都需要我们保持高度的专注和效率。在这个过程中&#xff0c;一个得力的助手可以极大地提升我们的工作效率。今天&#xff0c;我想和…

洛谷P2571.传送带

洛谷P2571.传送带 三分模板题 用于单峰函数求极值 一定可以将答案路径分成三段即AE - EF - FD (E和A可能重复&#xff0c;F和D可能重合) E在线段AB上&#xff0c;F在线段CD上 因为有两个不定点EF&#xff0c;因此假设E为参数&#xff0c;三分求F的位置再外层三分求E的位置 …

PMP--三模--解题--1-10

文章目录 9.资源管理1、 [单选] 项目已经准备好开工&#xff0c;资源已经配置好。开发经理随后通知项目经理&#xff0c;由于家庭紧急情况&#xff0c;关键资源不再可用。开发经理表示&#xff0c;所有其他开发人员都被分配到其他项目&#xff0c;任何开发人员都没有能力承担额…

Windows内核编程基础(1)

在前面的文章中&#xff0c;介绍了如何配置开发环境以及如何进行调试。 接下来的几篇文章&#xff0c;将会重点介绍内核编程中所需要了解的一些理论基础。 我写这个系列文章的主要目的是方便以后自己查阅&#xff0c;同时也给正在学习内核开发的小伙伴一些参考&#xff0c;所…

在线PDF转图片怎么转?4种简单转换的方法分享

在线PDF转图片怎么转&#xff1f;在线PDF转图片不仅简化了文档处理流程&#xff0c;还极大地提升了工作效率。无论是教师准备教学材料、学生整理笔记&#xff0c;还是职场人士分享报告&#xff0c;都能通过这一功能轻松实现PDF到图片的转换&#xff0c;确保内容的高清展示与便捷…

OLED(3)字库篇

文章目录 1 显示图片1.1 图片取模 2 字符集与编码2.1 字符编码2.2 ASCII2.3 中文编码2.3.1 GB2312 标准2.3.2 GBK 编码2.3.3 GB18030 2.4 Unicode 字符集和编码2.4.1 UTF-322.4.2 UTF-162.4.3 UTF-8 3 字库 DIY3.1 生成字库3.2 烧录到 W25Qxx 1 显示图片 1.1 图片取模 1&#…

【完整梳理验证】企业微信第三方应用接入全流程java版

企业微信第三方应用接入全流程java版 1. 概念与流程1.1 概念1、企业内部应用2、`第三方应用`3、代开发自建应用1.2 流程1.2.1 全局流程1.2.2 应用配置1.2.3 数据流程2. 核心文档2.1 理解第三方应用开发流程和概念2.1.1 应用开发阶段2.1.2 应用推广阶段2.1.3 基本流程1)前期应用…

【VUE_ruoyi-vue】基于ruoyi-vue框架实现简单的系统通用文件模块

基于ruoyi-vue框架&#xff0c;新增一个简单的系统通用文件模块&#xff0c;服务与各个模块涉及到文件上传信息的记录和相关展示 运行sql,创建数据库表 DROP TABLE IF EXISTS sys_file_info; CREATE TABLE sys_file_info (id int(11) NOT NULL AUTO_INCREMENT COMMENT id,lin…

怎样才能远程了解在iPhone、iPad上看了什么网站、用了什么APP?

有不少家长在网上吐槽&#xff1a; ——自家小孩每天抱着手机看&#xff0c;一看就两三个小时&#xff0c;到底在看什么&#xff1f; ——没有不允许小孩玩手机&#xff0c;但他一玩就一整天&#xff0c;用什么户外活动、家庭活动都吸引不回来。 ——每次问小孩在手机上看什…

【C++掌中宝】用最少的话让你全方位理解内联函数

文章目录 引言1. 什么是内联函数2. 工作原理3. 内联函数的编程风格4. 使用限制5. 内联函数与宏的比较6. 优缺点7. 何时使用内联函数8. 补充9. 总结结语 引言 在C编程中&#xff0c;函数的调用开销是程序运行效率的一个重要影响因素。为了解决频繁调用函数时的性能问题&#xf…

8080时序

通过RS来区分是命令还是数据 在WR高电平时&#xff0c;将数据放入D[0:15]数据线上 在WR上升沿&#xff0c;读取D[0:15]数据线上的数据 //提前把默认信号设置为对应电平 static inline void LcdSendCmd(uint16_t cmdVal) {LCD_CS_RESET();//cs输出低电平&#xff0c;表示片选…

【Diffusion分割】MedSegDiff-v2:Diffusion模型进行医学图像分割

MedSegDiff-V2: Diffusion-Based Medical Image Segmentation with Transformer 摘要&#xff1a; 最近的研究揭示了 DPM 在医学图像分析领域的实用性&#xff0c;医学图像分割模型在各种任务中表现出的出色性能就证明了这一点。尽管这些模型最初是以 UNet 架构为基础的&…

低代码BPA(业务流程自动化)技术探讨

一、BPA流程设计平台的特点 可视化设计工具 大多数BPA流程设计平台提供直观的拖拽式界面&#xff0c;用户可以通过图形化方式设计、修改及优化业务流程。这种可视化的方式不仅降低了门槛&#xff0c;还便于非技术人员理解和参与流程设计。集成能力 现代BPA平台通常具备与其他系…

My_String完善

#include "my_string_ok.h" My_string_Ok::My_string_Ok():size(20) { len 0; ptr new char[size]; ptr[len] \0; } My_string_Ok::My_string_Ok(int num,char c) { cout<<"有参构造"<<endl; ptr new char [20] ; len 0; for…

K8s安装部署(v1.28)--超详细(cri-docker作为运行时)

1、准备环境 ip角色系统主机名cpumem192.168.40.129mastercentos7.9k8smaster48192.168.40.130node1centos7.9k8snode148192.168.40.131node2centos7.9k8snode248192.168.40.132node3centos7.9k8snode348 2、系统配置&#xff08;所有节点&#xff09; 重要&#xff1a;首先…

怎么更换自己的ip地址?多种方法可实现

在当今的数字化时代&#xff0c;IP地址作为我们在网络世界中的“身份证”&#xff0c;扮演着举足轻重的角色。然而&#xff0c;有时候出于隐私保护、网络安全或是访问特定服务等需求&#xff0c;我们可能需要更换自己的IP地址。那么&#xff0c;如何实现这一目标呢&#xff1f;…