苍穹外卖笔记

目录

关于Swagger使用

添加新员工

员工分页查询

启用禁用员工

编辑员工信息

分类接口

注解AOP自动填充创建修改信息

阿里云上传文件

新增菜品

菜品分页展示

删除菜品

修改菜品

店铺营业状态开发

微信小程序登录

用户端浏览菜品

使用spring_cache管理套餐缓存

购物车业务

订单提交

订单支付

[使用Spring Task定时处理异常订单](#使用Spring Task定时处理异常订单)

使用websocket实现订单提醒

[Apache Echarts制表](#Apache Echarts制表)

管理端数据分析功能

订单管理


关于Swagger使用

Swagger能生成接口文档,方便后端调试

使用方式

导入maven坐标

html 复制代码
<dependency>
 <groupId>com.github.xiaoymin</groupId>
 <artifactId>knife4j-spring-boot-starter</artifactId>
 <version>3.0.2</version>
</dependency>

创建配置类

java 复制代码
@Bean
    public Docket docket() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

图中扫描了controller包,之后会自动生成接口文档

添加静态资源映射

java 复制代码
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

完成后访问页面即可,按图中操作既是访问/doc.html

注解

注解的作用是在swagger生成的接口文档中添加相应的说明,随手添加养成好习惯

添加新员工

创建类往线程中添加键值对存储当前操作者的id,前端的每一次请求都会开启一个线程

java 复制代码
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();
    }

}

在员工类中对dto和其他属性进行封装,BeanUtils.copyProperties(a,b)能将a对象中成员值传递到b对象的同名成员中

java 复制代码
public static Employee create(EmployeeDTO employeeDTO,Long creator)
    {
        Employee employee=new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);
        employee.setStatus(StatusConstant.ENABLE);
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        employee.setCreateUser(creator);
        employee.setUpdateUser(creator);
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        return employee;
    }

用户名在数据库里设置为unique,当重复时抛出SQLIntegrityConstraintViolationException异常,对前端返回

java 复制代码
​
@ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        String message=ex.getMessage();
        if(message.contains("Duplicate entry")) return Result.error("用户名已存在");
        return Result.error("未知错误");
    }

​

员工分页查询

在service层使用PageHelper插件,该插件类似于拦截器,在sql语句之后自动添加limit,故不要加分号,同时,在此之前会进行对数据库的一次count()询问,得到total

PageHelper依赖

java 复制代码
<dependency>
     <groupId>com.github.pagehelper</groupId>
     <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

此时,返回的值中日期时间会以数组形式返回给前端

在WebMvcConfiguration中重写实现json转换器对传回前端的日期等数据进行格式转化

java 复制代码
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){
        MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(new JacksonObjectMapper());
        converters.add(0,converter);//往转换器集合中添加自己的转换器
    }

重启服务器后得到正确格式

启用禁用员工

为员工设置状态,限制其登录,本质上就是修改操作update

将update使用<set>标签,传递一个实体类,若不为空就修改,这样使得sql语句可以复用,以后就不用写update了

javascript 复制代码
<update id="update">
        update sky_take_out.employee
        <set>
            <if test="name!=null and name!='' ">name=#{name},</if>
            <if test="username!=null and username!='' ">username=#{username},</if>
            <if test="password!=null and password!='' ">passord=#{password},</if>
            <if test="phone!=null and phone!='' ">phone=#{phone},</if>
            <if test="sex!=null and sex!='' ">sex=#{sex},</if>
            <if test="idNumber!=null and idNumber!='' ">id_number=#{idNumber},</if>
            <if test="status!=null">status=#{status},</if>
            <if test="createTime!=null">create_time=#{createTime},</if>
            <if test="updateTime!=null">update_time=#{updateTime},</if>
            <if test="createUser!=null">create_user=#{createUser},</if>
            <if test="updateUser!=null">update_user=#{updateUser},</if>
        </set>
        where id=#{id}
   </update>

编辑员工信息

前端需要数据回显,先进行id查询员工的操作,然后在修改

我们利用上面提到的update更新即可,记得更新修改者和修改时间,同上单线程内的当前id已经放入context了,直接取出来就行了

