Solar Debugger:一个轻量易扩展的上位机调试助手

Solar Debugger:一个轻量易扩展的上位机调试助手

在我以往从事的项目中,一个系统可能有几十甚至几百个参数,为了便于系统调试,我制作一个可以适配多个项目、简单的、专注于数据写入和显示的上位机:Solar Debugger。

gitee:https://gitee.com/lsksp/solar_-debugger

发布版本百度网盘链接: https://pan.baidu.com/s/1Cfo5AZWSh1g5_Qf7VgOTiQ?pwd=iwys 提取码: iwys

发布版本gitee链接:https://gitee.com/lsksp/solar_-debugger/releases/tag/V1.0.0

一、项目简介

Solar Debugger 是一款基于 Qt6/C++ 开发的跨平台上位机调试工具。工具主打轻量化、易扩展、零代码适配,用户仅需修改 JSON 配置文件,即可快速完成设备参数定义、界面布局与通讯协议适配,无需改动源码、无需重新编译。

本工具面向嵌入式、电机控制、机器人开发等设备调试场景,专注设备通讯交互、参数监控与曲线分析。区别于传统硬编码上位机,本工具实现参数定义、UI 布局、通讯协议完全解耦,所有参数的名称、类型、量程、单位、分组及UI样式均由配置文件动态生成,大幅提升设备调试与项目适配效率。

项目 版本 / 说明
语言标准 C++17
GUI 框架 Qt 6.5 及以上(Core、Widgets、SerialPort、PrintSupport)
构建系统 CMake ≥ 3.19
编译器 MSVC 2022 / MinGW 13+ / GCC 11+ / Clang 14+
第三方库 QCustomPlot
设计工具 Qt Creator

二、功能描述

视频演示:Solar Debugger:一个轻量易扩展的上位机调试助手

2.1 下行数据

软件页面上半部分包含系统配置、参数配置 两个可自定义标签页,所有标签页的名称、数量、展示顺序,均可通过 __paramsConfig.json 配置文件自定义,无需修改源码。

标签页内支持自定义下发参数控件,如模式选择、参数增益等。用户手动输入参数数值后,点击下发按钮,即可按照预设协议格式向下位机发送数据,所有下发参数的名称、数量、排列顺序均由配置文件动态定义。

界面中央独立设置开关指令区域,和下发参数类似,可由用户定义。该类指令为预设开关量,无需手动输入数值,点击即可发送固定格式指令数据。指令的名称、数量、排序同样支持配置文件自定义适配。

2.2 上行数据(下位机上传至上位机)

上位机上行数据展示界面分为三大区域:左上波形图像显示区、左下功能控制区、右侧参数通道列表区,所有功能均基于配置文件动态适配,灵活度高。

2.2.1 右侧通道列表

通道列表会动态展示配置文件中注册的所有上行参数,包含参数实时数值、波形显示开关等信息。为避免无效数据干扰、提升调试效率,参数不会默认展示,仅当下位机上传对应有效数据后,通道列表才会动态刷新显示,可搭配上行控制功能联动使用。

2.2.2 左上波形显示区

支持多维缩放操作,适配不同数据观测需求:纵向坐标轴侧滚动鼠标滚轮可实现纵向缩放,横向坐标轴侧滚动滚轮可实现横向缩放,坐标轴外侧区域滚动滚轮可实现波形整体缩放,操作方式贴合常规调试工具使用习惯。

2.2.3 左下功能控制区

集成全套波形调试工具,核心功能包含:自定义FIFO数据缓存上限、波形自动适配、数据接收与波形刷新暂停、清空历史波形数据、缓存数据导出CSV文件、光标显示/隐藏切换,满足日常数据观测、记录、调试需求。

2.2.4 上行控制核心功能

该功能可读取配置文件中注册的全部上行参数,用户可自主勾选需要采集的参数,确认发送后,上位机将推送ID=0 的上行调控特殊帧 。下位机接收响应后,会主动上传ID=1 的批量上传特殊帧,一次性回传所有勾选的参数数据,大幅提升批量数据采集效率。

常规数据上传规则:下位机可使用自定义参数ID作为帧ID,单帧数据段可携带该ID对应的多组参数数据。为保留特殊帧预留位,用户自定义参数帧ID必须大于24,≤24的ID为系统预留特殊帧专用。

