Seata(分布式事务集成测试和总结)

news2025/1/16 8:50:15

文章目录

    • 1.集成测试
        • 1.集成测试正常下单
          • 1.步骤
          • 2.浏览器访问 http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100
          • 3.注意事项和细节
        • 2.集成测试模拟异常
          • 1.步骤
            • 1.com/sun/springcloud/controller/StorageController.java 休眠12s,模拟openfeign超时
            • 2.仍然按照正常步骤启动并测试
            • 3.预测结果
          • 2.执行前数据库状态
            • order表
            • account表
            • storage表
          • 3.浏览器输入 http://localhost:10008/order/save?userId=666&productId=1&nums=2&money=200,12s后查看数据库状态
            • order表
            • account表(这里在写sql时并没有计算数量,所以确实应该减去200)
            • storage表(库存减2)
        • 3.集成测试分布式事务控制
          • 1.在com/sun/springcloud/service/Impl/OrderServiceImpl.java 的save方法加上@GlobalTransactional进行分布式事务控制
          • 2.测试步骤
          • 3.全部启动后查看nacos注册情况
          • 4.测试前数据库状态
          • 5.浏览器请求 http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100
          • 5.注意事项
    • 2.seata分布式事务总结
        • 1.工作机制
        • 2.seata的使用流程
          • 1.安装
          • 2.修改 D:\seata\conf\file.conf文件
            • 1.修改事务组(需要进行分布式事务控制的微服务在同一组)
            • 2.修改日志存储模式为db
            • 3.修改数据库(MySQL5.7)连接信息
          • 3.修改registry.conf 配置注册中心nacos(seata需要注册到nacos)
          • 4.创建seata数据库和表
            • 1.创建seata数据库
            • 2.复制db_store.sql的内容,创建需要的表
          • 5.启动nacos和seata进行测试,seata会注册到nacos
          • 6.为每个需要分布式事务控制的数据库执行 db_undo_log.sql,创建undo_log表,用于事务回滚
          • 7.pom.xml引入依赖
          • 8.application.yml
          • 9.D:\seata\conf下的两个配置文件复制到src/main/resources下并修改
            • 1.将file.conf复制到src/main/resources下,然后修改这行,`seata的服务ip+端口`以及`db`根据实际情况修改(前面是修改过的)
            • 2.registry.conf复制到src/main/resources下,然后配置注册中心nacos(前面也配过,直接复制即可)
            • 3.最终的文件目录
          • 10.进行业务逻辑编写
            • 1.创建实体类
            • 2.dao层
            • 3.service层
            • 4.controller层
          • 11.两个常规配置类
            • 1.MyBatisConfig.java 配置类,依赖注入所有Mapper接口(不用加@Mapper注解了)
            • 2.DataSourceProxyConfig.java 配置数据源代理为 seata
          • 12.创建主启动类(示例)
          • 13.OpenFeign远程调用细节
            • 1.在前面的依赖中已经引入了OpenFeign,并且在主启动类中也开启了Feign客户端
            • 2.service接口声明需要远程调用的controller
            • 3.远程调用的方式
            • 4.谁可以远程调用

1.集成测试

1.集成测试正常下单
1.步骤

image-20240331195720151

2.浏览器访问 http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image-20240331200211635

image-20240331200315814

image-20240331200345159

image-20240331200405815

image-20240331200414455

3.注意事项和细节

image-20240331201240727

2.集成测试模拟异常
1.步骤
1.com/sun/springcloud/controller/StorageController.java 休眠12s,模拟openfeign超时

image-20240331204813269

2.仍然按照正常步骤启动并测试

image-20240331195720151

3.预测结果
  • 保存订单,扣减账户余额,扣减库存成功!但是修改订单状态失败,也就是status是0!

image-20240331202326884

image-20240331202503115

2.执行前数据库状态
order表

image-20240331204912334

account表

image-20240331204932277

storage表

image-20240331205004551