java 复制代码
@Override
    public void updateEmployee(EmployeeDTO employeeDTO) {
        Employee employee=new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);
        employee.setUpdateUser(BaseContext.getCurrentId());
        employee.setUpdateTime(LocalDateTime.now());
        employeeMapper.update(employee);
    }

分类接口

分类接口和员工接口操作类似,不再赘述

注解AOP自动填充创建修改信息

发现随着项目的复杂化,在创建修改数据是会产生大量冗余的代码来设置创建修改的信息,所以考虑采用AOP和注解的方式实现自动填充信息

自定义注解AutoFill

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //设置操作类型,update 和 insert
    OperationType value();
}

这里我们在需要填充信息的方法上添加注解,在切面设置Before方法时通过下面的代码就能获得添加的注解的value从而执行不同的填充操作,然后利用反射对mapper的参数填充信息

java 复制代码
//获得加了注解的方法对象
        MethodSignature signature=(MethodSignature) joinPoint.getSignature();
        //通过方法对象获得该方法上的注解对象
        AutoFill autoFill=signature.getMethod().getAnnotation(AutoFill.class);
        //获得注解的枚举值
        OperationType operationType=autoFill.value();

阿里云上传文件

我们可以把项目中所上传保存的图片上传到阿里云保存,先开通阿里云的对象存储oss服务

创建bucket并获取自己的AccessKey的id和secret,在meaven中导入依赖,创建上传工具类后即可,创建controller以接受前端传来的请求

安装方式参考官方文档

工具类代码如下

java 复制代码
@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();
    }
}

新增菜品

菜品除了自身单一的属性之外,还有和口味的一对多的关系,这里将口味抽离出来作为单独的对象,自建一个数据库表,所以在添加菜品时应先将口味除外的属性添加到Dish表,并拿到回显的主键,然后将设置好对应关系的口味插入DishFlavor表中

java 复制代码
@Override
    public Result save(DishDTO dishDTO) {
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        dish.setStatus(StatusConstant.ENABLE);
        List<DishFlavor> flavors=dishDTO.getFlavors();

        if(dishMapper.check(dish.getName())!=0)
        {return Result.error("菜品已存在");}

        dishMapper.save(dish);
        Long id=dish.getId();
        if(flavors!=null && !flavors.isEmpty()){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(id);
            });
        }
        dishFlavorMapper.saveBatch(flavors);
        return Result.success();
    }

菜品分页展示

dish表中只含有category_id,没有菜品分类的名字,这里使用多表查询,为c的项目起别名来映射到对象

java 复制代码
<select id="pageQuery" resultMap="dishVoMapper">
        select d.*,c.name as categoryName from dish  d left outer join category  c on d.category_id=c.id
        <where>
            <if test="categoryId != null">and d.category_id=#{categoryId}</if>
            <if test="name !=null and name!='' ">and d.name like concat('%',#{name},'%')</if>
            <if test="status !=null ">and d.status = #{status}</if>
            </where>
        order by d.create_time desc
</select>

删除菜品

删除单个和多个菜品共用一个方法即可,删除菜品时得删两个表,菜品表和口味表,在售的菜品不能删,还包含在套餐里的菜品也不能删

java 复制代码
@Override
    public Result deleteBatch(List<Long> ids) {
        //在售不能删
        Long countSell=dishMapper.numberOfSell(ids);
        //套餐关联不能删
        Long countSetMeal=setMealDishMapper.numberOfDishId(ids);

        if(countSell!=0||countSetMeal!=0)
        {
            return Result.error("有菜品在售或被套餐绑定!");
        }
        dishMapper.deleteBatch(ids);
        dishFlavorMapper.deleteByDishId(ids);
        return Result.success("删除成功");
    }

修改菜品

修改菜品时口味表也许会变,得先删除原先的口味表,然后新设置口味表插入

java 复制代码
@Override
    @Transactional
    public void update(DishDTO dishDTO) {
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO,dish);

        List<Long> dishId=new ArrayList<>();//复用方法删除口味表
        dishId.add(dishDTO.getId());
        dishFlavorMapper.deleteByDishId(dishId);

        List<DishFlavor> flavors=dishDTO.getFlavors();//为dish设置口味表
        Long id=dishDTO.getId();
        if(flavors!=null && !flavors.isEmpty()){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(id);
            });
        }
        dishFlavorMapper.saveBatch(flavors);
        System.out.println("OK");
        dishMapper.update(dish);
    }

