ABAP基础知识 访问外部数据库-开发篇-程序员宅基地

技术标签: python  java  编程语言  mysql  数据库  

点击蓝字 关注我们

前言

前文讲解了怎么配置连接外部数据库

详见链接

无峰,公众号:ABAP 技巧与实战ABAP基础知识 访问外部数据库-配置篇

本文主要介绍通过ABAP语言访问外部数据库的几种方式

外部数据库配置

本文示例中的代码访问了两个外部数据库

MTD : 外部oracle数据库,其中示例表 ZTTEMP 字段( ZZTNO,WERKS)

S4Q : 外部HANA数据库(开发系统访问测试系统的数据库), 使用表USR02,ZTTEMP

ABAP访问外部数据库

通过ABAP访问外部数据库有四种方式.根据不同的情况,可以选择不同的方法.

  • OPEN SQL访问

  • NATIVE SQL 访问

  • ADBC(ABAP Database Connectivity)

  • AMDP ABAP Managed Database Procedures ? (未验证通过)

OPEN SQL直接访问

OPEN SQL 访问的限制条件:必须在ABAP数据字典中存在该表名,并且最好同目标系统表结构一致, 一般情况下,用来访问另外一个同版本的ECC数据库.当然,也可以把ECC的表定义语句在目标系统中创建一个同名同结构的表,然后用该方式访问.

直接访问时,在FROM TABLE 后面添加 CONNECTION s4q .

s4q是DBCO中建立的和另外一个S/4系统的连接

01

报错及处理一

可能的报错及处理方式

下图报错的原因是访问ORACLE数据库必须指定一个SCHEMA. 这个可以配置在连接参数中.

01

报错及处理二

出现下面的报错,表示系统尝试使用MANDT限制数据, 此时需要给OPEN SQL 语句添加CLIENT SPECIFIED 强制OPEN SQL 不要补充MANDT限制

NATIVE SQL访问

通过NATIVE SQL 访问外部数据库步骤

  • 打开连接

  • 执行SQL命令

  • 关闭连接

示例代码见文末

01

读取多条记录的方式

  • 游标方式     图一

  • 非游标方式 图二

非游标方式其实隐式使用了游标.性能比游标方式要差.数据量小的时候看不出来. 大量数据读取就能看出二者的性能差异了. 

图三是标准帮助中提示的优劣比较.

图一

图二

图三

ADBC访问

ADBC(ABAP Database Connectivity) 是SAP提供的原生SQL(Native SQL)接口API.可以通过ADBC执行任何数据库的原生SQL语句.

ABAP中有个标准的DEMO程序: ADBC_DEMO 演示了各种SQL语句的调用方式.图四的代码示例给出了SELECT语句的常用写法.

大概需要如下的过程

  • 创建默认数据库的链接对象

  • 创建一个查询对象

  • 基于sql语句创建一个结果对象

  • 定义传递结果集一个数据对象-内表

  • 获取数据内容

  • 关闭连接

  • 赋值数据到内表

示例代码详见文末

通过AMDP 访问?

AMDP ABAP Managed Database Procedures

标准ABAP帮助体系中提到访问外部数据库的方法中还有一种AMDP方式.为了完善本文,补充了一个AMDP访问外部数据库表的示例.(验证未通过)

尝试中发现AMDP只适用与HANA数据库. 并且尝试通过DEMO程序 DEMO_AMDP_CONNECTION 连接外部数据库S4Q. 尝试失败. (报错见图四)

感觉AMDP 并不支持连接外部数据库. 

图五中示例代码的连接 是 R/3*开头. 帮助中说S/3*是SERVICE CONNECT. 但是不理解有什么用处.

图四

图五

图六

总结

ABAP访问外部数据库的几种方式中. OPEN SQL 最简单,但是有很大局限性. NATIVE方式最简单易懂. 性能也最好. 但是不利于代码动态化. ADBC 可以动态的实现数据的读取及内表的赋值.