3.浏览器输入 http://localhost:10008/order/save?userId=666&productId=1&nums=2&money=200,12s后查看数据库状态
order表

image-20240331205124780

account表(这里在写sql时并没有计算数量,所以确实应该减去200)

image-20240331205149516

storage表(库存减2)

image-20240331210545726

3.集成测试分布式事务控制
1.在com/sun/springcloud/service/Impl/OrderServiceImpl.java 的save方法加上@GlobalTransactional进行分布式事务控制

image-20240401085848112

2.测试步骤

image-20240331195720151

3.全部启动后查看nacos注册情况

image-20240401090152198

4.测试前数据库状态

image-20240401090227123

image-20240401090238720

image-20240401090249370

5.浏览器请求 http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image-20240401090600011

image-20240401090536951

image-20240401090546928

image-20240401090555538

5.注意事项

image-20240401091116586

2.seata分布式事务总结

1.工作机制

image-20240401100528767

2.seata的使用流程
1.安装
2.修改 D:\seata\conf\file.conf文件
1.修改事务组(需要进行分布式事务控制的微服务在同一组)

image-20240331094307051

2.修改日志存储模式为db

image-20240331094402339

3.修改数据库(MySQL5.7)连接信息

在这里插入图片描述

3.修改registry.conf 配置注册中心nacos(seata需要注册到nacos)

image-20240331095851458

image-20240331095834040

4.创建seata数据库和表
1.创建seata数据库
# 创建数据库 seata
create database seata;
use seata;
2.复制db_store.sql的内容,创建需要的表

image-20240401101509841

image-20240331095307621

5.启动nacos和seata进行测试,seata会注册到nacos
6.为每个需要分布式事务控制的数据库执行 db_undo_log.sql,创建undo_log表,用于事务回滚

image-20240331103729957

7.pom.xml引入依赖
  • 注意这里引入的nacos是nacos-discovery starter,没有整合Seata之前引入的跟这个不一样!!!
    <dependencies>
        <!-- 提示 application.yml -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 引入 openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- 在微服务模块引入 nacos-discovery starter -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!-- 排除自带的 seata-all -->
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入指定版本的 io.seata -->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>

        <!-- springboot web starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- 如果在子工程/模块指定了 version,则以指定为准 -->
        </dependency>

        <!--
        1. starter-actuator 是 springboot 程序的监控系统,可以实现健康检查,info 信息
        等
        2. 访问 http://localhost:10000/actuator 可以看到相关链接, 还可以做相关设置. -->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <!-- 这里我们重新指定一下 version 因为父项目中没有对这个依赖进行版本仲裁-->
            <version>1.1.13</version>
        </dependency>

        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 公共模块的jar包 -->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>e_commerce_center-common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
8.application.yml
server:
  port: 10010 # 配置服务端口
spring:
  application:
    name: seata-storage-micor-service # 配置服务的名称,名字任意这里与项目名保持一致
  cloud:
    alibaba:
      seata:  # 配置seata
        tx-service-group: sun_order_tx_group # 自定义事务组名称,与\conf\file.conf保持一致
    nacos: # 配置nacos
      discovery:
        server-addr: localhost:8848
  datasource: # 配置数据源
    driver-class-name: com.mysql.jdbc.Driver
    # 别忘记创建数据库之后修改数据库名称
    url: 
    username: 
    password: 
logging:  # 配置seata日志级别
  level:
    io:
      seata: info