店铺营业状态开发

店铺状态查询需要频繁查询,内容又简单,适合使用redis来实现

Redis在spring-boot中的使用方法

1.导入maven坐标

java 复制代码
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2.在配置文件中配置redis

java 复制代码
  redis:
    password: 123456  //密码
    host: localhost  //地址
    port: 6379   //端口
    database: 0  //使用几号库

3.添加配置类,导入序列化器

java 复制代码
@Configuration
@Slf4j
public class RedisConfiguration {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("创建redis模板对象");
        RedisTemplate redisTemplate=new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

然后在代码里通过redisTemplate对象得到的如ValueOperations的方法就可使用redis

营业端代码

java 复制代码
private final String KEY="SHOP_STATUS";
    @PutMapping("/{status}")
    @ApiOperation("设置营业状态")
    public Result setShopStatus(@PathVariable Integer status)
    {
        log.info("设置营业状态:{}",status==1?"营业中":"打样中");
        ValueOperations valueOperations=redisTemplate.opsForValue();
        valueOperations.set(KEY,status);
        return Result.success();
    }

    @GetMapping("/status")
    @ApiOperation("查询营业状态")
    public Result<Integer> getShopStatus()
    {
        ValueOperations valueOperations= redisTemplate.opsForValue();
        Integer status= (Integer) valueOperations.get(KEY);
        log.info("营业状态:{}",status==1?"营业中":"打样中");
        return Result.success(status);
    }

微信小程序登录

微信小程序登录的官方流程图如下

小程序先发出携带js_code 的登录请求,服务端接收后将小程序的id密钥 连同用户的js_code 发送给微信接口服务,微信接口服务返回用户的openid给服务端,服务端此时可以通过此id来注册登录用户

向微信接口服务发送请求时可以使用Httpclient,此处用工具类包装

java 复制代码
private String WX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";
    private String getOpenid(String code){
        Map<String,String> mp=new HashMap<>();
        mp.put("appid", weChatProperties.getAppid());
        mp.put("secret",weChatProperties.getSecret());
        mp.put("js_code",code);
        mp.put("grant_type","authorization_code");
        String json=HttpClientUtil.doGet(WX_LOGIN,mp);
        JSONObject jsonObject= JSON.parseObject(json);
        return jsonObject.getString("openid");
    }

同时注意要再设置一个拦截器拦截来自用户端的请求

用户端浏览菜品

后端代码与管理端大同小异,不再赘述,根据需求文档编写代码

但是,在用户端访问时,可能出现频繁请求访问的情况,使用redis缓存来减轻数据库压力,再通过分类查询菜品后,将该分类id作为key,将得到的数据放入redis缓存中,下次用户查询若redis中已经存在该数据,就不用查询数据库了

java 复制代码
String key="dish_"+categoryId;
        List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
        if(list!=null && !list.isEmpty())
        {
            return Result.success(list);
        }

为了保持数据的一致性,管理端在更新修改菜品时,应该清除redis中相关的缓存

在修改操作返回前执行清除缓存操作

java 复制代码
private void cleanCache(String pattern)
    {
        Set keys=redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
    }

使用spring_cache管理套餐缓存

补全套餐接口,注意没写根据id获得套餐信息(数据回显)之前无法正常测试修改套餐接口,(明明前端已经有套餐id了还要使用回显的id),代码重复度高,略

spring_cache使用方法

导入坐标

java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

在启动类上加上@EnableCaching注解开启缓存代理,会自动判断你使用的缓存来进行管理

在需要管理的方法上加上对应注解即可

@Cacheable(cacheNames="xxx",key="#yyy")

将在缓存中添加key为xxx::yyy,value为方法返回值的键值对(#代表取值而不是字符串)

@CacheEvict(cacheNames="xxx",allentries=true)

删除所有xxx::的缓存,也可将后面替换为key来精确删除

购物车业务

单独开表实现购物车功能,购物车表里每一项都代表着某人的购物车里的某个菜,在添加时先查询是否存在,如果存在就修改数量,不存在就从其他表里拿出必要信息补全并添加,删除同理,删除单个时要先判断菜品的数量来决定修改还是删除

添加代码如下

java 复制代码
@Override
    public void add(ShoppingCartDTO shoppingCartDTO) {
        ShoppingCart shoppingCart=new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        shoppingCart.setUserId(BaseContext.getCurrentId());
        List<ShoppingCart> list=shoppingCartMapper.list(shoppingCart);
        if(list!=null && !list.isEmpty())
        {
            ShoppingCart res=list.get(0);
            res.setNumber(res.getNumber()+1);
            shoppingCartMapper.update(res);
            return ;
        }

        if(shoppingCart.getDishId()!=null)
        {
            Long dishId=shoppingCart.getDishId();
            Dish dish= new Dish();
            dish.setId(dishId);
            List<Dish> res=dishMapper.list(dish);
            dish=res.get(0);
            shoppingCart.setImage(dish.getImage());
            shoppingCart.setName(dish.getName());
            shoppingCart.setAmount(dish.getPrice());
        }
        else
        {
            Long setmealId=shoppingCart.getSetmealId();
            Setmeal setmeal=setmealMapper.selectById(setmealId);
            shoppingCart.setImage(setmeal.getImage());
            shoppingCart.setName(setmeal.getName());
            shoppingCart.setAmount(setmeal.getPrice());
        }
        shoppingCart.setNumber(1);
        shoppingCart.setCreateTime(LocalDateTime.now());
        shoppingCartMapper.add(shoppingCart);
    }

删除代码如下

java 复制代码
@Override
    public void delete(ShoppingCartDTO shoppingCartDTO) {
        ShoppingCart shoppingCart=new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        shoppingCart.setUserId(BaseContext.getCurrentId());

        List<ShoppingCart> list=shoppingCartMapper.list(shoppingCart);
        ShoppingCart res=list.get(0);
        if(res.getNumber()>1)
        {
            res.setNumber(res.getNumber()-1);
            shoppingCartMapper.update(res);
        }
        else
        {
            shoppingCartMapper.delete(shoppingCart);
        }
    }

订单提交

订单提交到后端时,先处理下数据,处理没有地址,购物车为空的情况(前端已经完成过,以防万一),然后将前端传过来的数据补全,添加到订单表(order)和订单详情表(order_detail),最后注意清空用户的购物车表(shoppingcart)

订单支付

个人认证的小程序没法使用支付功能,这里跳过支付,只要用户点击支付就算支付成功,代码如下

java 复制代码
 @Override
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) {
        Long userId=BaseContext.getCurrentId();
        User user=userMapper.getByid(userId);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "ORDERPAID");

        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));//返回前端的对象

        Orders orders=new Orders();
        orders.setNumber(ordersPaymentDTO.getOrderNumber());
        orders.setUserId(userId);

        Orders ordersDB = orderMapper.select(orders);//根据userid和订单号查询指定订单

        orders.setId(ordersDB.getId());
        orders.setStatus(Orders.TO_BE_CONFIRMED);
        orders.setPayStatus(Orders.PAID);
        orders.setPayMethod(ordersPaymentDTO.getPayMethod());
        orders.setCheckoutTime(LocalDateTime.now());

        orderMapper.update(orders);//更新为支付状态
        return vo;
    }