前文DB02 SQL编辑器SQL语句自动生成报表 就采用了ADBC访问数据库的方法: 根据语句动态定义选择屏幕,动态定义内表, 读取的数据写入内表呈现.

详见链接

无峰,公众号:ABAP开发技巧SAP工具箱之一键生成报表

该工具在付费文章中可以获取.

文中通过AMDP方式连接外部数据库的验证失败. 不推荐使用. 其它三种方式根据实际情况选择使用就好.

示例代码详见文末.

THE

END

约定

如果你对这篇文章感兴趣,请帮忙点赞,在看,分享.       

    (如果你真的喜欢这篇文章,请记得回来打个赏,作为支持我继续下去的动力,这是一个正反馈过程. 越多的人打赏,作者越有动力分享,读者就能享受更多的福利.毕竟打赏的金额富不了我,穷不了你,却能支持这个公众号长久发文.)

公众号 : syjf1976_abap

          ABAP开发技巧

微信号 : 392077

请微信联系管理员: 

syjf1976 

sharry_xlp  

Yannick_Duan 申请进入公众号讨论群

示例代码,OPEN SQL:

*&---------------------------------------------------------------------*
*& Report ZTS_SQL_DBCO
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zts_dbco_opensql.


PARAMETERS: p_s4  RADIOBUTTON GROUP ra1,
            p_ora RADIOBUTTON GROUP ra1.


START-OF-SELECTION.
  CASE 'X'.
    WHEN p_s4.
*访问另一个S4系统的同名表
*需要注意的是,目标系统CLIENT和当前CLIENT 很可能不一样, 所以需要加上CLIENT SPECIFIED 避免CLIENT不同干扰数据获取
      SELECT * FROM usr02  CLIENT SPECIFIED  INTO  TABLE @DATA(lt_usr02)  BYPASSING BUFFER CONNECTION R/3*S4Q
        WHERE bname = '00177'.
      ."可以获取数据
      DATA(lv_subrc) = sy-subrc .
      cl_demo_output=>write( lv_subrc ).
      cl_demo_output=>write( lt_usr02 ).
      cl_demo_output=>display(  ).
    WHEN p_ora.
*访问另一个其它系统的同名表
*如果ABAP表有MANDT , 目标表没有, 则需要添加CLIENT SPECIFIED 避免系统自动添加MANDT 的限制条件,导致报错:字段MANDT不存在
      DATA: BEGIN OF ls_temp,
              zztno(30),
              werks(4),
            END OF ls_temp.
      DATA: lt_temp LIKE TABLE OF ls_temp.


      SELECT zztno,werks FROM zttemp CLIENT SPECIFIED CONNECTION mtd
         INTO TABLE @lt_temp.


      cl_demo_output=>display( lt_temp ).
  ENDCASE.
*  COMMIT CONNECTION s4q. "在连接中提交.

示例代码 NATIVESQL

*&---------------------------------------------------------------------*
*& Report ZTS_SQL_DBCO
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zts_dbco_nativesql.


PARAMETERS:
  p_1 RADIOBUTTON GROUP ra1,
  p_2 RADIOBUTTON GROUP ra1,
  p_3 RADIOBUTTON GROUP ra1,
  p_4 RADIOBUTTON GROUP ra1.


DATA: BEGIN OF gs_temp,
        zztno(30),
        werks(4),
      END OF gs_temp.
DATA: gt_temp LIKE TABLE OF gs_temp.
DATA conn TYPE dbcon-con_name.


INITIALIZATION.
  %_p_1_%_app_%-text = 'SELECT方式一:DO循环读取游标,添加内表'.
  %_p_2_%_app_%-text = 'SELECT方式二:通过例程添加内表'.
  %_p_3_%_app_%-text = 'UPDATE:更新表内容'.
  %_p_4_%_app_%-text = 'INSERT:写入表内容'.


START-OF-SELECTION.
  conn = 'MTD'.
  "检查连接是否已经打开
  EXEC SQL.
    SET CONNECTION :conn
  ENDEXEC.
  IF sy-subrc <> 0. "如果连接没有打开, 打开连接
    EXEC SQL.
      CONNECT TO :conn
    ENDEXEC.
  ENDIF.