mybatis:
  mapperLocations: classpath:mapper/*.xml # 扫描所有Mapper.xml
  configuration:
    map-underscore-to-camel-case: true # 开启驼峰命名
9.D:\seata\conf下的两个配置文件复制到src/main/resources下并修改
1.将file.conf复制到src/main/resources下,然后修改这行,seata的服务ip+端口以及db根据实际情况修改(前面是修改过的)

image-20240331112838536

2.registry.conf复制到src/main/resources下,然后配置注册中心nacos(前面也配过,直接复制即可)

image-20240331113355613

3.最终的文件目录

image-20240331113408627

10.进行业务逻辑编写
1.创建实体类
  • 如果数据库中的数据是sun_name则在实体类中可以编写为sunName,不过这样需要在application.yml中开启自动驼峰命名(上面配置了)
  • 这个就相当于在每个Mapper.xml中加了一个resultMap映射,如果实体类属性和表的字段可以被自动驼峰命名所映射那么就不需要再写resultMap
2.dao层
  • Mapper接口注入容器
  • Mapper接口的@Param注解一旦指定,就不需要在xml实现的时候指定参数类型了,直接使用#{}来取出数据即可
  • Mapper.xml在application.yml中已经配置了自动扫描
3.service层
  • service接口编写
  • service实现类注入容器
4.controller层
  • 注入容器
  • 在微服务中如果使用GetMapping则参数需要添加@RequestParam
  • 如果使用PostMapping则参数需要添加@RequestBody
11.两个常规配置类
1.MyBatisConfig.java 配置类,依赖注入所有Mapper接口(不用加@Mapper注解了)
package com.sun.springcloud.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

/**
 * Description: MyBatis配置类,扫描所有Mapper接口,这样就不用在每个Mapper接口上添加@Mapper注解
 *
 * @Author sun
 * @Create 2024/3/31 13:16
 * @Version 1.0
 */
@MapperScan("com.sun.springcloud.dao")
@Configuration
public class MyBatisConfig {
}

2.DataSourceProxyConfig.java 配置数据源代理为 seata
  • 这里需要注意:配置文件中的mybatis.mapperLocations是以驼峰命名的
package com.sun.springcloud.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**
 * Description: 配置数据源的代理是 seata
 *
 * @Author sun
 * @Create 2024/3/31 13:28
 * @Version 1.0
 */
@Configuration
public class DataSourceProxyConfig {
    // 配置文件中的mybatis.mapperLocations
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations; // 1.mybatis的mapper文件位置

    // 配置数据源
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource") // 2.读取配置文件中的数据源配置
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    // 配置数据源代理为seata的DataSourceProxy
    @Bean // 配置数据源代理引入的包: io.seata.rm.datasource.DataSourceProxy
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    // 配置SqlSessionFactory为seata的SqlSessionFactoryBean
    @Bean // 配置SqlSessionFactory,常规配置
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy)
            throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean =
                new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations
                (new PathMatchingResourcePatternResolver().getResources(mapperLocations)); // 3.mybatis的mapper文件位置
        sqlSessionFactoryBean.setTransactionFactory
                (new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }
}

12.创建主启动类(示例)
package com.sun.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/3/31 13:37
 * @Version 1.0
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) // 取消数据源的自动创建,使用代理数据源
@EnableDiscoveryClient // 开启服务发现
@EnableFeignClients // 开启Feign客户端
public class SeataStorageMicroServiceApplication10010 {
    public static void main(String[] args) {
        SpringApplication.run(SeataStorageMicroServiceApplication10010.class, args);
    }
}

13.OpenFeign远程调用细节
1.在前面的依赖中已经引入了OpenFeign,并且在主启动类中也开启了Feign客户端
2.service接口声明需要远程调用的controller
  • 这里的FeignClient可以进行服务发现,得到要调用的服务的ip+端口+上下文路径
  • 这里的RequestMapping可以找到远程调用的服务的资源路径,与服务发现的路径进行拼接即可找到指定资源
  • 具体声明的方式就是
    1. 添加@FeignClient注解,指定要远程调用服务的application-name(注意不能带_)
    2. 将需要调用的controller直接复制过来,然后去掉方法体即可
package com.sun.springcloud.service;

import com.sun.springcloud.util.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/3/31 16:19
 * @Version 1.0
 */
@FeignClient(value = "seata-storage-micor-service")
public interface StorageService {
    @RequestMapping("/storage/reduce")
    public Result reduce(Long productId, Integer nums);
}

3.远程调用的方式
  • 通过依赖注入针对service接口的代理对象进行远程调用
