收藏 分享(赏)

struts2框架.ppt

上传人:暖洋洋 文档编号:1608374 上传时间:2018-08-11 格式:PPT 页数:96 大小:478.50KB
下载 相关 举报
struts2框架.ppt_第1页
第1页 / 共96页
struts2框架.ppt_第2页
第2页 / 共96页
struts2框架.ppt_第3页
第3页 / 共96页
struts2框架.ppt_第4页
第4页 / 共96页
struts2框架.ppt_第5页
第5页 / 共96页
点击查看更多>>
资源描述

1、Struts2框架,什么是框架?,什么是框架? 框架,即framework。其实就是某种应用的半成品,就是一组组件,供开发系统使用,它们相互协作实现某一功能或行为。而且,框架一般是成熟的,不断升级的软件。,什么是框架?,“框架的强大之处不是源自它能让你做什么,而是它不能让你做什么。”(Rickard)。 Rickard解释框架为:框架使混乱的东西变得结构化。 Web应用程序框架则鼓励开发人员使用一系列框架所提供的基础类和类库,从而避免杂乱的JSP所造成的混乱。,软件组件根据性质不同大致分为两类: 业务逻辑组件:直接处理业务的组件,由于业务性质的不同不大可能在不同的系统中重用 应用服务组件:如决

2、定程序流向的控制、输入的校验、错误处理及标签库等这些只与程序相关的组件,在不同的系统中可以很好地得到重用,组件的种类,传统JSP Web应用紧耦合Model,传统JSP Web应用松耦合Model,传统MVC模式与Web应用程序中的MVC模式,MVC模式(传统的单机中的MVC模式)MVC模式设法减少可复用的域模型与显示代码之间的联系。它通过在显示层与域对象之间引入一个控制器来实现。 控制器处理来自显示层的事件,并将事件映射为域模型的改动。 控制器通过注册显示层以获得域模型被改动的通知,从而令显示层能被更新。,客户端,控制器,视图,模型,视图事件,交互,改动模型,声明改动事件,刷新视图,传统MV

3、C模式的事件流,传统MVC模式与Web应用程序中的MVC模式,传统MVC模式的问题: 在Web世界中,视图是在客户端浏览器中生成的,而控制器和模型则在服务器端。 传统MVC模式在HTTP和HTML世界中是行不通的,使用HTTP请求/响应模式的Web应用程序需要一个与传统MVC模式截然不同的设计,这个设计借用了传统MVC模式的名字和一些方式。,传统MVC模式与Web应用程序中的MVC模式,传统MVC模式的问题(续): 在Web版的MVC中,视图不能直接调用控制器,但是可以基于web请求映射成不同的URL。 视图不是一个可被更新的对象,而是在客户端发出新请求的时候随之重新呈现的Web页面。 模型也

4、不能将自身的改变通知视图,因为视图动态呈现在不同机器的用户浏览器中。因此视图每次都要依据最新的数据重新生成。,传统MVC模式与Web应用程序中的MVC模式,传统MVC模式的更新:前端控制器(Front Controller) 在Web世界中MVC应用程序是通过使用前端控制器实现的。 该模式包含一个分发器(在Java的web MVC实现中,使用Servlet 来实现分发器)。 分发器将URL请求映射成需要执行的命令实例(command instance,它是action- Struts的基本要素)。 action与系统后端的服务(服务组合成模型)进行交互。 命令实例处理完业务逻辑后返回一个码值,

5、码值映射到一个视图。 最后,结合控制器和模型,视图呈现给用户。 通常视图会使用标签库,以便更简单地访问数据。,客户端,控制器,视图,模型,刷新视图,Web应用程序中的MVC事件流,分发器,执 行,改 动 模 型,呈现,请求,Struts简介,Struts是一个基于Sun J2EE平台的MVC框架,是MVC模式的一种实现。 Struts继承了MVC的各项特性,并根据J2EE的特点,做了相应的变化与扩展。 Struts能充分满足应用开发的需求,简单易用,敏捷迅速,颇受关注。,Struts的结构,业务逻辑:业务逻辑更新模型的状态,并帮助控制应用程序的流程。就 Struts 而言,业务逻辑是由 Act

