《苍穹外卖》项目复盘--功能实现(1)

一、增删改(员工、菜品、套餐,以员工信息为例)

1、增加

    (1)接口设计

            请求参数:使用Post请求,JSON数据格式。使用具有员工信息的DTO对象接收。                                            @RequestBody EmployeeDTO employeeDTO

            返回值:返回状态码即可。

            DTO设计示例  

@Data
public class EmployeeDTO implements Serializable {

    //编号
    private Long id;
    //用户名
    private String username;
    //姓名
    private String name;
    //电话号码
    private String phone;
    //性别
    private String sex;
    //员工id
    private String idNumber;

}

  补充:@Data注解的作用是自动生成以下方法:①所有字段的 get 和 set 方法②toString() 方法,用于将对象转换成字符串形式,便于日志输出和调试。③hashCode() 方法,用于计算对象的哈希值。④equals() 方法,用于判断两个对象是否相等。⑤会生成一个无参构造方法

    (2)数据库设计

    (3)代码实现:

            Controller:接收请求与数据返回,调用业务层的方法。

            Service:实现业务功能。①将DTO对象转换为实体类对象②进行属性拷贝

                          ③进行相关属性设置、默认值设置④进行异常处理⑤调用mapper方法

                          ⑥返回值的处理

            Mapper:与数据库进行交互。Sql语句的编写Insert

(4)Sql语句

  普通插入代码示例(注意数据表中的命名方式和属性小驼峰的命名)    