*两种方式: 方式一性能好于方式二
  CASE 'X'.
    WHEN p_1.
      PERFORM frm_method_1. "SELECT方式一:DO循环读取游标,添加内表'.
    WHEN p_2.
      PERFORM frm_method_2. "SELECT方式二:通过例程添加内表'.
    when p_3.
      perform frm_update.
    when p_4.
      perform frm_insert.
  ENDCASE.


  "关闭数据库连接
  EXEC SQL.
    DISCONNECT :CONN
  ENDEXEC.


*输出结果
  CASE 'X'.
    WHEN p_1 or p_2.
      cl_demo_output=>display( gt_temp ).
  ENDCASE.
*&---------------------------------------------------------------------*
*& Form FRM_METHOD_1
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_method_1 .
  DATA: ls_temp LIKE gs_temp,
        lt_temp LIKE TABLE OF ls_temp.
  "执行SQL语句:通过open dbcur打开游标
  EXEC SQL.
    OPEN dbcur FOR
    SELECT zztno,werks FROM zttemp
  ENDEXEC.
  "循环通过游标读取记录
  " 两种赋值方式:
  " 1.按字段顺序赋值,select 字段与 INTO 字段顺序必须一致
  "   FETCH NEXT dbcur INTO :ls_TEMP-ZZTNO,:LS_TEMP-WERKS
  " 2.按结构整体赋值:select 字段必须与结构字段顺序一致,且字段长度一致.
  "   FETCH NEXT dbcur INTO :ls_TEMP
  DO.
    EXEC SQL.
      FETCH NEXT dbcur INTO :ls_TEMP-ZZTNO,:LS_TEMP-WERKS
    ENDEXEC.
    IF sy-subrc <> 0.
      EXIT.
    ELSE.
      APPEND ls_temp TO lt_temp.
    ENDIF.
  ENDDO.
  "关闭游标
  EXEC SQL.
    CLOSE dbcur
  ENDEXEC.
  gt_temp[] = lt_temp[].
ENDFORM.
*&---------------------------------------------------------------------*
*& Form FRM_METHOD_2
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_method_2 .
  conn = 'MTD'.
  "检查连接是否已经打开
  EXEC SQL.
    SET CONNECTION :conn
  ENDEXEC.
  IF sy-subrc <> 0. "如果连接没有打开, 打开连接
    EXEC SQL.
      CONNECT TO :conn
    ENDEXEC.
  ENDIF.
*注意:工作区 gs_temp 内表 gt_temp 必须是全局变量
  EXEC SQL PERFORMING FRM_FILL_DATA.
    SELECT zztno,werks FROM zttemp INTO :GS_TEMP
  ENDEXEC.


ENDFORM.
FORM frm_fill_data.
  APPEND gs_temp TO gt_temp.


ENDFORM.
*&---------------------------------------------------------------------*
*& Form FRM_UPDATE
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_update .
  DATA: lv_werks(4).
  lv_werks = '1002'.
  EXEC SQL.
    UPDATE ZTTEMP SET WERKS = :LV_WERKS
     WHERE WERKS = '1003'
  ENDEXEC.
  IF sy-subrc = 0.
*提交数据更新
    EXEC SQL.
      COMMIT WORK
    ENDEXEC.
    DATA: lv_msg(50).
    lv_msg = '更新成功记录数:' && sy-dbcnt .
    cl_demo_output=>display(  lv_msg ).
  ELSE.
    cl_demo_output=>display( '更新失败' ).
  ENDIF.
ENDFORM.
*&---------------------------------------------------------------------*
*& Form FRM_INSERT
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_insert .
  DATA: lv_werks(4).
  lv_werks = '1002'.
  EXEC SQL.
    INSERT INTO ZTTEMP VALUES ('4502',:LV_WERKS)
  ENDEXEC.
  IF sy-subrc = 0.