6、ion 类完成的。 Action 类封装了具体的处理过程,调用业务逻辑模块,并将响应提交给合适的视图组件以产生响应。,Struts的结构,View(视图): 视图就是一个 JSP 文件。其中没有流程逻辑,没有业务逻辑,也没有模型信息只有标记。标记是使 Struts 有别于其他框架(如 Velocity)的因素之一。,Struts的结构,控制类: (1)将客户请求映射到相应的Action类,如果该类第一次收到请求,服务器将实例化该类。 (2)调用Action实例的exectue()方法。 (3) exectue()方法返回一个result code说明执行结果的String。 result co

7、de 并非一定对应一个将被执行的result(如JSP页面、Servlet或另一个Action),尽管通常如此。,Struts开发步骤,开发Struts实例的步骤(以Eclipse环境为例) 将相应的支持包引入开发环境 定制web特性 开发action类 配置action 使用action,Struts2概述,Struts2是现今最流行的Web框架之一,是基于MVC模式构建的一种先进的WEB解决方案。 Struts2中使用IOC和AOP最先进的设计思想,使得控制器与容器最大程度的解耦,让系统的核心业务代码与表示层分离。 Struts2的提供了如国际化,类型转换,数据验证,ajax等一系列优越的

8、功能。,Struts2概述,从根本上讲,Struts2是一个MVC框架。 Struts2除了是一个MVC框架之外,还是一个小巧的轻量级容器。让你可以创建某些特性,以便让其它程序员从你所构建的独一无二的容器中收益。,容器能够做什么,Java运行环境中包括多种类型应用程序的组件,Java EE产品必须支持这些组件。 Java容器是为相应类型的应用程序组件提供所需服务的运行时环境。 同种类型的不同产品容器在很多方面有差异,如成本、性能和支持等。一个容器的关键是为了装入相应的程序并为代码提供特有的特性。 一个容器拥有越多有用的特性,则该容器就越好。,Struts2的轻量级容器,Servlet容器提供了

9、规范要求的特性,或者说提供了容器开发者所实现的特性。其它Java EE容器也是如此。 轻量级容器则不同,它允许为一个基本的容器增加新的特性,使其符合特定应用程序的要求。 比如,要为程序员提供简单的事务支持,但是不要用EJB,则可以将事务支持特性加入到轻量级容器中,使装入容器的项目受益。,Xwork和Struts2之间交互图,Struts,Servlet 分发器,Servlet 响应,HTTP 请求,5,6,4,HTTP 响应,XWork,2,3,1,用户的 Action (构件在 Struts和 XWork 基础上),请求到达分发 器中转换成 action command,响应在Servlet

10、 响应中转换成 Web识别的响应,StrutsPrepareAndExecuteFilter,Interceptor,Action,Result,Jsp/html,用户请求,Struts2内置的一些拦截器或用户自定义拦截器,用户编写的action类,类似struts1中的Action,类似struts1中的forward,响应,Struts2处理流程,Struts2特点,1.无需与Servlet API整合,更容易测试 2.优雅的请求参数封装 3.灵活的视图技术 4.丰富的表达式语言 5.机动,灵活,简单的配置 6.线程安全的控制器(Action) 7.众多的插件,Struts2相对于Stru

11、ts1的优势,Struts2的应用无需依赖于Servlet API和Struts API Struts2提供了拦截器,利用拦截器可以进行AOP编程,实现如权限拦截等功能 Strut2提供了类型转换器,我们可以把特殊的请求参数转换成需要的类型。 Struts2的输入校验可以对指定方法进行校验,解决了Struts1长久之痛。 提供了全局范围、包范围和Action范围的国际化资源文件管理实现,Struts应用的支持包,从apache网站上下载struts2.0.14的完整包(http:/struts.apache.org/downloads.html ),解压后需要找到下列几个文件: commons

12、-logging-1.0.4.jar 日志包 freemarker-2.3.8.jar Struts2的UI标签模板ognl-2.6.11.jar 对象图导航语言 struts2-core-2.0.14.jar Struts2框架核心类库xwork-2.0.7.jar Struts的基础底层组件,Struts2开发步骤,开发Struts实例的步骤(以Eclipse环境为例) 将Struts2的支持包引入开发环境(Struts2.1.6以后的版本必须加入commons-fileupload-1.2.1.jar包) 编写Action类,注意必须从com.opensymphony.xwork2.Ac

