ISV接入钉钉详细示例以及代码(JAVA版本)  --服务窗代码部分放出-问答-阿里云开发者社区-阿里云

开发者社区> 问答> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

ISV接入钉钉详细示例以及代码(JAVA版本)  --服务窗代码部分放出

2016-08-13 16:22:00 56739 37
本帖和代码  持续更新,请收藏关注。
talk is cheap, show me the code
============================================




注册套件,并且授权企业
1.前提准备
        公网服务器,假设IP地址为127.0.0.1
        TOMCAT端口假设为8080
        MYSQL数据库
        (工程目前只依赖了 TOMCAT,MYSQL,这两个个中间件,如果不满足请自行解决。删除了ACTIVEMQ和REDIS,降低工程配置和维护难度)





2.部署接入工程
      1).下载代码 (代码分析见后续)
        github地址为
        https://github.com/hetaoZhong/ding-isv-access.git      开放平台对接工程
         https://github.com/hetaoZhong/ding-isv-app.git      钉钉微应用DEMO(比较简单.仅供参考。这个不推荐了。服务化的部署方式不适合代码DEMO。和 ding-isv-access工程二合一了 )


      1).新建MYSQL数据库,名称为ding_isv_access。(如果不喜欢这个名称,可自行更换)
          将 ding-isv-access 工程中的 db_sql.sql 文件执行到mysql数据库
          如果发现执行过程中出现 (创建DB和表的过程中出现Unknown character set: 'utf8mb4'),
          请升级mysql版本至>5.5,或者将db_sql.sql文件中的utf8mb4编码更改成utf8

       4).修改ding-isv-access的配置文件,auto-config.xml. 各种配置项的属性看注释自行理解
  
       5).打包编译 ding-isv-access 。 mvn clean package -Dmaven.test.skip=true
         打包之前看一下个人目录之下antx.properties是否存在,如果存在需要清理一下 rm -rf ~/antx.properties

       6).将打包好的ding-isv-access .war放入tomcat启动。
          访问 http://127.0.0.1:8080/ding-isv-access/checkpreload.htm 返回success 表示启动完毕

        7)如果提示加解密错误,请看文档升级无限制加解密包 https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7386797.0.0.A5d7BC&treeId=175&articleId=104945&docType=1

        8)如果您正在成为钉钉的ISV,请参考ISV上塔流程https://open-doc.dingtalk.com/docs/doc.htm?docType=1&articleId=106046


3.开发者平台注册套件
    1) 在开发者平台注册套件的时候,填写回调地址http://127.0.0.1:8080/ding-isv-access/suite/create
        在代码中可以发现suite/create该路径。 里面的suitesecret和token可以更换。也可以直接在开发者后台输入代码中的值


    2) 套件注册成功,在db中插入套件信息
         insert into isv_suite(id, gmt_create, gmt_modified, suite_name, suite_key, suite_secret, encoding_aes_key, token, event_receive_url)
         values(1, '2016-03-14 18:08:09', '2016-03-14 18:08:09', 'X套件', 'suitekeyX', 'suitesecretX','suiteAesKeyX', 'suiteTokenX', '');
         或者运行代码中的测试用例插入
    3)数据插入之后,修改套件地址         http://127.0.0.1:8080/ding-isv-access/suite/callback/{suiteKey} 占位符请自行替换

    4)观察服务端log.
          /home/mint/logs/ding-isv-access/ding-isv-access.log               该log记录了tomcat的ding-isv-access工程启动情况
         /home/mint/logs/ding-isv-access/biz/http_request_helper.log   该log记录了所有通过httpclient开放平台请求的http记录
          /home/mint/logs/ding-isv-access/biz/http_invoke.log               该log记录了所有通过sdk开放平台请求的http记录
         /home/mint/logs/ding-isv-access/biz/task.log                             该log记录了所有定时任务相关日志。
         /home/mint/logs/ding-isv-access/biz/suite_callback.log             该log记录了所有开放平台调用套件回调信息的日志


4.创建微应用
        在开发者后台创建微应用