项目配套提供下位机适配函数库,适配逻辑与上位机一致,开发者仅需注册对应参数,即可直接调用封装好的帧构建、响应、编解码相关函数,快速完成下位机协议适配,具体使用方法详见文档第六章。

三、配置文件说明

上位机所有上下行参数、UI布局、控件属性与通讯映射关系,均由**__paramsConfig.json** 文件统一配置。项目无需硬编码参数,修改配置文件即可完成设备适配与界面自定义。

配置文件分为两大核心节点:

  • meta:配置文件基础信息,包含项目描述、版本号等通用标识。
  • parameters:参数配置数组,所有上下行通讯参数、UI控件属性均在此定义。

完整可用的配置示例如下:

jsonc 复制代码
{
    "meta": {
        "description": "Solar Debugger Configuration",
        "version": "1.0"
    },
    "parameters": [
        [30, "急停", "急停", "Uint8", 0, 1.0, 0.0, "", 1, 0, 1, "开关指令", 2],
        

        [258, "d轴电流", "d轴电流", "Int16", 0, 0.01, 0.0, "A", 0, -1000, 1000, "上行数据", 0],
        [258, "q轴电流", "q轴电流", "Int16", 2, 0.01, 0.0, "A", 0, -1000, 1000, "上行数据", 0],
        [259, "温度", "温度", "Int32", 0, 0.01, 0.0, "℃", 0, -1000, 1000, "上行数据", 0],
        [260, "速度", "速度", "Int16", 0, 0.01, 0.0, "rpm", 0, -500, 500, "上行数据", 0],
        [261, "电量", "电量", "Uint8", 0, 0.1, 0.0, "", 0, 0, 100, "上行数据", 0],

        [512, "模式选择", "模式选择", "Uint8", 0, 1.0, 0.0, "", 4, 1, 16, "系统配置", 1],
        [513, "增益", "增益", "Float", 0, 1.0, 0.0, "LSB", 1.0, 0.001, 10.0, "系统配置", 1],
        
        [500, "电流环Kp", "电流环比例增益", "Float", 0, 1.0, 0.0, "", 1.0, 0.0, 100.0, "参数配置", 1],
        [501, "电流环Ki", "电流环积分增益", "Float", 0, 1.0, 0.0, "", 10.0, 0.0, 1000.0, "参数配置", 1]
    ]
}

parameters 数组内每个元素为固定13位参数配置数组,字段顺序、含义固定,完整定义如下:

text 复制代码
[ID, 名称, 描述, 数据类型, 位偏移量, 比例, 偏置, 单位, 默认值, 最小值, 最大值, 分类, ui显示类型]
序号 字段 类型 说明
0 ID int 数据段的唯一 ID(必须大于24),对应通讯帧中的 frameId下行参数 一个 ID 对应一个参数;上行参数 一个 ID 可对应多个参数(通过 位偏移量 区分)
1 名称 string UI 上显示的参数名
2 描述 string 对参数作用的详细介绍
3 数据类型 string Uint8 / Int8 / Uint16 / Int16 / Uint32 / Int32 / Float
4 位偏移量 int 参数在帧 data 段中的起始字节偏移
5 比例 float 缩放因子:物理值 = 原始值 × 比例 + 偏置
6 偏置 float 偏移量
7 单位 string 物理单位(如 Ar/min),可留空
8 默认值 float UI 初始化时显示的值
9 最小值 float UI 输入校验下限
10 最大值 float UI 输入校验上限
11 分类 string UI 分组标签,相同分类的参数会自动归到同一个 Tab 页
12 ui显示类型 int 0 = 只读数值显示;1 = 可写数值输入框;2 = 开关按钮

3.2 下行数据配置规范

下行数据为上位机下发至下位机的控制参数,为简化协议逻辑、提升传输效率,统一遵循以下规范:

  • ID唯一绑定:单个通讯ID仅对应一个下行参数,映射关系唯一。
  • 无数据缩放:下行参数比例固定为1、偏置固定为0,帧原始数据与物理值完全一致,支持直接指针操作,无需类型转换。
  • 双UI下发模式:支持两类可下发控件,底层通讯逻辑完全一致,仅展示形式不同。UI类型1为数值输入框,手动输入数值后下发;UI类型2为功能按钮,点击直接发送默认值,适用于急停、启停等开关指令