微信小程序支付官方流程图

使用Spring Task定时处理异常订单

使用方法

导入坐标(在整合包中已经包含)

在启动类上添加@EnableScheduling开启定时任务

创建定时任务类,在方法上添加@Scheduled(cron = "参数")注解

在项目启动后,就会按照cron所表达的内容定时执行该方法

java 复制代码
@Scheduled(cron = "0 * * * * ?")
    public void timeOutOrder()
    {
        log.info("处理超时未支付订单:{}",LocalDateTime.now());
        LocalDateTime time=LocalDateTime.now().plusMinutes(-15);
        List<Orders> list=orderMapper.wrongOrder(Orders.PENDING_PAYMENT,time);
        if(list==null||list.isEmpty()) return;

        for(Orders orders:list)
        {
            orders.setStatus(Orders.CANCELLED);
            orders.setCancelReason("支付超时");
            orders.setCancelTime(LocalDateTime.now());
            orderMapper.update(orders);
        }
    }


@Select("select * from orders where status=#{status} and order_time<#{orderTime}")
    List<Orders> wrongOrder(Integer status, LocalDateTime orderTime);

使用websocket实现订单提醒

与http短连接协议不同,ws协议是持续链接协议,建立了全双工通道,可以实时相互发送消息,项目里包装使用,代码如下

