简介这份PDF教程面向使用周立功USBCAN2的CAN总线初学者与二次开发人员覆盖汽车诊断、工业自动化、医疗设备等常见应用场景重点解决驱动安装、软件配置与设备排错中容易卡壳的问题。全文以单份PDF呈现压缩包仅174KB内含1个PDF文件体积轻便便于在电脑或移动端随查随看。内容围绕驱动下载、应用软件、二次开发资料和常见问题展开既给出上手所需的准备清单也整理了不能识别、连接不上、数据传输不稳定等故障的排查思路并提示API文档、示例代码与开发指南等二次开发入口。已有7232人学习适合希望快速建立USBCAN2使用框架、减少环境搭建与调试弯路的学习者和开发者参考。1. 插上设备没反应这类问题大多不是设备坏了设备插上电脑设备管理器里什么都没多出来或者多出来一个带黄色感叹号的未知 USB 设备再或者驱动看着装好了点开上位机软件弹一句打开设备失败。这三个画面几乎是每个第一次用 USBCAN-II 的人都要撞的第一堵墙也是各类 USBCAN-II 使用教程中被问得最多的部分。这类故障让人难受的地方在于它不像代码报错能直接给你一个堆栈。设备不亮、系统不认、软件不开口三段链条里任何一段断了现象都一样。很多人折腾半天最后直接重装系统、换电脑、换线靠更换设备碰运气。这篇内容是按枚举失败 → 打开失败 → 收不到帧 → 二次开发加载失败的顺序拆的排错记录新手可以照着命令一步步走老手可以对照根因和预防手段。刚拿到设备的人、接手别人设备的人、以及把设备交给产线或实验室新人使用的人都适合从头看一遍。2. 设备管理器认不出感叹号、未知 USB 设备和没有新硬件的三种差别2.1 先分清三种枚举结果别一上来就重装驱动同样叫认不出在系统层面是三个完全不同的阶段。插上设备后打开设备管理器先看它落在哪一类这一步决定了后面该查硬件还是查驱动现象卡在哪一层优先排查方向设备管理器毫无变化连未知设备都不出现USB 物理层 / 供电换 USB 口、换线、换电脑看设备指示灯出现未知 USB 设备设备描述符请求失败USB 枚举阶段驱动未安装、线材质量、USB 3.0 口兼容性出现厂商命名的设备但带黄色感叹号驱动加载阶段驱动版本、系统签名策略、位数不匹配出现厂商设备且状态正常但软件里没有通道应用层设备索引、软件版本、通道号完全没反应这一类里最常见的不是驱动问题而是线。有些 USB 线是纯充电线只有电源两根线插上后设备能亮灯如果灯由 USB 供电但数据线不存在系统自然什么都看不到。判断方法很直接换一根确定能传数据的线插到机箱后面的 USB 口再试一次。前置面板的 USB 口供电和信号质量参差不齐遇到时好时坏的情况先换后置口是成本最低的一步。2.2 装驱动的正确顺序先装驱动包再插设备厂商的驱动安装包大多是为设备尚未插入的状态准备的。先插设备再装驱动Windows 会先用一套通用逻辑去枚举装完驱动后往往要手动卸载设备再重新扫描否则残留的设备实例会一直挂在未知设备分类下。我一般按这个顺序走拔掉设备 → 装驱动包 → 重启 → 插设备 → 看设备管理器。重启这一步不要省很多驱动要等内核模块重新加载才会绑定到新设备。装完别急着点软件先用系统自带工具确认驱动真的进了驱动库而不是只解压到了某个文件夹# 列出系统里已安装的第三方驱动包确认 CAN 驱动对应的 oemXX.inf 是否存在 pnputil /enum-drivers | Select-String -Pattern can,usb -Context 0,4 # 查设备状态和问题码Status 异常时Problem 码能直接告诉你是没装驱动还是加载失败 Get-PnpDevice | Where-Object { $_.FriendlyName -like *CAN* } | Format-List FriendlyName, Status, Problem, InstanceIdpnputil /enum-drivers输出的是驱动包清单能看到每个包对应的 inf 文件名、发布者、版本号和日期。如果这里根本没有 CAN 相关的条目说明驱动安装程序压根没把驱动装进系统后面所有排查都是白费。Get-PnpDevice里的Problem字段是定位关键28表示驱动未安装10表示设备无法启动52表示数字签名验证失败43表示设备被上层驱动中止。看到 28 就去补驱动看到 52 就去处理签名看到 10 多半是驱动装错版本或设备本身异常这三种情况的解法完全不同不要混着试。2.3 Problem 52驱动装上了但被系统签名策略拦下在老设备配新系统、或者用了随设备附带的旧版驱动的场景里Problem 52出现得非常多。表现是驱动包确实装进去了设备也能被识别出名字但旁边挂着感叹号属性里写着无法验证设备的数字签名。原因不复杂系统对内核态驱动有签名强制要求旧驱动没有符合当前要求的签名加载环节被拒绝。处理方式按优先级排第一优先是去厂商下载页拿最新版驱动这类驱动通常已经适配新系统的签名要求换版本比改系统策略干净得多第二是在内网测试机、离线设备这种可控环境里临时调整启动策略做验证验证完再改回来最不推荐的是在生产机上长期关闭签名强制一旦机器接入别的设备风险会被放大。# 查看设备被拦下的具体原因确认是不是签名问题 Get-PnpDevice | Where-Object { $_.Problem -ne 0 } | Format-List FriendlyName, Status, Problem, ProblemDescription # 仅在隔离的测试环境验证用临时调整启动策略重启后生效验证完务必改回 bcdedit /set nointegritychecks off bcdedit /enum {current}bcdedit /enum {current}用来确认当前启动项的实际配置改完一定要回头看这条输出很多人改完没重启就以为生效了结果现象没变化白白多排查一轮。生产环境别动这条命令驱动版本升级才是正解。2.4 装完仍是感叹号手动指定 INF 目录的方式有些驱动安装程序只做了解压没有真正执行安装或者安装过程中被杀毒软件拦了一步。这种情况下设备管理器里能看到设备但驱动始终绑不上。手动指定 INF 是兜底手段右键设备 → 更新驱动程序 → 浏览我的电脑 → 让我从可用驱动列表中选取 → 从磁盘安装 → 指向驱动目录里的 inf 文件。手动安装时有两点容易翻车。一是驱动目录里往往同时存在 32 位和 64 位两套 inf选错了会出现安装成功但设备无法启动Problem 10因为系统加载了位数不匹配的内核驱动。二是目录里可能有多份 inf分别对应不同型号型号选错同样表现为设备无法启动。判断依据是设备管理器里显示的硬件 ID和 inf 文件里[Manufacturer]段的型号列表对一下能省掉大量试错。注意手动指定 INF 前先把未知设备或带感叹号的旧实例卸载掉勾选删除此设备的驱动程序软件否则新驱动会叠加在旧实例上装了也白装。3. 打开设备失败设备明明在句柄却拿不到3.1 设备被另一个进程占着最常见也最容易被忽略设备管理器里一切正常软件点打开设备却失败第一嫌疑是设备已被占用。多数 CAN 分析仪的驱动不做多进程共享一个进程拿到设备句柄后第二个进程再打开就是失败而且很多上位机软件给的提示极其含糊只有一句初始化失败。现场最常见的三个占用来源上一次运行的软件没退干净窗口关了但进程还在托盘里、产线上另一个采集程序在后台跑、以及自己写的测试程序没调关闭函数就异常退出了。排查顺序是先看进程再看代码# Windows列出可能占用设备的进程重点看上次用的上位机和你自己的程序 tasklist | findstr /I can view test monitor # 需要更细的句柄级信息时用系统自带的资源监视器按句柄搜索设备名 # 或在 Linux 下直接看谁打开了对应的设备节点 sudo lsof /dev/bus/usb/*/* 2/dev/null | head -n 20tasklist只做粗筛看到可疑进程先结束掉再重试打开这一步能解决大半打开失败。lsof在 Linux 下更精确能直接列出持有设备节点的进程 PID如果输出里能看到你自己的程序那就是代码里漏了关闭流程问题不在设备而在程序生命周期管理上。代码侧的预防很简单把打开和关闭做成严格的配对用try/finally或 RAII 保证异常路径也会释放。我见过不止一次因为一个未捕获的异常导致设备句柄被吊死之后所有人用这台机器都打不开设备最后只能拔插 USB 才恢复这种问题排起来特别费时间。3.2 位数不匹配32 位程序加载 64 位 DLL二次开发里打开设备失败有很大一部分根本不是设备问题而是 DLL 加载就没成功程序用了错误的返回值或者干脆走了默认分支。典型报错是找不到指定的模块错误码 0x8007007E或者托管环境下的BadImageFormatException。根因只有一个进程位数和 DLL 位数不一致。64 位进程只能加载 64 位 DLL32 位进程只能加载 32 位 DLL中间没有兼容层。厂商的二次开发包里通常同时提供 x86 和 x64 两个目录很多人把 x64 目录里的 DLL 拷到程序输出目录而程序本身是 AnyCPU 在 32 位模式下跑的于是必然失败。// 加载前先确认进程位数位数不对时后面的所有调用都没有意义 Console.WriteLine(Environment.Is64BitProcess ? x64 process : x86 process); // 声明时不要把路径写死放在输出目录由运行时按位数自行解析 [DllImport(canlib.dll, CallingConvention CallingConvention.StdCall)] private static extern uint OpenDevice(uint devType, uint devIndex, uint reserved);Environment.Is64BitProcess是最直接的判据把它打印在第一行日志里能省掉很多到底是设备问题还是加载问题的争论。DllImport里只写文件名不写路径是为了让运行时按标准搜索顺序去找配合把对应位数的 DLL 放到输出目录这一条规则能让开发机和部署机的行为一致。如果必须写绝对路径请用IntPtr方式手动LoadLibrary并在失败时打印GetLastError否则错误信息会被吞掉。排查时的顺序建议是先确认进程位数 → 再确认 DLL 位数 → 最后确认 DLL 的依赖项运行库是否齐全。缺 VC 运行库的表现和找不到模块非常像容易误判成路径问题。3.3 设备索引写错多设备场景下最隐蔽的错打开设备的函数一般有三个参数设备类型、设备索引、保留位。设备索引从 0 开始插两台设备时分别是 0 和 1。听起来简单但实际项目里翻车的场景不少设备换了个 USB 口枚举顺序变了原来的索引 0 现在指向另一台设备或者程序里把索引写死成 0现场插了两台就永远只能打开其中一台。/* 打开设备类型、索引、保留参数。索引从 0 开始多设备时必须逐个枚举 */ uint32_t devType DEV_TYPE; /* 按你手上型号对应的设备类型常量填写 */ uint32_t devIndex 0; /* 0 表示第一台插拔后顺序可能变化 */ uint32_t reserved 0; /* 保留参数通常传 0 */ if (OpenDevice(devType, devIndex, reserved) ! 1) { /* 返回非 1 就是失败先区分是没有这台设备还是被占用 */ fprintf(stderr, open dev %u failed\n, devIndex); return -1; }这段代码的关键不在函数本身而在失败分支的处理。返回失败时不要直接退出先把当前系统里到底枚举到几台设备打印出来和期望的索引对一下。多数二次开发包都提供了获取设备数量的接口启动时先读数量再按索引打开比写死索引稳得多。reserved参数虽然现在没用也不要随手传个随机值某些版本的驱动对它有校验。3.4 Linux 下打开失败权限和 udev 规则同样的程序在 Windows 上跑得好好的挪到 Linux 就打开失败八成是权限问题。普通用户默认没有对 USB 设备的读写权限程序打开设备节点时被拒绝表现就是打开失败而没有任何进一步信息。# 1. 确认设备已被内核枚举能看到厂商 ID 说明硬件层没问题 lsusb # 2. 看这次插入的日志区分是枚举失败还是驱动没绑上 sudo dmesg -T | tail -n 30 # 3. 把当前用户加入可访问设备节点的用户组重新登录后生效 sudo usermod -aG dialout $USERlsusb是硬件层的第一道验证如果这里看不到设备后面的权限配置都是空谈。dmesg会打印枚举过程的详细信息出现device descriptor read error这类字样通常是线材或供电问题出现new full-speed USB device但没有后续驱动绑定日志说明内核认了设备但厂商驱动没加载。如果不想每次都用 root 跑或者不想依赖用户组可以写一条 udev 规则按厂商 ID 和产品 ID 给固定权限# /etc/udev/rules.d/99-can-device.rules # idVendor / idProduct 从 lsusb 的输出里抄MODE 给读写权限 SUBSYSTEMusb, ATTR{idVendor}xxxx, ATTR{idProduct}xxxx, MODE0666, GROUPplugdev # 改完规则要重新加载并触发一次否则规则不会对已插入的设备生效 sudo udevadm control --reload-rules sudo udevadm triggerMODE0666适合实验室或产线这类内部环境省事但权限偏宽对外交付的设备建议用GROUP加用户组的方式收敛权限。udevadm trigger这一步经常被漏掉改完规则发现没效果多半是没触发。4. 能发不能收波特率、终端电阻和 Bus Off 的三连击4.1 收不到帧先怀疑波特率和采样点设备打开了、通道也启动了发送看着成功但一帧都收不到或者收到的全是错误帧。这类问题里波特率不匹配占了绝大多数。CAN 是异步总线两端没有时钟线全靠约定的位时间对齐一端 500 kbps 另一端 250 kbps物理层上表现为持续的格式错误软件层表现为发得出去、收不回来。初始化参数里最容易被忽略的是定时参数它决定了波特率和采样点。不同驱动版本的定时参数编码方式可能不同务必以你手上那版 API 手册里的对照表为准别照抄别人博客里的十六进制值/* 通道初始化波特率、滤波、工作模式三项决定能不能收到帧 */ InitConfig cfg; cfg.AccCode 0x00000000; cfg.AccMask 0xFFFFFFFF; /* 掩码全 1表示不滤波先收全部帧排除滤波问题 */ cfg.Filter 1; /* 单滤波 */ cfg.Mode 0; /* 0 正常1 只听2 自测 */ cfg.Timing0 0x00; /* 定时参数 0取值查手册中波特率对照表 */ cfg.Timing1 0x1C; /* 定时参数 1和 Timing0 一起决定波特率与采样点 */ if (InitChannel(devType, devIndex, ch, cfg) ! 1) { /* 初始化失败 */ } if (StartChannel(devType, devIndex, ch) ! 1) { /* 启动失败注意必须先 Init 再 Start */ }AccMask全 1 的意义是先关掉滤波这样如果突然能收到帧了就说明原来的问题出在滤波配置上而不是链路。Mode的三种取值各有用途正常模式收发都参与只听模式不发送、不回 ACK适合挂到别人的总线上旁听而不干扰自测模式自发自收用来验证设备本身是否正常后面会用到。Timing0/Timing1必须两端一致采样点位置也要在同一个量级否则长线缆下会间歇性出错这种跑一会儿错几帧的问题最折磨人。4.2 接线和终端电阻反接、缺电阻、不共地波特率对上了还是不稳就该看物理层了。CAN 是差分总线CAN_H 和 CAN_L 接反的典型表现是完全收不到帧或者满屏错误帧总线两端各需要一个 120 Ω 终端电阻缺了会导致信号反射短线上可能看不出来线一长就开始丢帧两端设备不共地共模电压漂移同样表现为随机错误。断电状态下用万用表量 CAN_H 与 CAN_L 之间的电阻是最快的一步正常应该是 60 Ω 左右两个 120 Ω 并联。量出来是 120 Ω 说明只接了一端量出来是无穷大说明一个都没接量出来明显偏离 60 Ω 就要检查是不是某个节点内部还并了别的电阻。这一步花不了一分钟却能排掉相当比例的玄学丢帧。注意量电阻必须在断电、且所有节点都断开通信的情况下做带电测量得到的值没有参考意义。4.3 Bus Off 与错误帧错误计数器告诉了你真相总线上的节点有发送错误计数器和接收错误计数器发送错误计数超过 255 时节点进入 Bus Off 状态主动退出总线不再发送任何东西。表现出来就是程序还在跑发送函数也返回成功但总线上什么都没有。这时候光看应用层日志是查不出问题的得看错误状态。Bus Off 的常见成因有三类波特率不一致最常见、总线上只有一个节点没人回 ACK单节点自发自收必然出错、以及线缆干扰或终端电阻缺失导致的位错误。对应的处理方式不同成因现象特征处理方式波特率不一致一上电就大量错误帧收不到任何正常帧两端定时参数对齐用示波器或自测模式核对单节点无应答只有一块设备接在总线上发送即错接上第二个节点或临时切到自测模式验证终端电阻缺失短线正常、长线随机出错错误率随线长上升补齐两端 120 Ω检查线缆屏蔽与共地干扰特定工况电机启停、变频器工作时集中出错屏蔽层单端接地、走线远离动力线、降波特率验证进入 Bus Off 后应用层要主动做恢复不能指望驱动自动重连。标准做法是先停止通道再重新启动中间加一个退避延时避免总线本身有问题时程序陷入疯狂重启的死循环。这一点在长时间运行的采集程序里尤其重要否则一次干扰就能让程序静默失联日志里还看不出异常。4.4 用自测模式把设备问题和总线问题分开排查到这一步如果还是不确定是设备坏了还是总线有问题最快的办法是切到自测模式不接外部总线设备自发自收。能收到自己发的帧说明设备、驱动、初始化参数、读取逻辑这一整条链路都是通的问题一定在外部接线或对端配置上收不到问题就在设备或软件这一侧跟总线无关。这个动作的价值在于把排查范围一刀切成两半避免在到底是硬件还是软件之间反复横跳。我一般建议在项目开始阶段就写一个自测小工具十几次按键就能跑完比每次现场靠猜高效得多。切换模式只需改初始化参数里的一个字段然后重新 Init 和 Start不需要重插设备。5. 二次开发避坑五个让我反复翻手册的报错5.1 报找不到指定的模块但 DLL 明明就在目录里现象程序启动时抛DllNotFoundException或错误码 0x8007007E去输出目录一看DLL 文件确实躺在那里。原因这条报错名不副实它说的模块经常不是你要加载的那个 DLL而是它依赖的下游 DLL。位数不匹配也会报这一句——32 位进程去加载 64 位 DLL系统给的就是找不到而不是位数不对。另外开发时用绝对路径加载成功、部署到别的机器失败多半是那台机器缺运行库。解决先用位数检查把第一种可能排掉见 3.2再用依赖查看工具把 DLL 的下游依赖列出来逐个确认存在。部署包里把运行库一起带上别指望目标机器装过。加载失败时打印系统错误码比打印加载失败四个字有用一百倍。5.2 收到的数据是乱码甚至程序直接崩现象能收到帧但帧 ID 和数据全是乱的或者一读到某个字段就访问越界崩溃。原因结构体对齐方式不一致。C 编译器默认会做字节对齐填充而 DLL 内部是按紧凑布局解析的两边差一个字节后面所有字段全部错位。这个坑在 C# 这类托管语言里尤其常见因为默认的LayoutKind.Sequential仍然可能插入填充。解决C 侧用#pragma pack(push, 1)关掉填充C# 侧在结构体上标Pack 1并且固定长度数组要显式声明长度不能写成变长// Pack 1 关掉自动对齐字段顺序和字节数必须和头文件逐字节一致 [StructLayout(LayoutKind.Sequential, Pack 1)] public struct CanFrame { public uint Id; // 帧 ID扩展帧时注意标记位 public uint TimeStamp; // 时间戳仅接收方向有效 public byte TimeFlag; public byte SendType; public byte RemoteFlag; // 远程帧标志 public byte ExternFlag; // 扩展帧标志 public byte DataLen; // 数据长度0~8 [MarshalAs(UnmanagedType.ByValArray, SizeConst 8)] public byte[] Data; // 定长 8 字节不能用变长数组 [MarshalAs(UnmanagedType.ByValArray, SizeConst 3)] public byte[] Reserved; // 保留字段也要占位少一个字节后面全错位 }改完之后一定要用Marshal.SizeOf和 C 侧sizeof的结果交叉验证一次两个数字对上才算真的对上。这一步很容易被跳过但对不上就是随机崩溃而且崩溃点往往离真正的错误很远查起来非常费时间。5.3 接收函数一直返回 0以为设备没数据现象Receive调用不报错但每次返回都是 0总线上明明有帧在跑。原因三个高频可能。一是通道只 Init 了没 Start或者 Start 失败但返回值被忽略了二是通道号传错单设备双通道时通道 0 和通道 1 是两条独立总线接在通道 0 上却去读通道 1三是超时参数理解错单位是毫秒但很多人按秒去填结果 50 毫秒就返回了程序还没来得及处理就以为没数据。解决把打开 → 初始化 → 启动 → 读取四步的返回值全部打日志哪一步没返回成功一目了然。读取时先用非阻塞方式快速轮询确认有数据后再调整阻塞超时避免一上来就设一个很大的超时把线程卡死。# 读取返回值语义要分清0 是这次读到的帧数0 是这段时间没帧全 F 才是真出错 ret dll.Receive(dev_type, dev_index, ch, byref(buf), 100, 50) if ret 0: pass # 正常这段时间总线安静 elif ret 0: handle(buf, ret) # 处理 ret 帧 else: log_error(receive failed) # 无符号返回值下通常表现为 0xFFFFFFFF100是这次最多读多少帧50是等待超时毫秒这两个参数的组合决定了程序的实时性和 CPU 占用。生产环境里我一般用短超时 循环的组合超时设 50~100 毫秒单次读取帧数给足既能及时响应又能把 CPU 压下来。5.4 跑几个小时之后打不开设备了现象程序连续跑几小时后重启或者同机器上另一个程序要打开设备时失败必须拔插 USB 才能恢复。原因句柄泄漏。异常路径上没走到关闭函数设备句柄一直挂着或者每次操作都重新打开一次设备而关闭没有对应执行。也有的是线程退出时没做清理后台线程还持有引用。解决打开和关闭严格成对用try/finally包住整个使用周期把设备访问做成单例全局只开一次在程序退出、线程结束、异常捕获这三个位置都加清理逻辑。长时间运行的采集程序还要加个心跳日志定期打印设备状态别等用户发现数据断了才去查。5.5 发送返回失败或者帧莫名其妙丢了现象发送函数返回失败或者返回成功但总线上抓不到帧高负载时丢帧明显。原因发送缓冲区满。上位机发得比总线快缓冲区打满后新帧被丢弃有的驱动返回失败有的直接静默丢弃后者更坑。另外总线负载本身接近饱和时比如 500 kbps 上跑满周期报文重发机制会让情况进一步恶化。解决不要在循环里无脑发自己维护一个发送队列按总线速率估算可用带宽发不出去就排队等下一次。判断总线负载可以用设备自带的统计接口读错误计数和总线负载率负载长期超过 70% 就要考虑提波特率或者精简报文。丢帧问题一旦在现场出现排查成本极高不如在设计阶段就把流控做进去。6. 快速定位从插上设备到收到第一帧的排查顺序现场排障最怕的是没有顺序东试一下西试一下把本来清晰的问题搅成一团。我自己的习惯是固定按一条链路走每一步都有明确的通过标准通不过就停在那一步不往下跳步骤检查内容通过标准1换线换口重插看设备指示灯设备管理器出现新条目2看问题码Get-PnpDeviceProblem 为 0设备状态正常3关闭可疑占用进程后打开设备打开接口返回成功4Init Start 两个返回值都确认两步均返回成功5切自测模式自发自收能收到自己发出的帧6接回总线核对两端波特率与定时参数收发的帧 ID 与周期符合预期7断电测 CAN_H/CAN_L 电阻约 60 Ω8读错误计数器与总线负载无持续增长的错误计数前四步覆盖了绝大多数打不开的问题第五步是关键分水岭它把设备侧和总线侧彻底分开。很多人在第 6 步反复折腾找不到原因其实第 5 步根本没做一直在用一条不确定是否正常的链路去验证另一条不确定的链路这种排查方式很难收敛。两个补充习惯。第一所有接口返回值都要打日志尤其是那些看起来不会失败的初始化函数失败率比想象中高。第二把出问题时的现场信息固定下来设备管理器的截图、dmesg或系统日志的片段、初始化参数的完整打印、错误计数器的数值。少了这些事后复盘只能靠回忆而回忆在现场排障里是最不可靠的东西。那份 USBCAN-II 使用教程把驱动下载、应用软件安装、二次开发资料获取这三段流程按顺序整理好了配合上面这条排查链路用能少走不少弯路——先照教程把驱动和软件装对再按第 6 章的表格一步步验证比你从零开始猜要快得多。资源里还带了常见问题的对照说明遇到不能识别连接不上传输不稳定这几类典型现象时可以直接对号入座。从那以后我每次接手一块新的 CAN 设备都会先花五分钟把自测模式的收发跑通再去接真实总线。这个习惯救过我很多次——它保证了我排查总线问题时手上那条链路一定是可信的。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?