3.3 上行数据配置规范

上行数据为下位机上传至上位机的监测参数,支持单ID多参数绑定,提供两种传输模式,适配不同调试场景,且下位机上传数据无需上位机回复响应帧。

  • 单ID多参数绑定:同一个通讯ID可绑定多个监测参数,通过字节偏移量区分不同参数,节省帧ID资源。
  • 普通单ID上传:以参数自定义ID为帧ID,单帧仅传输该ID对应的一组参数数据,适用于单点实时监测。
  • 批量统一上传:固定帧ID为1,单帧可聚合多个自定义ID的参数数据,配合上位机上行控制功能,实现按需批量采集,高效调试多通道数据

四、软件架构

复制代码
Solar_Debugger/
├── CMakeLists.txt
├── main.cpp
├── MainWindow.cpp / .h                # 主窗口装配
├── CommunicationManager.cpp / .h      # 通讯管理(子线程)
├── __paramsConfig.json                # 用户配置文件,需要复制到可执行exe文件同目录
├── Solar_Debugger.rc                  # Windows 资源文件
├── ReadMe.md
├── ui/                                # UI 层
│   ├── BaseConfigPage.*               # 通用参数配置页(表格 + 输入框)
│   ├── ConfigTabwidget.*              # 多分类 Tab 容器
│   ├── ConnectionWidgetBase.h         # 连接配置对话框基类
│   ├── SerialConnectionWidget.*       # 串口连接页面
│   ├── Switchwidget.*                 # 开关按钮组
│   ├── CurvePlotWidget.*              # 实时曲线绘图(基于 QCustomPlot)
│   ├── UplinkControlDialog.*          # 上行调控对话框(勾选 ID 下发 frameId=0)
│   └── Logwidget.*                    # 通讯日志面板
├── src/
│   ├── core/                          # 数据核心层
│   │   ├── Parameter.h                # 参数描述结构
│   │   ├── ParameterRegistry.*        # 参数注册表
│   │   ├── DataCacheLayer.*           # 数据缓存与节流
│   │   └── RingBuffer.h               # 环形缓冲
│   ├── protocol/                      # 协议层
│   │   ├── ProtocolCodec.h            # 抽象编解码接口
│   │   └── SerialCodec.*              # 串口协议实现
│   ├── communication/                 # 通讯驱动层
│   │   ├── AbstractCommunicationDriver.h
│   │   └── SerialDriver.*
│   └── utils/
├── device_library/                    # 下位机调用库(纯 C 实现)
│   ├── serial_protocol.*              # 串口帧协议(打包、解析、CRC16)
│   ├── param_table.*                  # 参数管理引擎(注册、校验、上传)
│   └── param_manager.*                # 应用层示例(参数定义、回调处理)
├── qcustomplot/                       # 第三方绘图库源码
├── res/                               # 资源文件(可替换自己的qss文件),需要复制到可执行exe文件同目录
└── build/                             # 构建输出目录

Solar Debugger 采用 分层 + 抽象接口 的设计,便于后续替换通讯方式与协议而无需改动上层 UI。整个系统自上而下分为四层,数据流向如下:

复制代码
UI层 ←→ 核心层(参数注册表+数据缓存) ←→ 协议层(编解码) ←→ 通讯层(串口驱动)
  • UI层 (ui/):负责界面展示与用户交互,包括参数配置页(BaseConfigPage)、Tab容器(ConfigTabWidget)、实时曲线绘图(CurvePlotWidget)、日志面板(LogWidget)等。所有UI均由 __paramsConfig.json 驱动自动生成,无需硬编码。
  • 核心层 (src/core/):ParameterRegistry(单例)在启动时从 JSON 加载全部参数定义,供 UI 和协议层查询;DataCacheLayer 对高频接收数据做节流降采样,避免 UI 刷新过载。
  • 协议层 (src/protocol/):ProtocolCodec 是纯虚接口(encode / decode),当前由 SerialCodec 实现自定义串口帧协议。替换协议只需新增一个 ProtocolCodec 子类。
  • 通讯层 (src/communication/):AbstractCommunicationDriver 定义了打开/关闭/下发等纯虚接口,当前由 SerialDriver 实现串口收发。CommunicationManager 将驱动运行在独立子线程中,通过信号槽与主线程通信。

五、通讯协议