java 复制代码
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

由于无法实现支付,新订单提醒放在了暂定的实现方法后面,正常应放在支付成功,微信服务端返回调用的函数中

催单提醒时,先判断订单号和订单状态,存在相应的订单才向客户端发送催单

Apache Echarts制表

此为前端技术,引入js文件后,按照指定格式填写数据就能简单地在页面生成一个表格

Apache Echarts官方手册

因此,后端的任务根据接口文档就知道返回数据列表即可

先将前端转过来的日期范围落实成一个日期的列表,然后根据列表的内容去查询每天的营业额,再把每天的营业额放入另一个列表,最后将两个列表封装返回给前端

java 复制代码
@Override
    public TurnoverReportVO turnoverStatistics(LocalDate begin, LocalDate end) {
        List<LocalDate> datelist=new ArrayList<>();
        datelist.add(begin);
        while(!begin.equals(end))
        {
            begin=begin.plusDays(1);
            datelist.add(begin);
        }
        List<Double> incomelist=new ArrayList<>();
        for(LocalDate date:datelist)
        {
            LocalDateTime beginTime=LocalDateTime.of(date, LocalTime.MIN);
            LocalDateTime endTime=LocalDateTime.of(date, LocalTime.MAX);
            Map mp=new HashMap<>();
            mp.put("begin",beginTime);
            mp.put("end",endTime);
            mp.put("status",Orders.COMPLETED);
            Double income=orderMapper.sumByMap(mp);
            incomelist.add(income==null?0.0:income);
        }
        return TurnoverReportVO.builder()
                .dateList(org.apache.commons.lang.StringUtils.join(datelist,","))
                .turnoverList(StringUtils.join(incomelist,","))
                .build();
    }

管理端数据分析功能

通过上述制表技术,前端要求给出日期始末,返回相应的数据

将拿到的日期先得到一个日期的List,再根据每一个日期在数据库中相应的数据

将得到的数据List转化为字符串(此处可用stream流),封装之后返回给前端

订单管理

按着计划写下来最后就差订单管理板块,根据接口文档写即可,注意在put的请求时,这里接收参数都是用dto或path,单独接收id会出错(传过来的就是对象)

至此项目基本完成

gitee链接

相关推荐
Yawesh_best31 分钟前
思源笔记轻松连接本地Ollama大语言模型,开启AI写作新体验!
笔记·语言模型·ai写作
CXDNW2 小时前
【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0
网络·笔记·http·面试·https·http2.0
使者大牙2 小时前
【大语言模型学习笔记】第一篇:LLM大规模语言模型介绍
笔记·学习·语言模型
ssf-yasuo2 小时前
SPIRE: Semantic Prompt-Driven Image Restoration 论文阅读笔记
论文阅读·笔记·prompt
ajsbxi2 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
TeYiToKu3 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
dsywws3 小时前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
cuisidong19975 小时前
5G学习笔记三之物理层、数据链路层、RRC层协议
笔记·学习·5g
乌恩大侠5 小时前
5G周边知识笔记
笔记·5g
咔叽布吉6 小时前
【论文阅读笔记】CamoFormer: Masked Separable Attention for Camouflaged Object Detection
论文阅读·笔记·目标检测