4.谁可以远程调用
  • service接口实现类
  • controller

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

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

相关文章

【项目新功能开发篇】需求分析和开发设计

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

揭秘rmallox病毒:防范、清除、恢复一步到位!

引言&#xff1a; 随着信息技术的快速发展&#xff0c;计算机病毒已成为网络安全领域的一大难题。其中&#xff0c;rmallox病毒是近年来备受关注的一种恶意软件。本文将深入探讨rmallox病毒的特性、传播途径、防范措施、清除方法以及数据恢复技巧&#xff0c;帮助读者全面了解这…

Mac苹果电脑air/pro包含m1~m3打开app显示弹框“xxx”已损坏,无法打开。您应该将它移到废纸篓

应该是保姆级教程了&#xff1a; Mac苹果电脑air/pro包含m1~m3打开app显示弹框“xxx”已损坏&#xff0c;无法打开。您应该将它移到废纸篓。 我下载的是 Sublime Text 3 for Mac中文直装版&#xff0c;https://www.32r.com/soft/38404.html 安装后打开就gg了&#xff1a; 表现…

【图论】【分类讨论】LeetCode3017按距离统计房屋对数目

本文涉及的知识点 图论 分类讨论 本题同解 【差分数组】【图论】【分类讨论】【整除以2】3017按距离统计房屋对数目 LeetCode3017按距离统计房屋对数目 给你三个 正整数 n 、x 和 y 。 在城市中&#xff0c;存在编号从 1 到 n 的房屋&#xff0c;由 n 条街道相连。对所有 …

2024最新Notepad++下载安装教程图文步骤演示

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 文章目录 &#x1f42f;2024最新Notepad下载安装教程图文步骤演示&#x1f4dd;摘要引言正文&#x1f4d8;Notepad简介&#x1f4e5;下载Notepad&#x1f4e6;安装教程1. 获取安装…

2024HW-->Wireshark攻击流量分析

在HW中&#xff0c;最离不开的&#xff0c;肯定是看监控了&#xff0c;那么就要去了解一些wireshark的基础用法以及攻击的流量&#xff01;&#xff01;&#xff01;&#xff01; 1.Wireshark的基本用法 比如人家面试官给你一段流量包&#xff0c;你要会用 1.分组详情 对于我…

深入理解Java异常处理机制(day20)

异常处理 异常处理是程序运行过程产生的异常情况进行恰当的处理技术 在计算机编程里面&#xff0c;异常的情况比所我们所想的异常情况还要多。 Java里面有两种异常处理方式&#xff1b; 1.利用trycatchfinaly语句处理异常&#xff0c;优点是分开了处理异常代码和程序正常代码…

Android自定义view;实现掌阅打开书籍动画效果

这里利用自定义view的方式来处理&#xff0c;初始化数据&#xff0c;camera通过setLocation调整相机的位置&#xff0c;但是Camera 的位置单位是英寸&#xff0c;英寸和像素的换算单位在 Skia 中被写成了72 像素&#xff0c;8 x 72 576&#xff0c;所以它的默认位置是 (0, 0, …

STL--deque

deque 容器deque是一个双向队列&#xff08;double-ended queue&#xff09;&#xff0c;可以在队列的两端进行元素的插入和删除操作。deque 和 vector 非常相似。也采用dynamic array(动态数组) 来管理元素&#xff0c;提供随机访向&#xff0c;并有着和 vector 几乎一模一样…

jenkins+docker实现可持续自动化部署springboot项目

目录 一、前言 二、微服务带来的挑战 2.1 微服务有哪些问题 2.2 微服务给运维带来的挑战 三、可持续集成与交付概述 3.1 可持续集成与交付概念 3.1.1 持续集成 3.1.2 持续交付 3.1.3 可持续集成与交付核心理念 3.2 可持续集成优点 3.3 微服务为什么需要可持续集成 四…

anaconda虚拟环境安装apex0.1教程win10