*提交数据更新
    EXEC SQL.
      COMMIT WORK
    ENDEXEC.
    DATA: lv_msg(50).
    lv_msg = '写入成功记录数:' && sy-dbcnt .
    cl_demo_output=>display(  lv_msg ).
  ELSE.
    cl_demo_output=>display( '写入失败' ).
  ENDIF.
ENDFORM.

示例代码 ADBC

*&---------------------------------------------------------------------*
*& Report ZTS_DBCO_ADBC
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zts_dbco_adbc.


DATA: BEGIN OF gs_temp,
        zztno(30),
        werks(4),
      END OF gs_temp.
DATA: gt_temp LIKE TABLE OF gs_temp.
DATA conn TYPE dbcon-con_name.
DATA: gv_sql TYPE string.


START-OF-SELECTION.
  gv_sql = 'SELECT zztno,werks FROM zttemp'.
  PERFORM frm_get_data_adbc_simple.
*  PERFORM frm_get_data_adbc.
  cl_demo_output=>display( gt_temp ).
*&---------------------------------------------------------------------*
*& Form FRM_GET_DATA_ADBC
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
*& -->  p1        text
*& <--  p2        text
*&---------------------------------------------------------------------*
FORM frm_get_data_adbc .
  DATA: r_adbc_conn   TYPE REF TO  cl_sql_connection,
        r_adbc_query  TYPE REF TO  cl_sql_statement,
        r_metadata    TYPE REF TO  data,
        it_metadata   TYPE         adbc_rs_metadata_descr_tab,
        lv_len        TYPE         i,
        lv_off        TYPE         i,
        wa_metadata   LIKE LINE OF it_metadata,
        r_adbc_result TYPE REF TO  cl_sql_result_set,
        r_tabletype   TYPE REF TO  cl_abap_tabledescr,
        r_cxadbc      TYPE REF TO  cx_dba_adbc,
        r_cxsql       TYPE REF TO  cx_sql_exception,
        tabix_n(4)    TYPE n,
        column_names  TYPE HASHED TABLE OF adbc_name WITH UNIQUE KEY table_line.
  DATA:         lv_stmt_type      TYPE string.
  DATA:
    ex_structdescr TYPE REF TO  cl_abap_structdescr,
    ex_result_ref  TYPE REF TO data.
*获取sql语句的类型
  lv_stmt_type = cl_hdb_sql_executor=>get_statement_type( gv_sql ).
*创建默认数据库的链接对象
  r_adbc_conn    = cl_db6_con=>get_connection( 'MTD' ).
*创建一个查询对象
  r_adbc_query   = r_adbc_conn->create_statement( ).
*基于sql语句创建一个结果对象
  r_adbc_result  = r_adbc_query->execute_query( gv_sql  ).
*获取结果集合的字段名
  it_metadata = r_adbc_result->get_metadata( ).
*使用结果集合的字段信息,创建一个数据对象-结构
  r_metadata = r_adbc_result->get_struct_ref( md_tab = it_metadata   p_strict = abap_false ).
*创建一个数据对象-内表
  ex_structdescr ?= cl_abap_typedescr=>describe_by_data_ref( r_metadata ).
  r_tabletype     = cl_abap_tabledescr=>create( p_line_type  = ex_structdescr
                                                p_table_kind = cl_abap_tabledescr=>tablekind_std ).


  CREATE DATA ex_result_ref TYPE HANDLE r_tabletype.
*传递结果集一个数据对象-内表
  r_adbc_result->set_param_table( itab_ref = ex_result_ref ).
*获取数据内容
  r_adbc_result->next_package( EXPORTING upto = 100 ).
*关闭连接
  r_adbc_result->close( ).
*赋值数据到内表
  FIELD-SYMBOLS: <fs_itab> TYPE STANDARD TABLE.
  ASSIGN ex_result_ref->* TO <fs_itab>.
  MOVE-CORRESPONDING <fs_itab> TO gt_temp.
ENDFORM.