5.生成先下部署二维码(该过程要求开发者后台的关联的钉钉企业必须是认证企业)
        在开发者后台生成线下部署二维码,
        第一次生成二维码的时候,建议不要勾选 开启序列号校验选项。
        之后再次生成二维码可以勾选
        生成二维码的时候,钉钉会把套件为指定的一家企业开通,开发者需要观察服务端日志。


6.二维码开生成成功,可以扫码开通套件啦


7.钉钉微应用简单DEMO
    1)代码比较简单。仅仅实现了dd.config流程。
        可以修改文件中的corpid.授权该套件的企业的corpid即可。 在调试过程中自己修改
       文件地址为https://github.com/hetaoZhong/ding-isv-app/blob/master/web/src/main/webapp/microapp.html
    2)更多demo在更新中... 前端不会,多包含
         ( DEMO更新中,见谅)


ding-isv-access接入层代码分析
1.代码结构



上图说明如下:

1. 开放平台通过 SuiteCallBack Controller提供的RPC接口推送事件。
      包括
               1)企业授权套件过程中的临时授权码,权限变更,权限解除等等,RPC接口通过 Corp Suite Auth Service处理企业授权事件
               2)企业信息发生变更的回调事件,员工变更,部门变更,群会话变更等等。


2. Suite Service, Crm Service, Dept Service等具体的业务代码,是封装了开放平台提供的http接口。
    这些 Service 接口通过各种 RestHelper向开放平台 请求数据,例如ConfOapi RestHelper。
     同时这些Service也通过springhttpinvoker向微应用业务方提供方法调用

3. SuiteTokenJob,CorpAuthFaileJob,MonitorJob 服务是定时任务.定时生成token,处理授权失败,监控等


4.ISVBizLockSerive是锁服务。





2.数据库表说明

+---------------------------------------------+
| Tables_in_ding_isv_access              |
+-----------------------------+
| QRTZ_BLOB_TRIGGERS                |         QRTZ前缀表明为spring-quarzt用到的表。全部删掉了,维护成本高。不易理解。如果ISV集群方式执行定时任务自行解决
| QRTZ_CALENDARS                        |
| QRTZ_CRON_TRIGGERS              |
| QRTZ_FIRED_TRIGGERS              |
| QRTZ_JOB_DETAILS                      |
| QRTZ_LOCKS                                 |
| QRTZ_PAUSED_TRIGGER_GRPS |
| QRTZ_SCHEDULER_STATE           |
| QRTZ_SIMPLE_TRIGGERS            |
| QRTZ_SIMPROP_TRIGGERS         |
| QRTZ_TRIGGERS                           |
| isv_app                                             |       ISV套件下的微应用信息
| isv_biz_lock                                      |       锁表。 由于系统没有采用redis之类的缓存,所以用了数据库表来实现业务上的加锁功能
| isv_corp                                            |       企业信息表
| isv_corp_app                                    |       企业开通微应用表
| isv_corp_suite_auth                         |       企业授权套件信息
| isv_corp_suite_auth_faile                |        企业授权套件过程中,失败的数据。由定时任务定时处理这个表中的数据进行重试
| isv_corp_suite_callback                   |       企业对套件的回调注册url表(当前这张表没有使用)  
| isv_corp_suite_jsapi_ticket              |        企业的jsapiticke表
| isv_corp_token                                |         企业的accesstoken表
| isv_suite                                          |         ISV套件下的套件信息
| isv_suite_ticket                                |         ISV套件的suiteticket表
| isv_suite_token                                |         ISV套件的suitetoken表.这张表的信息由定时任务定时刷新
+--------------------------------------------+



3.处理企业授权套件流程图如下.

     1.接收开放平台的临时授权码推送
     2.临时授权码做存储,db或者log
     3/4/5/6.使用临时授权码换取永久授权码。                            如果失败,计入失败表,使用定时任务做重试
     7/8/9.调用开放平台接口获取企业和微应用信息,做存储。     如果失败,计入失败表,使用定时任务做重试
     10.调用开放平台接口激活套件。                                         如果失败,计入失败表,使用定时任务做重试
     11.注册企业对套件的回调地址。                                          如果失败,计入失败表,使用定时任务做重试