我安装apex0.1的环境是&#xff1a;torch&#xff08;gpu&#xff09;1.8.0&#xff0c;cuda10.2&#xff0c;cuda7.6.5。 第一步&#xff1a;下载对应的pytorch、cuda、cudnn版本 这里就不详细介绍了&#xff0c;具体可以参考我的这篇博文win10中anaconda创建虚拟环境配置py…

Redis(性能管理、主从复制、哨兵模式)概述及部署

目录 一、性能管理 1、查看Redis内存使用 2、内存碎片率 3、跟踪内存碎片率 4、内存使用率 5、内回收key 二、Redis集群有三种模式 三、Redis主从复制 1、主从复制的概念 2、主从复制的作用 3、主从复制的流程 4、搭建Redis主从复制 1.环境准备 2.安装Redis&#…

如何选择和注册域名,域名有什么作用,什么是域名解析?域名的需要多少钱?

大家好欢迎来到易极赞&#xff0c;今天我们来跟大家聊一下“如何选择和注册域名”这个话题。 域名用来做什么&#xff1f; 域名对您的网站至关重要&#xff0c;因为它代表您的品牌名称并充当网站的地址。对于企业主来说&#xff0c;一个令人难忘的域名有助于建立在线形象和客户…

RabbitMQ3.13.x之七_RabbitMQ消息队列模型

RabbitMQ3.13.x之七_RabbitMQ消息队列模型 文章目录 RabbitMQ3.13.x之七_RabbitMQ消息队列模型1. RabbitMQ消息队列模型1. 简单队列2. Work Queues(工作队列)3. Publish/Subscribe(发布/订阅)4. Routing(路由)5. Topics(主题)6. RPC(远程过程调用)7. Publisher Confirms(发布者…

嵌入式Qt QGridLayout网格布局管理器

一.QGridLayout网格布局管理器 //以行为单位 设置比例系数 void QGridLayout::setRowStretch ( int row, int stretch ) //以列为单位 设置比例系数 void QGridLayout::setColumnStretch ( int column, int stretch ) 实验&#xff1a; Widget.h&#xff1a; #ifndef _WIDGE…

了解强化学习算法 PPO

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 介绍&#xff1a; PPO 算法&#xff0c;即 Proximal Policy Optimization&#xff08;近端策略优化&#xff09;&#xff0c;是一种强化学习算法。它的主要目的是改进策略梯度方法&#xff0c;使得训练…

numpy二维与三维数组简单操作示例

1.运行PowerShell然后输入python 2.在python命令行输入 import numpy as np进入导入numpy库并添加别名为np 3.使用numpy的zeros函数创建一个2行4列的全0矩阵 4.查看上面创建的全0矩阵 5.查看矩阵类型 6.使用numpy的ones函数创建一个2行4列全1的矩阵 ,并查看类型及矩阵中的数据 …

JavaScript中堆栈内存管理机制及其在深拷贝与浅拷贝场景中的应用与解析

一.堆栈的定义 1.栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。 结论&#xff1a;后进先出&#xff08;Last In First Out&#xff09;&#xff0c;简称为LIFO线性表。 栈的应用有&#xff1a;数制转换&#xff0c;语法词法分析&…

小小总结之二分查找三种情况

小小总结之二分查找三种情况 二分查找主要难点在于&#xff1a; 起始条件 left,right的取值&#xff0c;一般若闭合区间&#xff0c;则0, length(nums)。如果涉及边界位置代入计算&#xff0c;则0,length(nums)。循环条件&#xff0c;第一要能够避免死循环&#xff0c;第二要看…

最优乘车

题目描述 H 城是一个旅游胜地&#xff0c;每年都有成千上万的人前来观光。为方便游客&#xff0c;巴士公司在各个旅游景点及宾馆&#xff0c;饭店等地都设置了巴士站并开通了一些单程巴上线路。每条单程巴士线路从某个巴士站出发&#xff0c;依次途经若干个巴士站&#xff0c;…