当前默认采用 自定义串口帧协议,结构清晰、自带校验、支持粘包处理。协议的基本格式如下:

字段名 长度(字节) 数据内容 说明
帧头 (SOF) 2 0x55, 0xAA 固定起始标志
帧计数 1 cnt 每发送一帧自增 1,用于检测丢包或乱序(溢出后自动归零)
帧 ID 2 frameId 参数 ID(沿用 CAN ID 概念),小端模式(低字节在前)
数据长度 2 len 数据字段的长度,小端模式
数据 N data 实际传输的参数数据,长度由上一字段决定
CRC16 2 crc Modbus CRC16,小端模式。校验范围:从"版本"到"数据"
帧尾 (EOF) 2 0xAA, 0x55 固定结束标志

帧ID段和数据段是必要的,帧ID段用于判定数据对应的参数,数据段是数据的载体。对于用户定义的帧ID必须大于24,这是因为对于小于等于24的ID会用于一些特殊帧(响应帧、控制帧、批量上传帧)

5.2 数据打包规则

  • 物理值与原始字节之间的转换遵循公式:physical = raw * scale + offsetValue
  • 多字节整型按 小端模式 打包与解析
  • 一个 frameId(一次下行帧)可携带多个上行参数,参数在帧内通过 offset 字段区分字节位置

5.3 特殊帧

5.3.1 上行调控帧(frameId = 0)

frameId = 0 是保留的上行调控帧,上位机专属控制帧,无对应业务参数,作用是主动指定下位机需要上传的参数ID列表,实现按需采集、筛选数据,减少无效传输。

上行调控帧不对应参数表中的任何 ID,固定ID为0,用于上位机主动告诉下位机"我需要看哪些上行数据"。数据段格式:

帧ID段(2字节) 数据段(2×N 字节)
0 按顺序排列用户勾选的 ID号,每个 ID 占 2 字节,小端打包。

如下例子所示,若我想要获取温度和速度信息,选择后发送。

则下位机将发送下述十六进制源码到下位机:

tex 复制代码
55 AA 00 00 00 04 00 03 01 04 01 99 F0 AA 55

对源码解析如下:

5.3.2 批量上传帧(frameId = 1)

依赖上行调控帧生效,是下位机的批量数据应答帧。下位机严格按照上行调控帧携带的ID顺序,将对应参数数据依次填入数据段,单帧可完成多参数批量上传,大幅提升数据传输效率。

若上位机未提前下发上行调控帧、无对应ID排序记录,收到该帧后会提示"未注册批量上传配置"。

相较于传统单ID绑定多参数的上传方式,该模式支持动态筛选采集参数,适配复杂控制系统上百级参数的调试场景,灵活性更强。

5.3.3 响应帧(frameId = 2)

下位机应答专用帧,用于确认上位机所有下行指令(普通参数、特殊帧)是否执行成功。

数据段格式 :固定2字节,存储被应答的目标帧ID,告知上位机对应指令已正常接收处理。

超时机制:上位机下发指令后,100ms内未收到下位机响应帧,判定为通讯异常并打印日志报错。

帧ID段(2字节) 数据段(2 字节)
2 需要响应的帧ID

六、下位机调用库

为了方便下位机(如 STM32)快速接入 Solar Debugger,工程的 device_library/ 文件夹提供了一套纯 C 实现的参数通讯库,无需依赖任何第三方库。

6.1 库文件概览

文件 模块 功能
serial_protocol.h/.c 串口帧协议 帧打包、帧解析、CRC16 校验
param_table.h/.c 参数管理引擎 参数注册、范围校验、数据写入、批量/单次上传
param_manager.h/.c 应用层示例 具体参数定义、UART 回调处理、周期上传逻辑

三个模块的依赖关系:param_managerparam_tableserial_protocol,自上而下调用。

6.2 serial_protocol --- 串口帧协议

负责将原始字节流解析为帧结构,或将帧数据打包为字节流。解析时采用零拷贝 方式,payload 指针直接指向接收缓冲区。

对外接口:

函数 作用
protocol_parse_frame(rx_buf, size, frame) 从接收缓冲区中解析一帧,校验帧头、帧尾、CRC16,成功返回 true
protocol_pack_frame(frame_id, data, data_len, out_buf, out_len) 将 frameId + 数据段打包为完整协议帧,自动填充帧头、帧计数、CRC、帧尾
param_table_pack_response(responded_id, out_buf, out_len) 打包响应帧(frameId=2),用于应答上位机的下行指令
crc16_modbus(data, len) 计算 Modbus CRC16 校验值