FORM frm_get_data_adbc_simple .
  DATA: r_adbc_conn   TYPE REF TO  cl_sql_connection,
        r_adbc_query  TYPE REF TO  cl_sql_statement,
        r_metadata    TYPE REF TO  data,
        it_metadata   TYPE         adbc_rs_metadata_descr_tab,
        lv_len        TYPE         i,
        lv_off        TYPE         i,
        wa_metadata   LIKE LINE OF it_metadata,
        r_adbc_result TYPE REF TO  cl_sql_result_set,
        r_tabletype   TYPE REF TO  cl_abap_tabledescr,
        r_cxadbc      TYPE REF TO  cx_dba_adbc,
        r_cxsql       TYPE REF TO  cx_sql_exception,
        tabix_n(4)    TYPE n,
        column_names  TYPE HASHED TABLE OF adbc_name WITH UNIQUE KEY table_line.
  DATA:         lv_stmt_type      TYPE string.
  DATA:
    ex_structdescr TYPE REF TO  cl_abap_structdescr,
    ex_result_ref  TYPE REF TO data.


*创建默认数据库的链接对象
  r_adbc_conn    = cl_db6_con=>get_connection( 'MTD' ).
*创建一个查询对象
  r_adbc_query   = r_adbc_conn->create_statement( ).
*基于sql语句创建一个结果对象
  r_adbc_result  = r_adbc_query->execute_query( gv_sql  ).


*定义
  DATA: lr_ref LIKE REF TO gt_temp.
  CREATE DATA lr_ref .
*传递结果集一个数据对象-内表
  r_adbc_result->set_param_table( itab_ref = lr_ref ).
*获取数据内容
  r_adbc_result->next_package( EXPORTING upto = 100 ).
*关闭连接
  r_adbc_result->close( ).
*赋值数据到内表


  gt_temp = lr_ref->*.
ENDFORM.
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/syjf1976/article/details/120320522

智能推荐

python色卡识别_用Python帮小姐姐选口红,人人都是李佳琦-程序员宅基地

文章浏览阅读502次。原标题:用Python帮小姐姐选口红,人人都是李佳琦 对于李佳琦,想必知道他的女生要远远多于男生,李佳琦最早由于直播向广大的网友们推荐口红,逐渐走红网络,被大家称作“口红一哥”。不可否认的是,李佳琦的直播能力确实很强,他能够抓住绝大多数人的心理,让大家喜欢看他的直播,看他直播推荐的口红适不适合自己,色号适合什么样子的妆容。为了提升效率,让自己的家人或者女友能够快速的挑选出合适自己妆容的口红色号,今..._获取口红品牌 及色号,色值api

linux awk命令NR详解,linux awk命令详解-程序员宅基地

文章浏览阅读3.6k次。简介awk命令的名称是取自三位创始人Alfred Aho 、Peter Weinberger 和 Brian Kernighan姓名的首字母,awk有自己的程序设计语言,设计简短的程序,读入文件,数据排序,处理数据,生成报表等功能。awk 通常用于文本处理和报表生成,最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk抽取信息后,才能进行其他文本操作。awk 通常以文件的一行为处理单位..._linux awk nr

android 网络连接失败!failed to connect to /192.168.1.186(port 8080)_failed to connect to 192.168.88.218:80-程序员宅基地

文章浏览阅读1.3w次,点赞5次,收藏2次。在网上找了一个小时,一直没有头绪,因为上个星期还是好好的,最后看到一个大神的解答,只需要将防火墙关闭就好了.原本向测试功能的,却卡在了登录上.以此记录.另外好像还有种错误是电脑与手机连接的WiFi不同,也可以看看...._failed to connect to 192.168.88.218:80

matlab 多径衰落,利用MATLAB仿真多径衰落信道.doc-程序员宅基地

文章浏览阅读1.9k次。利用MATLAB仿真多种多径衰落信道摘要:移动信道的多径传播引起的瑞利衰落,时延扩展以及伴随接收过程的多普勒频移使接受信号受到严重的衰落,阴影效应会是接受的的信号过弱而造成通信的中断:在信道中存在噪声和干扰,也会是接收信号失真而造成误码,所以通过仿真找到衰落的原因并采取一些信号处理技术来改善信号接收质量显得很重要,这里利用MATLAB对多径衰落信道的波形做一比较。一,多径衰落信道的特点关于多径衰落..._matlab多径衰落工具箱