13、tionSupport类继承或实现action接口 编写相应的输入输出jsp页面代码 编写struts.xml配置Action类及动作 在web.xml中加入Struts2 MVC框架启动配置,Struts2开发实例,在Eclipse下创建一个Tomcat项目 创建和编写Action类: 这一步和Struts1.x相似。只是Struts1.x中的动作类必须从Action类中继承,而Struts2.x的动作类需要从com.opensymphony.xwork2.ActionSupport类继承。下面是计算两个整数和的Action类,代码FirstAction.java (应放在WEB-INF/s

14、rc/mystruts下)为:,package mystruts; import com.opensymphony.xwork2.ActionSupport; public class FirstAction extends ActionSupport private int operand1;private int operand2;public String execute() throws Exceptionif (getSum() = 0) / 如果代码数和是非负整数,跳到positive.jsp页面return “positive“; else / 如果代码数和是负整数,跳到nega

15、tive.jsp页面return “negative“;public int getOperand1()return operand1;,public void setOperand1(int operand1)System.out.println(operand1);this.operand1 = operand1;public int getOperand2()return operand2;public void setOperand2(int operand2)System.out.println(operand2);this.operand2 = operand2;public in

16、t getSum()return operand1 + operand2; / 计算两个整数的代码数和 ,关于Action类,所有的action类必须实现action接口,或从ActionSupport派生; execute()方法在struts2执行action时调用,通常的业务逻辑在其中处理; execute()方法必须返回result code,SUCCESS、INPUT、ERROR等都定义在action接口中。, 输入操作数 求代数和,在sum.jsp中使用了Struts2带的tag。在Struts2中已经将Struts1.x的好几个标签库都统一了,在Struts2中只有一个标签库/s

17、truts-tags。但在使用Struts2的标签时,在中最好都使用Struts2标签,尽量不要用HTML或普通文本。,入口sum.jsp(位于项目根目录),positive.jsp (位于项目根目录),显示代数和 代数和为非负整数 ,negative.jsp (位于项目根目录), 显示代数和 代数和为负整数 ,配置struts.xml文件,在Eclipse中将struts2-blank-2.0.14.zip里标准struts.xml文件拷贝到WEB-INF/src目录下并作如下修改: /positive.jsp/negative.jsp,关于struts.xml中Action的默认值: 如果

18、没有为action指定class,默认是ActionSupport。 如果没有为action指定method,默认执行action中的execute() 方法。 如果没有指定result的name属性,默认值为success。,Action配置的各项默认值,定制web特性,在web.xml文件中,配置在web应用中使用struts2的一些特性。 Struts2.0与WebWork2.2以上的版本类似,所有的配置被整合在一个Filter里面,该Filter位于org.apache.struts2.dispatcher.FilterDispatcher,因此,在web.xml中应该这样声明:, s