6.3 param_table --- 参数管理引擎

通用的参数注册表,将整数 ID 映射到应用变量,支持多数据类型、缩放因子、范围校验和批量上传。最多可注册 32 个参数条目。

核心概念:

  • scale(缩放因子):上行传输时,浮点变量会除以 scale 转为整数发送;上位机收到后乘以 scale 还原物理值
  • flagsPARAM_FLAG_FLOAT_TO_INT(变量是 float,传输时转整数)或 PARAM_FLAG_DIRECT_INT(直接拷贝原始字节)
  • offset(偏移):同一 ID 下多个参数通过 offset 区分字节位置(如 d 轴/q 轴电流共享 ID=258)

对外接口:

函数 作用
param_table_init(tbl, min_id) 初始化参数表,设置最小合法 ID
param_table_register(tbl, id, offset, type, var_ptr, scale, flags, min, max) 注册一个参数条目,绑定 ID 与应用变量
param_table_process(tbl, id, data, len) 处理接收到的下行数据:校验范围 → 写入绑定变量 → 置修改标志
param_table_set_upload_ids(tbl, ids, count) 设置批量上传的 ID 列表(由上行调控帧触发)
param_table_batch_upload(tbl, out_buf, out_len) 将所有注册的上传 ID 打包为一帧(frameId=1)
param_table_upload_single(tbl, id, out_buf, out_len) 将单个 ID 的数据打包为一帧
param_table_clear_modified(tbl) 清除所有参数的修改标志

6.4 param_manager --- 应用层示例

这是面向具体项目的应用层文件,演示如何使用上述两个模块。开发者可直接参考此文件进行改造。

对外接口:

函数 作用
param_init() 初始化参数表并注册所有上下行参数
param_handle(id, data, len) 处理接收到的帧:id ≥ 24 时写入参数并回复 ACK;id = 0 时配置批量上传列表
param_upload() 周期调用,将所有上行参数打包发送(需替换其中的模拟数据为真实传感器数据)

6.5 使用示例讲解

param_manager.c 为例,分三步说明库的使用方式:

第一步:初始化参数(param_init

c 复制代码
/**
 * 初始化参数管理器,注册所有参数条目
 */
void param_init(void)
{
    /* 初始化参数表,设置最小ID */
    param_table_init(&g_param_tbl, MIN_PARAM_ID);


    /**************** 上行数据 ***************/
    /* 注册电流参数(ID258,不同偏移) */
    param_table_register(&g_param_tbl, PARAM_ID_CURRENT, 0, DT_INT16,
                         &d_current_f, 0.01f, 1, -1000, 1000);
    param_table_register(&g_param_tbl, PARAM_ID_CURRENT, 2, DT_INT16,
                         &q_current_f, 0.01f, 1, -1000, 1000);

    /* 注册温度、速度、电池参数 */
    param_table_register(&g_param_tbl, PARAM_ID_TEMPERATURE, 0, DT_INT32,
                         &temperature_f, 0.01f, 1, -1000, 1000);
    param_table_register(&g_param_tbl, PARAM_ID_SPEED, 0, DT_INT16,
                         &speed_f, 0.01f, 1, -500, 500);
    param_table_register(&g_param_tbl, PARAM_ID_BATTERY, 0, DT_UINT8,
                         &battery_f, 0.1f, 1, 0, 100);
    
    /**************** 下行数据 ***************/
    /* 注册系统参数 */
    param_table_register(&g_param_tbl, PARAM_ID_EMERGENCY, 0, DT_UINT8,
                         &emergency, 1.0f, 2, 0, 1);
    param_table_register(&g_param_tbl, PARAM_ID_MODE, 0, DT_UINT8,
                         &mode, 1.0f, 2, 1, 16);
    param_table_register(&g_param_tbl, PARAM_ID_GAIN, 0, DT_FLOAT,
                         &gain, 1.0f, 2, 0.001f, 10.0f);
}

每个 register 调用将一个 ID 绑定到一个全局变量,后续收发数据时库会自动读写该变量。

第二步:接收处理(UART 回调 → param_handle

c 复制代码
/**
 * 处理接收到的参数帧
 * @note 接收到调控帧后调用,处理参数写入和批量上传ID设置。
 *       直接在接收到数据帧后调用发送响应帧,一般不建议在中断直接调用全局变量、占用硬件资源等。
 *       但考虑仅在上位机下发数据时调用,程序此时应处于调试或等待指令阶段,一般不会影响外部任务。
 *       且发送函数内部会等待发送完成,因此暂时不考虑中断安全问题。
 */
void param_handle(uint16_t id, uint8_t *data, uint16_t len)
{
    if (id >= MIN_PARAM_ID) {
        /* 处理普通参数写入 */
        param_table_process(&g_param_tbl, id, data, len);

        /* 打包响应帧 */
        param_table_pack_response(id, send_buf, &send_len);

        /* 等待串口发送完成并发送数据 */
        while (uart3_is_busy()) {
            /* 等待发送完成 */
        }
        debug_send(send_buf, send_len);

    } 
    else if (id == PARAM_ID_CONTROL)  /* 检测到上行控制帧 */
    {
        /* 处理系统参数:设置批量上传ID列表 */
        param_table_set_upload_ids(&g_param_tbl, (uint16_t *)data, len / 2);

        /* 打包响应帧 */
        param_table_pack_response(id, send_buf, &send_len);

        while (uart3_is_busy()) {
            /* 等待发送完成 */
        }
        debug_send(send_buf, send_len);

        /* 清除发送标志,允许上传 */
        send_flag = 0;
    }
}

param_handle 内部逻辑:

  • id ≥ 24:调用 param_table_process 将数据写入对应变量,然后发送 ACK 响应帧
  • id = 0:调用 param_table_set_upload_ids 配置批量上传列表

第三步:周期上传(param_upload

c 复制代码
/**
 * 上传参数(由应用层周期调用)
 */
void param_upload(void)
{
    static uint32_t tick = 0;      /* 模拟时间计数器 */
    float sin_val = 0.0f;

    tick++;

    /* 计算正弦值,周期约为 100 个 tick */
    sin_val = sinf((float)tick * 0.0628f); /* 0.0628 ≈ 2π/100 */

    /* 模拟 d_current_f:正弦波,范围 -10.0 ~ 10.0 */
    d_current_f = sin_val * 10.0f;

    /* 模拟 q_current_f:余弦波(正弦偏移90度),范围 -10.0 ~ 10.0 */
    q_current_f = sinf((float)tick * 0.0628f + 1.5708f) * 10.0f;

    /* 模拟 temperature_f:锯齿波,范围 0 ~ 100,周期 100 tick */
    temperature_f = (float)(tick % 100);

    /* 模拟 speed_f:三角波,范围 -500.0 ~ 500.0 */
    speed_f = (float)((tick % 200) - 100) * 5.0f;

    /* 模拟 battery_f:反向锯齿波,范围 0 ~ 100 */
    battery_f = 100.0f - ((float)(tick % 200) * 0.5f);

    /* 批量上传 */
    param_table_batch_upload(&g_param_tbl, send_buf, &send_len);
    
    /* 单参数上传ID为PARAM_ID_CURRENT的数据 */
    // param_table_upload_single(&g_param_tbl, PARAM_ID_CURRENT, send_buf, &send_len);


    /* 串口发送数据 */
    debug_send(send_buf, send_len);
}

在定时器中断或主循环中周期调用即可,库会自动按注册的 ID 顺序打包数据。

6.6 快速接入步骤

  1. serial_protocol.h/.cparam_table.h/.c 加入你的工程
  2. 参考 param_manager.h/.c,定义自己的全局变量和参数 ID
  3. 在初始化函数中调用 param_table_register() 注册所有参数
  4. 在 UART 接收回调中调用 protocol_parse_frame() + param_handle()
  5. 在定时任务中调用 param_upload(),将模拟数据替换为真实传感器读数

七、License

本项目以开源方式发布,欢迎学习、研究、二次开发使用。具体协议见 LICENSE(如未附带则默认遵循 MIT 协议)。

八、致谢


如果这个项目对你有帮助,欢迎 ⭐ Star 支持;问题与建议请提 Issue,PR 欢迎。

QQ:2981218238

邮箱:lsk0358@163.com