python对json的操作及实例解析_import json灰色-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏17次。Json简介:Json,全名 JavaScript Object Notation,是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。(来自百度百科)python关于json文_import json灰色

mysql实现MHA高可用详细步骤_mysql mha超详细教程-程序员宅基地

文章浏览阅读1.1k次,点赞6次,收藏3次。一、工作原理MHA工作原理总结为以下几条:(1) 从宕机崩溃的 master 保存二进制日志事件(binlog events);(2) 识别含有最新更新的 slave ;(3) 应用差异的中继日志(relay log) 到其他 slave ;(4) 应用从 master 保存的二进制日志事件(binlog events);(5) 通过Manager控制器提升一个 slave 为新 m..._mysql mha超详细教程

随便推点

Linux环境下主从搭建心得(高手勿喷)_linux的java主从策略是什么-程序员宅基地

文章浏览阅读194次。一 java环境安装:1 安装JDK 参考链接地址:https://blog.csdn.net/qq_42815754/article/details/82968464注:有网情况下直接 yum 一键安装:yum -y list java(1)首先执行以下命令查看可安装的jdk版本(2)选择自己需要的jdk版本进行安装,比如这里安装1.8,执行以下命令:yum install -y java-1.8.0-openjdk-devel.x86_64(3)安装完之后,查看安装的jdk 版本,输入以下指令_linux的java主从策略是什么

ACM第四题_acm竞赛题 i 'm from mars-程序员宅基地

文章浏览阅读104次。定义int 类型,由while实现A,B的连续输入,输出A+B的值按Ctrl Z结束循环。#include&amp;lt;iostream&amp;gt;using namespace std;int main(){ int A,B; while(cin&amp;gt;&amp;gt;A&amp;gt;&amp;gt;B) { cout&amp;lt;&amp;lt;A+B&amp;lt;&_acm竞赛题 i 'm from mars

TextView.SetLinkMovementMethod后拦截所有点击事件的原因以及解决方法-程序员宅基地

文章浏览阅读5.2k次。在需要给TextView的某句话添加点击事件的时候,我们一般会使用ClickableSpan来进行富文本编辑。与此同时我们还需要配合 textView.setMovementMethod(LinkMovementMethod.getInstance());方法才能使点击处理生效。但与此同时还会有一个问题:如果我们给父布局添加一个点击事件,需要在点击非链接的时候触发(例如RectclerV..._linkmovementmethod

JAVA实现压缩解压文件_java 解压zip-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏31次。JAVA实现压缩解压文件_java 解压zip

JDK8 新特性-Map对key和value分别排序实现_java comparingbykey-程序员宅基地

文章浏览阅读1.3w次,点赞7次,收藏21次。在Java 8 中使用Stream 例子对一个 Map 进行按照keys或者values排序.1. 快速入门 在java 8中按照此步骤对map进行排序.将 Map 转换为 Stream 对其进行排序 Collect and return a new LinkedHashMap (保持顺序)Map result = map.entrySet().stream() .sort..._java comparingbykey

GDKOI2021普及Day1总结-程序员宅基地

文章浏览阅读497次。第一次参加GDKOI,考完感觉还可以,结果发现还是不行,有一些地方细节打错,有些失分严重,总结出以下几点:1.大模拟一定要注意,细节打挂就是没分,像T1就是一道大模拟题,马上切了,后面就没想着检查以下,导致有些地方挂掉了,用民间数据一测,才85分。2.十年OI一场空,不开longlonglong longlonglong见祖宗。今天的T2本来想用暴力水点分的,结果没想到longlong→intlong long\to intlonglong→int,40→040\to040→0。3.代码实现能力太差,_gdkoi

推荐文章

热门文章

相关标签