19、truts2 org.apache.struts2.dispatcher.FilterDispatcher struts2 /* ,完成并测试,在Eclipse中将struts2-blank-2.0.14.zip里标准web.xml文件拷贝到WEB-INF目录下,无需作任何修改。Done! 现在可以测试新建的Struts2项目,在浏览器中打开: http:/127.0.0.1:8080/struts2_exercise/sum.jsp,程序的运行,在jsp代码的第2行标签库定义将前缀s和uri struts-tags建立了对应关系;前缀s指明了所有的struts标签在使用的时候均以:s开头,是

20、使用了自定义property标签的JSP页面; property标签中含有一个value属性值,通过设置value值,标签可以从action中获得相应表达式的内容。 因为在action中创建了一个getSum()方法,value值为sum的property标签将会得到getSum()方法调用后的返回值。,怎样处理一个form多个submit,在很多Web应用中,为了完成不同的工作,一个HTML form标签中可能有两个或多个submit按钮,如下面的代码所示: 使用Struts2 Action的execute方法就无法判断用户点击了哪一个提交按钮 Struts1.2.9之前的版本需要使用一个L

21、ookupDispatchAction动作来处理含有多个submit的form。但使用LookupDispatchAction动作需要访问属性文件,还需要映射,比较麻烦 。,主页面multiple_submit.jsp:My Struts2 multiple_sbmit.jsp starting page在multiple_submit.jsp中有两个submit:保存和打印。其中分别通过method属性指定了要调用的方法:save和print。因此,在Action类中必须要有save和print方法。,怎样处理一个form多个submit,结果显示页面result.jsp:提交结果$resu

22、lt,怎样处理一个form多个submit,实现Action类:package mystruts; import javax.servlet.http.*; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.interceptor.*; public class MultipleSubmitAction extends ActionSupport implements ServletRequestAware private String msg; private javax.servlet.http.

23、HttpServletRequest request; / 获得HttpServletRequest对象 public void setServletRequest(HttpServletRequest request) this.request = request; / 处理save submit按钮的动作 public String save() throws Exception request.setAttribute(“result“, “成功保存“ + msg + “); return “save“; ,怎样处理一个form多个submit,实现Action类(续):/ 处理prin

24、t submit按钮的动作 public String print() throws Exception request.setAttribute(“result“, “成功打印“ + msg + “); return “print“; public String getMsg() return msg; public void setMsg(String msg) this.msg = msg; 上面的代码需要注意如下两点: 1. save和print方法必须存在,否则会抛出NoSuchMethodException异常。 2. Struts2 Action动作中的方法和Struts1.x

25、Action的execute不同,只使用Struts2 Action动作的execute方法无法访问request对象,因此,Struts2 Action类需要实现一个Struts2自带的拦截器来获得request对象,拦截器为: org.apache.struts2.interceptor.ServletRequestAware,怎样处理一个form多个submit,配置struts.xml:/result.jsp /result.jsp ,怎样处理一个form多个submit,怎样获取内置对象,方法一,通过ServletActionContext.类直接获取:public String r

26、sa() throws ExceptionHttpServletRequest request = ServletActionContext.getRequest();ServletContext servletContext = ServletActionContext.getServletContext();request.getSession() HttpServletResponse response = ServletActionContext.getResponse();return “scope“; ,怎样获取内置对象,方法二,实现指定接口,由struts框架运行时注入:publ

27、ic class HelloWorldAction implements ServletRequestAware, ServletResponseAware, ServletContextAwareprivate HttpServletRequest request;private ServletContext servletContext;private HttpServletResponse response;public void setServletRequest(HttpServletRequest req) this.request=req;public void setServl

28、etResponse(HttpServletResponse res) this.response=res;public void setServletContext(ServletContext ser) this.servletContext=ser; ,怎样给Action附带属性参数,在struts2中还可以为action指定一个或多个参数。在struts1.x中可以使用标签的parameter属性为其指定一个action参数,如果要指定多个,就只能通过逗 号(,)或其他的分隔符将不同的参数隔开。而在struts2中可以通过标签指定任意多个参数。代码如下: value1 value2 /

29、result.jsp 注意:一定要要action类中定义相应的setter方法,怎样实现动态action方法调用,如果Action中存在多个方法时,我们可以使用!+方法名调用指定方法。如下: public class HelloWorldActionprivate String message;public String execute() throws Exceptionthis.message = “我的第一个struts2应用“;return “success“;public String other() throws Exceptionthis.message = “第二个方法“;re

30、turn “success“; 假设访问上面action的URL路径为: /struts/test/helloworld.action 要访问action的other() 方法,我们可以这样调用:/struts/test/helloworld!other.action,怎样实现动态action方法调用,/WEB-INF/page/hello.jsppublic class HelloWorldActionprivate String message;public String execute() throws Exceptionthis.message = “我的第一个struts2应用“;r

31、eturn “success“;public String other() throws Exceptionthis.message = “第二个方法“;return “success“; 要访问other()方法,可以通过这样的URL访问:/test/helloworld_other.action,怎样接收请求参数,采用基本类型接收请求参数(get/post) 在Action类中定义与请求参数同名的属性,struts2便能自动接收请求参数并赋予给同名属性。 请求路径: http:/localhost:8080/test/view.action?id=78 public class Produ

32、ctAction private Integer id;public void setId(Integer id) /struts2通过反射技术调用与请求参数同名的属性的setter方法来获取请求参数值this.id = id;public Integer getId() return id;,怎样接收请求参数,采用复合类型接收请求参数 请求路径: http:/localhost:8080/test/view.action?product.id=78public class ProductAction private Product product;public void setProduct

33、(Product product) this.product = product; public Product getProduct() return product; Struts2首先通过反射技术调用Product的默认构造器创建product对象,然后再通过反射技术调用product中与请求参数同名的属性的setter方法来获取请求参数值。,类型转换,客户端浏览器只能将字符串传送到服务器(文件上传除外),而服务器端java语言是强类型语言,数据类型非常丰富,这就造成B/S两端的类型不兼容问题. 传统做法: 将客户端数据转变成int int v = Integer.parseInt(re

34、quest.getParameter(v); 缺陷: 过程烦琐枯燥.,类型转换,Struts2提供了强有力的表现层类型转换机制,无需程序员过多干预即可自动完成转换. Struts能自动处理类型转换过程中出现的未知异常. 下面的数据类型会自动转换: boolean,char,int,long,float,double基础类型,包括封装类型和对应数组. Date,String数组,元素类型为String的List,类型转换,类型转换器是一个继承自StrutsTypeConverter (抽象类)的类,必须重写两个方法: public Object convertFromString(Map con

35、text, String values, Class toClass) 从字符串转换成目标类型 public String convertToString(Map context, Object o) 将目标类型转换成字符串 注:上面的两个方法都必须重写.,数据验证,验证器和转换器是一对兄弟,他们总是如影随形. 所有用户的输入都是邪恶的 先类型转换,再验证 Struts2主要通过验证框架来完成数据验证. Action必须继承ActionSupport,验证日期范围 验证双精度/整型/长整型数值范围 邮箱验证 验证字段关系 必填项验证 字符串长度验证 URL验证 正则表达式验证(Regular

36、Expression) 正则表达式能解决绝大部分验证问题,数据验证,使用validate方法验证数据,建立一个主页面(validate.jsp) 验证数据 输入内容: ,可以用Struts2的tag:、和,分别用来显示动作错误信息,字段错误信息,和动作信息。如果信息为空,则不显示。,使用validate方法验证数据,Action类定义:package action; import javax.servlet.http.*; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.interceptor.*;

37、 public class ValidateAction extends ActionSupport private String msg; public String execute() System.out.println(SUCCESS); return SUCCESS; public void validate() if(!msg.equalsIgnoreCase(“hello“) System.out.println(INPUT);,使用validate方法验证数据,使用validate方法验证数据,this.addFieldError(“msg.hello“, “必须输入hello

38、!“); this.addActionError(“处理动作失败!“); else this.addActionMessage(“提交成功“); public String getMsg() return msg; public void setMsg(String msg) this.msg = msg; ,使用Validation框架验证数据,我们可以为每一个单独的Action定义一个验证文件 验证文件格式:Action类名-validation.xml 也可以为Action的方法定义一个验证文件: 必须先为方法配置action验证文件格式: Action类名-name-validatio

39、n.xml 注:验证文件要和Action类放在同一包中,使用Validation框架验证数据练习,1、建立Action类(NewValidateAction.java) package mystruts2; import com.opensymphony.xwork2.ActionSupport; public class NewValidateAction extends ActionSupport private String msg; / 必须输入 private int age; / 在13和20之间 public String getMsg() return msg; public

40、void setMsg(String msg) this.msg = msg; public int getAge() return age; public void setAge(int age) this.age = age; ,使用Validation框架验证数据练习,2、配置Action类,struts.xml的代码如下: /validate_form.jsp /validate_form.jsp ,使用Validation框架验证数据练习,3、编写验证规则配置文件和struts1.x中的validator框架的验证规则配置文件类似。但一般放到和要验证的.class文件在同一目录下,而

41、且配置文件名要使用如下两个规则中的一个来命名: -validation.xml -validation.xml 其中就是struts.xml中的name属性值。在本例中使用第一种命名规则,所以文件名是NewValidateAction-validation.xml。文件的内容如下:,使用Validation框架验证数据练习, 请输入信息 13 20 必须在 13至20之间 ,使用Validation框架验证数据练习,4、编写数据录入JSP页validate_form.jsp: “ 验证数据 ,使用Validation框架验证数据练习,在上面的程序中还使用了一个styles.css来定制错误信息

42、的风格。代码如下: .label font-style:italic; .errorLabel font-style:italic; color:red; .errorMessage font-weight:bold; color:red; 需要在Web根目录中建立一个styles目录,并将styles.css 放到 styles目录中。,OGNL,OGNL Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的

43、结构图,实现字段类型转化等功能。Struts2默认的表达式语言是OGNL,70,OGNL的符号-#,# 访问OGNL上下文和Action上下文,#相当于ActionContext.getContext() . 示例见第5页 构造Map 示例见第6页 用于过滤和投影(projecting)集合 ?所有匹配选择逻辑的元素 只提取符合选择逻辑的第一个元素 $只提取符合选择逻辑的最后一个元素, - ,List list = new ArrayList(); list.add(new Student(1, “张三“, new Date(); list.add(new Student(2, “李四“, n

44、ew Date(); list.add(new Student(3, “王八“, new Date(); pageContext.setAttribute(“list“, list);,返回张三,王八返回张三返回王八,OGNL的主要功能,支持对象方法调用 调用保存在pageContext中的Student对象的doSomeThing() N语法 从ValueStack的第N个位置开始取值 top关键字 取出最顶端的对象 示例:从ValueStack中的第0个位置开始取出最顶部的对象,返回为”阿拉伯联合酋长国”,Struts标签分类,导入taglib,控制标签,if elseif else it

45、erator append merge generator subset sort,条件标签 ifelseifelse,主要属性: test : 测试条件 示例恭喜,您中了一等奖!恭喜,您中了二等奖!欢迎惠顾!,迭代标签 iterator,用于循环数组,集合和Map 属性 value:数组,集合或Map var:当前元素 status:当前元素的状态 index,count,even,odd,first,last 示例,遍历List遍历Map,迭代标签 iterator,集合合并标签 append和merge,将多个集合合并成一个集合 属性 var 新集合的名称 示例merge用法和appen

46、d相同,结果的组织不一样.,字符串分割标签 generator,将一个字符串按照指定分隔符分割成一个字符串数组 属性 separator: 分隔符 val(value): 字符串 count:字符串數組中元素個數 示例,集合子集标签 subset,从集合中取出一个子集,注意:子集被放在值栈的顶部,subset标签执行完后子集自动移除 属性 source: 集合 start: 起始索引 count: 子集中元素个数 示例,排序标签 sort,根据定义的排序规则,对集合中的元素进行排序,排序后生成的新的集合放在值栈顶部,标签结束后自动删除. 属性: comparator: 实现Comparator

47、的类 source: 集合 示例:,数据标签,set push bean date debug include url param property,设置变量标签 set,用于设置一个新变量,并将新变量放入指定的作用域中 属性 Var: 新变量的引用 Value:值 示例注:如果不指定,默认放在Stack Context中,栈顶置值标签 push,将某个值置于值栈的顶部,标签结束后,将从值栈中移除. 属性 value: 即值 示例,javabean创建标签 bean,用于创建javabean的实例,如果设置了var值,则存于Stack Context,否则,标签结束后自动移除. 属性 name

48、: javabean类名(带包) var:名称 示例1 张三好 2009-9-9,日期格式化标签 date,格式化日期并输出 属性 name:日期 format:格式化字符串 示例,调试标签 debug,用于当前环境下Value Stack和stack context中保存的数据,资源包含标签 include,用于将一个html,jsp或servlet包含到当前页面中,使用param传递参数 属性 value:包含页面名称 示例 ,URL生成标签 url,生成URL地址,可以通过param标签传递请求参数,如果参数为中文,自动编码 属性 value:url值 action:action名称,自动加上.action 示例中华人民共和国“链接,数据标出标签 property,用于输出值栈,Stack Context中的数据 属性 value:要输出的值 如果输出值栈中的值,不用# 如果输出Stack Context中的值,要用# 如果省略value,表示取出值栈的栈顶对象输出,OGNL总结,OGNL是标签的基础,要理解OGNL的基本使用,在struts2中,OGNL必须和标签紧密配合 控制标签包含一组条件判断标签和一组集合遍历和操作标签 数据标签包含一组数据访问相关的标签,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 专业基础教材

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报