苍穹外卖笔记

目录

关于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链接

相关推荐
饕餮争锋2 小时前
设计模式笔记_创建型_建造者模式
笔记·设计模式·建造者模式
萝卜青今天也要开心2 小时前
2025年上半年软件设计师考后分享
笔记·学习
吃货界的硬件攻城狮3 小时前
【STM32 学习笔记】SPI通信协议
笔记·stm32·学习
蓝染yy3 小时前
Apache
笔记
lxiaoj1114 小时前
Python文件操作笔记
笔记·python
半导体守望者5 小时前
ADVANTEST R4131 SPECTRUM ANALYZER 光谱分析仪
经验分享·笔记·功能测试·自动化·制造
啊我不会诶6 小时前
倍增法和ST算法 个人学习笔记&代码
笔记·学习·算法
逼子格6 小时前
振荡电路Multisim电路仿真实验汇总——硬件工程师笔记
笔记·嵌入式硬件·硬件工程·硬件工程师·硬件工程师真题·multisim电路仿真·震荡电流
一条破秋裤7 小时前
一份多光谱数据分析
笔记·数据挖掘·数据分析
zstar-_7 小时前
【算法笔记】6.LeetCode-Hot100-链表专项
笔记·算法·leetcode