更新记录
[font=-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol']一.2016.10.30更新
[font=-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol']   1.更新db,具体参照 add-10.11.sql
[font=-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol']   2.增加服务窗授权流程
[font=-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol']   3.增加授权失败的重新授权流程。具体参照 [size=; font-size: 9pt,9pt] ReAuthFailedCorpJob [font=-apple-system, BlinkMacSystemFont, "].java
[font=-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol']   4.调整部分代码结构以及日志格式













































取消 提交回答
全部回答(37)
  • eriver
    2018-07-25 10:37:10
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    楼主好,套件注册成功,钉钉平台会回调 插入数据库的代码么? 还是需要自己手动插入数据库数据?
    0 0
  • nic活着
    2018-04-08 15:54:05
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    运行的时候报错,是哪里没配置对呀?
    0 0
  • 你个流氓
    2018-01-31 15:17:07
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出

    2018-01-31 15:10:11,596 INFO  ISVBizLockServiceImpl.getISVBizLock:27 - logEvent:开始traceId:cb75f000-7a9e-4464-8f5a-f0a0d0a6026c        
    lockKey:suite_token_suitexdhgv7mn5ufoi9ui    date:Wed Jan 31 15:11:11 CST 2018
    2018-01-31 15:10:40,830 WARN  DefaultHandlerExceptionResolver.handlHttpRequestMethodNotSupported:194 - Request method 'POST' not supported




    大神是什么原因啊
    0 0
  • epicdove
    2018-01-04 15:53:41
    回 楼主蛋蛋oo蛋蛋的帖子
    大佬,最新版本的sql是不是没有更新 = = 如:isv_corp_channel_token
    0 0
  • sf象征
    2017-11-14 15:14:11
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
      123
    0 0
  • ehr_test
    2017-09-19 11:01:46
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    创建套件时候报错!!!!!!!


    com.dingtalk.oapi.lib.aes.DingTalkEncryptException: 计算解密文字错误
        at com.dingtalk.oapi.lib.aes.DingTalkEncryptor.decrypt(DingTalkEncryptor.java:149)
        at com.dingtalk.oapi.lib.aes.DingTalkEncryptor.getDecryptMsg(DingTalkEncryptor.java:94)
        at com.dingtalk.isv.access.web.controller.suite.callback.SuiteCallBackController.suiteCreate(SuiteCallBackController.java:83)
        at com.dingtalk.isv.access.web.controller.suite.callback.SuiteCallBackController$$FastClassBySpringCGLIB$$8ac1ce8d.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:700)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
        at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
        at com.dingtalk.isv.access.web.interceptor.ControllorAOP.handler(ControllorAOP.java:46)
        at sun.reflect.GeneratedMethodAccessor80.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
        at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:633)
        at com.dingtalk.isv.access.web.controller.suite.callback.SuiteCallBackController$$EnhancerBySpringCGLIB$$be104419.suiteCreate(<generated>)
        at sun.reflect.GeneratedMethodAccessor79.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:953)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:855)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:829)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:475)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:498)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:796)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1366)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)
    本人做了jce [font="]无限制权限策略文件,但还报次错,不知道为什么?急等人解决
    参照了g.alicdn.com/dingding/opendoc/docs/_isvguide/tab5.html?t=1464078310753#加解密库和demo下载
    也不行。。。。。
    有没有人解答啊!!!!
    0 0
  • 修行的蚂蚁
    2017-09-06 16:19:35
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    怎么关掉java oapi的日志 , 又不影响我的项目的日志的输出,发现client-sdk.example-1.0.0.jar,里面的log4j.xml的日志级别是WARN,会影响我项目的日志输出,求赐教啊
    0 0
  • dl恒智科技
    2017-08-24 16:09:28
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    有用好使通过的大神吗??
    0 0
  • 我们走
    2017-08-21 11:54:31
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    请问我为什么用不了 DingTalkClient   SmartworkBpmsProcessCopyRequest 这两个类呢,我少了什么包吗
    0 0
  • 红上金融
    2017-07-24 13:10:32
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    0 0
  • 一号营地
    2017-07-07 09:07:48
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    ttp://192.168.1.126:8080/ding-isv-access/checkpreload.htm
    我和8楼同问  , 编译打包好war项目后 放到本地虚拟机linux里运行 jdk1.7  tomcat7 ,tomcat启动日志没错,但是访问项目那个页面404
    0 0
  • 此昵称重复
    2017-04-24 14:51:21
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    https://github.com/ddtalk/HarleyCorp?spm=a219a.7629140.0.0.vAWbm9
    目前java版本的最新demo有问题啊,说是jdk1.7的,但是里面async-http-client-1.9.32.jar这个文件只是支持jdk1.8的,启动就报错了,谁能给我个说法?
    0 0
  • stoic
    2017-04-07 17:34:12
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    楼主,你发的这个开源和这个Demo是什么关系啊?《github.com/injekt/openapi-demo-java》

    你的是关于后台管理的demo? openapi-demo-java只是单纯的openapi的demo?
    0 0
  • kimiduan
    2017-03-15 10:58:07
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    在验证回调URL时,提示返回字符串不匹配,这么多人有这个问题,没人能回答下吗
    0 0
  • 糊涂001
    2017-03-13 00:16:23
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    小白有问题,求指教:
    阿里钉钉,客户端是怎么和自定义服务器端交互的呢。初步看了一下文档是否如下所示:



    重点是业务是否可以自定义?

    0 0
  • 正直做人
    2017-03-06 18:40:40
    Re回调验证,我的项目也是用springmvc做的
    大神,按照你代码,我写到自己程序中,测试时报错 。是因为啥呢?
    0 0
  • _zero_
    2017-02-28 13:35:50
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    回调URL验证有效性,检查成功了,为什么创建套间的时候还是 :系统错误:errorCode=-1, errMessage = 创建套件失败
    0 0
  • 傻男孩0608
    2016-12-29 14:46:45
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    请问我填写ding-isv-access/suite/create时候,为什么验证有效性总是返回:错误原因:返回字符串不匹配,求大神指教
    0 0
  • namecc
    2016-12-29 12:30:35
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    有没有PHP版本的啊?
    0 0
  • 独醉落梅
    2016-12-22 14:17:19
    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    你好,下面的意思是只需要打包 ding-isv-common 工程和 ding-isv-access里的api 模块是不是,biz模块是不需要打包的吗?


    2).打包发布 ding-isv-common 工程至maven仓库          打包发布 ding-isv-access的api model至maven仓库          (如果觉得代码曾引入jar包不爽,也可以把ding-isv-access.依赖的本地jar包上传到maven仓库)

    -------------------------

    ReISV接入钉钉详细示例以及代码JAVA版本服务窗代码部分放出
    在阿里云上,我用你的demo,在验证回调URL时,报了个返回字符串不匹配,不能创建套件,后台报了计算解密文字错误
    。但是在我们自己的服务器能创建套件
    0 0