<insert id="insert" parameterType="Orders" useGeneratedKeys="true" keyProperty="id">
        insert into orders (number,status,user_id,address_book_id,order_time,checkout_time,
                            pay_method,pay_status,amount,remark,phone,address,consignee,
                            estimated_delivery_time,delivery_status,pack_amount,
                            tableware_number,tableware_status)
        values
                    (#{number},#{status},#{userId},#{addressBookId},#{orderTime},
                     #{checkoutTime},#{payMethod},#{payStatus}, #{amount},#{remark},
                     #{phone},#{address},#{consignee},#{estimatedDeliveryTime},
                     #{deliveryStatus}, #{packAmount},#{tablewareNumber},
                     #{tablewareStatus})
</insert>

批量插入代码示例

<insert id="insertBatch">
        insert into order_detail (name,image,order_id,dish_id,setmeal_id, 
                                  dish_flavor,number,amount) 
        values
        <foreach collection="orderDetailList" item="od" separator=",">
            (#{od.name},#{od.image},#{od.orderId},#{od.dishId},
             #{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount})
        </foreach>
</insert>
2、删除

      (1)  接口设计

            请求参数:使用Delete请求。通过地址栏完成传参

                              @RequestParam List<Long> ids

            返回值:返回状态码即可。

    (2)代码实现:

            Controller:接收请求与数据返回,调用业务层的方法。

            Service:实现业务功能。①查询当前数据是否存在②判断当前数据是否具备可删除条件

            ③不具备删除条件可抛出删除异常信息④调用mapper方法删除

            Mapper:与数据库进行交互。Sql语句的编写Delete

    (3)Sql语句

            批量删除

    <delete id="deleteByDishIds">
        delete from dish_flavor where dish_id in
        <foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
            #{dishId}
        </foreach>
    </delete>
3、修改

       根据id查询当前数据用于回显

      (1)  接口设计

           请求参数:使用GET请求。路径参数

                             @PathVariable Long id

            返回值:返回状态码、Data(VO对象)

     (2)代码实现

            Controller:接收请求与数据返回,调用业务层的方法。

            Service:实现业务功能。①查询当前数据,调用mapper方法②判断返回数据是否为空

            ③为空抛出异常④不为空封装返回结果

            Mapper:与数据库进行交互。Sql语句的编写Select

       编辑修改数据      

      (1)  接口设计

           请求参数:使用PUT请求。JSON格式,封装为DTO对象

            返回值:返回状态码即可

     (2)代码实现

            Controller:接收请求与数据返回,调用业务层的方法。

            Service:实现业务功能。①将DTO对象转换为实体类②调用mapper方法进行更新数据

            Mapper:与数据库进行交互。Sql语句的编写Update(可写为动态更新提升代码复用率)

     (3)Sql语句

    <update id="update">
        update setmeal
        <set>
            <if test="categoryId!=null"> category_id=#{categoryId},</if>
            <if test="name!=null"> name=#{name},</if>
            <if test="price!=null"> price=#{price},</if>
            <if test="image!=null"> image=#{image},</if>
            <if test="description!=null"> description=#{description},</if>
            <if test="status!=null"> status=#{status},</if>
            <if test="updateTime!=null"> update_time=#{updateTime},</if>
            <if test="updateUser!=null"> update_user=#{updateUser},</if>
        </set>
        where id =#{id}
    </update>

二、分页查询

      (1) 接口设计

           请求参数:使用GET请求,使用Query查询参数(地址栏?)用DTO对象接收

           EmployeePageQueryDTO employeePageQueryDTO(第几页、每页个数、其它参数)

            返回值:返回状态码、data(total数据总数,records集合;封装成PageResult)

/**
 * 封装分页查询结果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private long total; //总记录数

    private List records; //当前页数据集合

}

补充:@AllArgsConstructor包含所有参数的构造器

           @NoArgsConstructor无参构造器(直接翻译)

   (2)代码实现:

            Controller:接收请求与数据返回,调用业务层的方法。返回Result<PageResult>

            Service:基于PageHelper的分页查询       

/**
 * 分页查询方法
 * @param employeePageQueryDTO
 * @return
 */
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {

    //开始分页查询(自动进行分页)   
   PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());

    //手动进行name的拼接工作
    Page<Employee> page=employeeMapper.pageQuery(employeePageQueryDTO);

    long total = page.getTotal();
    List<Employee> records = page.getResult();
    return new PageResult(total,records);
 }

            Mapper:与数据库进行交互。Sql语句的编写,完成分页查询Select

    (3)Sql语句编写

    上面代码中员工分页查询代码示例  employeeMapper.pageQuery()

    <select id="pageQuery" resultType="com.sky.entity.Employee">
       select * from employee
        <where>
            <if test="name != null and name !=''">
                and name like concat('%',#{name},'%')
            </if>
        </where>
        order by create_time desc
    </select>

 较复杂的订单查询代码示例:

    <select id="page" resultType="com.sky.entity.Orders" 
        parameterType="com.sky.dto.OrdersPageQueryDTO">

        select * from orders
        <where>
            <if test="number != null and number!=''">
                and number like concat('%',#{number},'%')
            </if>
            <if test="phone != null and phone!=''">
                and phone like concat('%',#{phone},'%')
            </if>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="status != null">
                and status = #{status}
            </if>
            <if test="beginTime != null">
                and order_time &gt;#{beginTime}
            </if>
            <if test="endTime != null">
                and order_time &lt; #{endTime}
            </if>
        </where>
        order by order_time desc
    </select>

        (4)PageHelper插件

        基于MyBatis的拦截器实现,进行动态sql拼接

        PageHelper和pageQuery的关系,怎么进行连接:PageHelper底层实现了ThreadLocal,存储相关数据,在pageQuery的Sql语句中再取出来,拼进Sql语句中并返回。

三、自动填充字段(自定义注解、AOP设计思想)

1、应用需求

     在员工、菜品、套餐增加和更新时,都需要编辑创建时间、创建人、修改时间、修改人信息

2、自定义注解AutoFill

   用于表示需要进行公共字段自动填充的方法

 /**
  * 自定义注解,用于标识某个方法需要进行功能字段的自动填充处理
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据操作类型,UPDATE INSERT 内部有一个属性
    OperationType value();
}

补充:@Target里面的标记,用于标识该注解在什么位置使用。常见的自定义注解有四种:注解用在类上面、注解用在方法上面、注解用在属性上面、注解用在参数上面,也可以写多个,比如:@Target({ElementType.METHOD,ElementType.FIELD})

@Retention注解标识注解的生命周期:RetentionPolicy.SOURCE 源文件  RetentionPolicy.CLASS类文件(默认)RetentionPolicy.RUNTIME 运行时也存在

3、自定义切面类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("开始进行公共字段自动填充...");

        //获取当前被拦截方法上的数据库操作类型 INSERT/UPDTE
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();//方法签名对象
        AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象
        OperationType value = annotation.value();//获取操作类型

        //获取当前被拦截方法的参数--实体对象
        Object[] args = joinPoint.getArgs();

        if(args==null || args.length==0){
            return;
        }

        Object entity = args[0];

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

        //根据类型通过反射进行赋值
        if(value==OperationType.INSERT){
            try {
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.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,localTime);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,localTime);
                setUpdateUser.invoke(entity,currentId);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }else if(value==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,localTime);
                setUpdateUser.invoke(entity,currentId);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

        }

    }
}

代码解读:

4、在Mapper方法上加上对应的注解
    /**
     * 根据主键动态修改属性
     * @param employee
     */
    @AutoFill(value=OperationType.UPDATE)
    void update(Employee employee);
5、AOP核心概念补充

连接点(JointPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

切入点(Pointcut):匹配连接点的式子。在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法

通知(Advice):在切入点执行的操作,也就是共性功能

通知类:定义通知的类

切面(Aspect):描述通知与切入点的对应关系

四、TnreadLocal获取当前用户id

1、用户登录流程及问题分析

在用户认证后发送请求携带的Token中,可以解析出来用户id

问题:在拦截器中没有直接调用Service方法。如何将解析出来的id传递至Service的save方法。

2、ThreadLocal

ThreadLocal是线程的一个局部变量。为每个线程提供单独一份存储空间,具有线程隔离的效果。只有在线程内才能取到对应的值,在线程外是无法访问的。

由于客户端每发的一次请求都是一次线程,可以通过Thread.currentThread().getId()来验证。因此可以通过ThreadLocal存值取值的方式实现不同类不同方法之间的变量的动态获取。

3、ThreadLocal常用方法
  • public void set(T value)   设置当前线程的线程局部变量的值
  • public T get()                   返回当前线程所对应的线程局部变量的值
  • public void remove()       移除当前线程的线程局部变量

补充:项目中使用了工具类进行封装该方法。

五、基于阿里云OSS的文件上传与获取

实现步骤:

  •     配置alioss的属性
  • 开始上传阿里云上传的工具类对象(在配置类中创建)
@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());
    }
}

补充:ConditionalOnMissingBean是保证容器中只有一个阿里云工具类对象

  • 构造新的文件名称(UUID+原始文件后缀),通过aliOssUtil调用upload方法上传文件保存至云盘

  • 返回路径

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

    @Autowired
    private AliOssUtil aliOssUtil;
    //使用配置类里面创建对象的方式 创建了一个Bean
    /**
     * 文件上传
     * @param file
     * @return
     * 此处MultipartFile file的file必须和接口上传的参数保持一致
     */
    @ApiOperation("文件上传")
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file ){
        log.info("文件上传:{}",file);

        try {
            //原始文件名
            String originalFilename = file.getOriginalFilename();

            //截取原始文件名的后缀
            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 Result.error(MessageConstant.UPLOAD_FAILED);
    }
}
@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();
    }
}

                                               

  • 58
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
苍穹外卖项目可以使用Postman进行API接口的测试和调试。Postman是一款常用的API开发工具,它可以帮助开发人员发送HTTP请求并查看响应结果,方便进行接口的测试和调试。 在苍穹外卖项目中,可以使用Postman发送各种类型的HTTP请求,比如GET、POST、PUT、DELETE等,来模拟用户操作和测试接口功能。通过Postman,可以验证接口的正确性、查看接口返回的数据、调试接口的参数等。 为了使用Postman进行苍穹外卖项目的接口测试,您需要以下步骤: 1. 下载并安装Postman:您可以从Postman官网(https://www.postman.com/)上下载并安装适合您的操作系统的版本。 2. 打开Postman并创建一个新的请求:打开Postman应用,在界面上选择"New"来创建一个新的请求。 3. 输入接口URL和选择请求方法:在新建请求的界面中,输入苍穹外卖项目的接口URL,并选择适当的请求方法,比如GET或POST。 4. 添加请求参数和请求头:根据需要,您可以添加请求参数和请求头,以便于模拟不同的请求情况。 5. 发送请求并查看响应:点击发送按钮,Postman会向服务器发送请求,并在界面上显示响应结果。您可以查看接口返回的数据、响应状态码等信息。 通过以上步骤,您可以使用Postman进行苍穹外卖项目的接口测试。这样可以帮助您确保接口的正确性和稳定性,提高项目的质量和用户体验。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值