滑动查看更多
相关问答

1

回答

钉钉开放平台服务端API的智能人事接口包含哪些内容?

2022-07-04 18:57:58 102浏览量 回答数 1

1

回答

钉钉开放平台服务端API的互联平台接口包含哪些内容?

2022-07-04 18:57:57 102浏览量 回答数 1

1

回答

钉钉开放平台服务端API的身份验证(免登)接口包含哪些内容?

2022-07-04 18:57:57 109浏览量 回答数 1

1

回答

钉钉接口获取不到部门的数据

2018-06-14 14:18:42 2863浏览量 回答数 1

1

回答

如何使用钉钉接口获取考勤数据?

2017-08-14 10:38:36 4455浏览量 回答数 1

0

回答

如何使用钉钉接口获取考勤数据?

2017-08-14 11:19:13 6219浏览量 回答数 0

0

回答

钉钉登录接口事件

2017-06-06 13:56:17 2130浏览量 回答数 0

0

回答

免登问题,钉钉登录接口事件

2017-06-06 13:47:34 3219浏览量 回答数 0

2

回答

我想用钉钉做一个企业内部的沟通接收消息用什么接口

2015-10-19 11:05:04 4016浏览量 回答数 2

1

回答

钉钉有接收消息接口吗?

2015-10-15 12:15:06 3511浏览量 回答数 1
+关注
蛋蛋oo蛋蛋
丫丫丫丫丫丫丫丫丫
0
文章
132
问答
问答排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载