Struts2介绍

Struts2是Struts的第二代产品,以WebWork为核心,采用拦截器的机制处理用户请求,使业务逻辑控制器能与Servlet API完全脱离。Struts1采用Servlet的机制处理用户请求。

Struts 2框架的所有类都基于接口,核心接口独立于HTTP。Struts 2配置文件中的大多数配置元素都会有默认值,有助于减少在XML文件中需要进行的配置。

Struts2框架主要由三部分组成:

核心控制器(StrutsPrepareAndExecuteFilter)、业务控制器和用户定义的业务逻辑组件。

(也有核心控制器使用FilterDispatcher)

Struts2框架的处理流程

第1步:客户端浏览器发送一个请求。

第2步: web服务器如Tomcat收到该请求,读取配置文件,将请求 导向Struts2的StrutsPrepareAndExecuteFilter(核心控制器), 后者根据请求决定调用合适Action。

第3步:StrutsPrepareAndExecuteFilter在调用Action之前被Struts2的拦截器拦截,拦截器自动对请求应用通用功能,如数据转换,校验等。

第4步:调用Action的execute方法,该方法根据请求的参数来执行一定的操作。

第5步:依据Action的execute方法处理结果,导向不同的URL。如在execute中验证用户,验证成功可以导向成功的页面。否则重新登录

深入理解Struts2的配置文件

Struts2框架配置文件中的包就是由多个Action、多个拦截器、多个拦截器引用构成。包的作用和java中的类包是非常类似的,它主要用于管理一组业务功能相关的action,在实际应用中,我们应该把一组业务功能相关的action 放在同一个包下。

<struts><!-- Struts2的action必须放在一个指定的包空间下定义--><packagename="default" extends="struts-default"><!-- 定义处理请求URL为login.action的Action--><action name="login" class=action.LoginAction"><!-- 定义处理结果字符串和URL之间的映射关系--><result name="success">/success.jsp</result><result name="input">/login.jsp</result></action></package>
</struts>

2、namespace命名空间配置

考虑到同一个Web应用中需要同名的Action,Struts2以命名空间的方式来管理Action,同一个命名空间不能有同名的Action。

Struts2通过为包指定namespace属性来为包下面的所有Action指定共同的命名空间。

包student:没有指定namespace属性。默认的命名空间是""。

包admin:指定了命名空间/admin。则如上名为adminaction.LoginAction的Action,它处理的URL为:

http://localhost:8080/chapter10web/admin/adminLogin.action

3、include包含配置

在Struts2中可以将一个配置文件分解成多个配置文件,那么我们必须在struts.xml中包含其他配置文件。

<struts><includefile="struts-default.xml"/><include file="struts-student.xml"/><include file="struts-admin.xml"/><include file="struts-user.xml"/>......
</struts>

Action类文件

在Struts2中,Action不同于struts1.x中的Action。Struts2中Action并不需要继承任何控制器类型或实现相应接口。比如struts1.x中的Action需要继承Action或者DispatcherAction。

同时Struts2中的Action并不需要借助于象struts1中的ActionForm获取表单的数据。可以直接通过与表单元素相同名称的数据成员(setter-getter函数)获取页面表单数据。

虽然Struts2中的Action原则上不用继承任何类。但是一般需要实现Action接口或者继承ActionSupport类,重写execute方法。如果继承ActionSupport类,我们可以在的控制器中增加更多的功能。

定义Action类的两种形式:

1、基本形式:从ActionSupport类继承public class LoginAction extends ActionSupport{private String username; private String password;/*getter-setter代码略*/public void validate(){…}public String execute()throws Exception {….}}
2、普通JavaBeanpackage com.bean;public class User {private String username;private String password;/*getter-setter代码略*/public String execute()throws Exception {...}}

Action动态处理函数

1、Action默认的execute函数一般客户端请求url被Struts2的拦截后,根据url指定 action名称,查找相应的action,默认调用Action类的execute函数。如:
<package name="chapter10" extends="struts-default"><!-- 通过Action类处理才导航的Action定义 --><action name="Login" class="com.action.LoginAction"><result name="input">/login.jsp</result><result name="success">/success.jsp</result></action>
</package>
2、如果不用调用默认execute()。要求调用指定函数fun()函数。
第1种方法是修改配置文件,修改action标记的method属性值
<action name="userLogin" class="com.action.LoginAction"  method="fun"><result name="input">/login.jsp</result><result name="success">/success.jsp</result></action>

我经过测试下面这种方法好像不支持。

第2种方法是在页面form标记action属性中指定调用处理方法名,如Action类编写如下:
public class LoginAction{public String fun1() throws Exception{  .....    }public String fun2() throws Exception{ ......   }
}
在页面form标记的action属性中指定调用处理方法名。<form action="/login!fun1.action" method="post"><form action="/login!fun2.action" method="post">
3、使用通配符映射方式配置文件 admin_* :定义一系列请求URL是admin_*.action模式的逻辑Action<action name="admin_*" class="action.UserAction" method="{1}"><result name="input">/login.jsp</result><result name="success">/success.jsp</result></action>如上,<action name=“admin_*”>定义一系列请求URL是admin_*.action模式的逻辑Action。同时method属性值为一个表达式{1},表示它的值是name属性值中第一个*的值。
例如:用户请求URL为admin_login.action时,将调用AdminAction类的login方法;用户请求URL为admin_regist.action时,将调用到AdminAction类的regist方法。

Struts2校验框架

输入校验几乎是任何一个系统都需要开发的功能模块,我们无法预料用户如何输入,但是必须全面考虑用户输入的各种情况,尤其需要注意那些非正常输入。Struts2提供了功能强大的输入校验机制,通过Struts2内建的输入校验器,在应用程序中无需书写任何代码,即可完成大部分的校验功能,并可以同时完成客户端和服务器端的校验。

如果应用的输入校验规则特别,Struts2也允许通过重写validate方法来完成自定义校验,另外Struts2的开放性还允许开发者提供自定义的校验器。

Struts2中可以通过重写validate方法来完成输入校验。如果我们重写了validate方法,则该方法会应用于此Action中的所有提供服务的业务方法。

Struts2的输入校验流程如下:

1、类型转换器负责对字符串的请求参数执行类型转换,并将这此值设置成Action的属性值。

2、在执行类型转换过程中可能出现异常,如果出现异常,将异常信息保存到ActionContext中,conversionError拦截器负责将其封装到fieldError里,然后执行第3步;如果转换过程没有异常信息,则直接进入第3步。

3、通过反射调用validateXxx()方法,其中Xxx是即将处理用户请求的处理逻辑所对应的方法名。

4、调用Action类里的validate()方法。

5、如果经过上面4步都没有出现fieldError,将调用Action里处理用户请求的处理方法;如果出现了fieldError,系统将转入input逻辑视图所指定的视图资源。

步骤:

第1步:编写一个Action类,该Action接受页面提交过来的参数

第2步:在该Action相同的目录下建一个xml文件,该文件的命名为ActionName-validation.xml,其中ActionName为该Action的类名,例如LoginValidateAction-validation.xml。然后在xml配置文件中配置需要验证的字段。

第3步:在struts.xml文件中配置Action,在Action配置中必须有input视图

<actionname="validate"class="com.action.LoginValidateAction">

<resultname="input">/login.jsp</result>

<result>/index.jsp</result>

</action>

第4步:添加一个jsp页面loginvalidate.jsp,放入一个struts标签

1、基础的Struts2输入校验规则

<validators>对必填校验<field name="requiredValidatorField"><field-validator type="required"><message>必填内容</message></field-validator></field>必填字符串校验<field name="requiredStringValidatorField"><field-validator type="requiredstring"><param name="trim">true</param><message>字符串必填校验</message></field-validator></field>
对int类型的校验
<field name="integerValidatorField"><field-validator type="int"><param name="min">1</param><param name="max">10</param><message key="validate.integerValidatorField" /></field-validator>
</field>对日期的校验<field name="dateValidatorField"><field-validator type="date"><param name="min">01/01/1990</param><param name="max">01/01/2000</param><message key="validate.dateValidatorField" /></field-validator></field>
对email的校验<field name="emailValidatorField"><field-validator type="email"><message key="validate.emailValidatorField" /></field-validator></field>对URL的校验<field name="urlValidatorField"><field-validator type="url"><message key="validate.urlValidatorField" /></field-validator></field>
对字符串长度的校验<field name="stringLengthValidatorField"><field-validator type="stringlength"><param name="maxLength">4</param><param name="minLength">2</param><param name="trim">true</param><message key="validate.stringLengthValidatorField" /></field-validator></field>
对正则表达式的校验<field name="regexValidatorField"><field-validator type="regex"><param name="expression">.*\.txt</param><message key="validate.regexValidatorField" /></field-validator></field>

Struts2拦截器

拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。

Struts 2拦截器是动态拦截Action调用的对象。它提供了一种机制,使开发者可以定义一个特定的功能模块,这个模块可以在Action执行之前或者之后运行,也可以在一个Action执行之前阻止Action执行。同时也提供了一种可以提取Action中可重用的部分的方式。

拦截器(Interceptor)是Struts 2的核心组成部分。很多功能都是构建在拦截器基础之上的,例如文件的上传和下载、国际化、转换器和数据校验等,Struts 2利用内建的拦截器,完成了框架内的大部分操作。

Struts2的拦截器和Servlet过滤器类似。在执行Action的execute方法之前,Struts2会首先执行在struts.xml中引用的拦截器,在执行完所有引用的拦截器的intercept方法后,会执行Action的execute方法。

Struts2拦截器类必须实现Interceptor接口或继承AbstractInterceptor类。

在Struts2中称为拦截器栈InterceptorStack。拦截器栈就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序依次被调用。

步骤:

第1步:建立一个拦截器的类MyInterceptor,这里的before()和after()方法是以后拦截器会执行的方法。

第2步:我们模拟一个业务组件接口ModelInterface和一个业务组件实现类ModelImpl。

第3步:创建一个动态代理类DynamicProxy,这个类是实现InvocationHandler接口。

第4步:写个类测试一下

Web中配置方式:

第1步:创建一个拦截器的触发页面test_interceptor.jsp

第2步:定义拦截器类 MyInterceptor1.java

第3步:Struts2配置文件,拦截器的映射

第4步:通过拦截器后进入 Action

第5步:通过Action处理后的视图页面interceptorsuccess.jsp

Struts2转换器

在B/S中,将字符串请求参数转换为相应的数据类型,应该是MVC框架提供的基本功能。Struts2也提供了类型转换功能。

在Struts2中分两种转换,一种是局部转换,另一种是全局类型转换。具体转换的实施需要一个转换类和一个自定义类。

1、局部类型转换

对于int,long,double,char,float等基本类型,Struts2会自动完成类型转换,像age年龄,在输入页面是String型的,到Action后会自动转换成int型。而如果是转换成其它类类型的话,就需要自定义类型转换。这样就需要一个自定义类。要定义一个转换类,需要继承ognl.DefaultTypeConverter这个类 ,这是个类型转换的类。

步骤:

第1步: 编写转换类PointConverter.java

第2步: 编写Point类。

第3步: 编写Action类。TypeConverterAction.java
第4步:编写转换属性文件。

TypeConverterAction-conversion.properties
内容为:point=com.converter.PointConverter
自定义类、转换类、action都创建好之后,要创建一个属性文件,放置在与action在同一包。该属性文件名为:action文件名-conversion.properties。文件中的内容如下:point = 转换类名  即 point=com.PointConverter注意:
(1)  point是Action的一个属性,转换类指明所使用哪个转换类对此属性进行转换。
(2) 有两种类型的转换器:一是局部类型转换器。仅仅对某个Action的属性起作用。属性文件名:ActionName-conversion.properties 。内容:属性名=类型转换器类,如date=com.DateConverter 。存放位置与ActionName类相同路径。二是全局类型转换器。对所有Action的特定类型的属性都会生效。属性文件名:xwork-conversion.properties 。内容如java.util.Date= com.DateConverter.存放位置为WEB-INF/classes/目录下。

第5步:编写JSP页面。

类型转换的流程:

1、用户进行请求,根据请求名在struts.xml中寻找Action。

2、在Action中,根据请求域中的名字去寻找对应的set方法。找到后在赋值之前会检查这个属性有没有自定义的类型转换。没有的话,按照默认进行转换;如果某个属性已经定义好了类型转换,则会去检查在Action同一目录下的 action文件名-conversion.properties文件。

3、从文件中找到要转换的属性及其转换类。

4、然后进入转换类中,在此类中判断转换的方向。我们是先从用户请求开始的,所以这时先进入从字符串到类的转换。返回转换后的对象。流程返回Action。

5、将返回的对象赋值给Action中的属性,执行Action中的execute()。

6、执行完execute()方法,根据struts.xml的配置转向页面。

7、在jsp中显示内容时,根据页面中的属性名去调用相应的get方法,以便输出。

8、在调用get方法之前,会检查有没有此属性的自定义类型转换。如果有,再次跳转到转换类当中。

9、在转换类中再次判断转换方向,进入由类到字符串的转换,完成转换后返回字符串。

10、将返回的值直接带出到要展示的页面当中去显示。

Struts2上传下载

在Java领域中,有两个常用的文件上传项目:一个是Apache组织Jakarta的Common-FileUpload组件(http://commons.apache.org/fileupload/),另一个是Oreilly组织的COS框架(http://www.servlets.com/cos/)。利用这两个框架都能很方便的实现文件的上传。

上传文件主要是通过读写二进制流进行操作的。Form表单元素的enctype属性指定的是表单数据的编码方式。

multipart/form-data。这种编码方式的表单会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数里。

上传:

struts.xml

<action name="fileUpLoad" class="cn.hncu.user.fileupload.FileUpLoadAction"><!-- 动态设置Action中的savePath属性的值 --> <param name="savePath">/upload</param><result name="input">/jsps/uploadfile.jsp</result><result name="success">/jsps/success.jsp</result></action>

接收文件的类:

package cn.hncu.user.fileupload;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;import org.apache.struts2.ServletActionContext;import com.opensymphony.xwork2.ActionSupport;public class FileUpLoadAction extends ActionSupport{private static final int BUFFER_SIZE=20*1024;private String title;//文件标题private File upload; //上传文件对象==传文件的内容所对应的属性名,本例取:upload--前端file组件的参数名private String uploadFileName; //上传文件名=upload+"FileName"private String uploadContentType;//上传文件类型=  upload+"ContentType"private String savePath;//保存文件的目录路径(通过依赖注入)private static void copy(File src,File dest){InputStream in=null;OutputStream out=null;try {in =new BufferedInputStream(new FileInputStream(src),BUFFER_SIZE);out=new BufferedOutputStream(new FileOutputStream(dest), BUFFER_SIZE);byte[] buffer=new byte[BUFFER_SIZE];int len=0;while((len=in.read(buffer))>0){out.write(buffer,0,len);}in.close();out.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public File getUpload() {return upload;}public void setUpload(File upload) {this.upload = upload;}public String getUploadFileName() {return uploadFileName;}public void setUploadFileName(String uploadFileName) {this.uploadFileName = uploadFileName;}public String getUploadContentType() {return uploadContentType;}public void setUploadContentType(String uploadContentType) {this.uploadContentType = uploadContentType;}public String getSavePath() {return savePath;}public void setSavePath(String savePath) {this.savePath = savePath;}@Overridepublic String execute() throws Exception {//根据服务器的文件保存地址和源文件名创建目录文件全路径String destPath=ServletActionContext.getServletContext().getRealPath(this.getSavePath())+"/"+this.getUploadFileName();ServletActionContext.getServletContext().getRealPath(this.getSavePath());System.out.println(this.getSavePath());System.out.println("上传的文件的类型:"+this.getUploadContentType());System.out.println("destPath  :....."+destPath);File destFile=new File(destPath);copy(this.upload,destFile);return "success";}}

下载:

<action name="down" class="cn.hncu.user.fileupload.FileDownAction"><!-- <result name="success" >/index.jsp</result>--><result name ="success" type="stream"><param name="contentType">application/vnd.ms-word</param><param name="contentDisposition">attachment;filename="a.docx"</param><param name="inputName">downloadFile</param></result> </action>
package cn.hncu.user.fileupload;import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;import com.opensymphony.xwork2.ActionSupport;public class FileDownAction extends ActionSupport{private InputStream downloadFile;public InputStream getDownloadFile(){File file=new File("d:/a/a.docx");try {downloadFile=new FileInputStream(file);} catch (FileNotFoundException e) {}return downloadFile;}@Overridepublic String execute() throws Exception {System.out.println("进来下载文件了......");return "success";}}

Struts2标签使用

这里给出了大部分常用的标签

<%@ page language="java" pageEncoding="gb2312"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- struts2标签库调用声明 -->
<%@taglib prefix="s" uri="/struts-tags"%>
<html><head></head><body><s:form action="aa" method="post"><b>Textfield标签--单行文本输入控件</b><br/><s:textfield label="用户名" name="uname" value="admin" size="20"></s:textfield><br/><b>Textarea标签--多行文本输入控件</b><br/><s:textarea name="note" label="备注" rows="4" cols="10"></s:textarea><br/><b>Radio标签--单选按钮</b><br/><s:radio label="性别" name="sex" list="#{'male':'男','female':'女'}"></s:radio><b>checkboxlist--标签复选框</b><s:checkboxlist name="hobby" label="爱好" list="{'体育','音乐','写代码'}"></s:checkboxlist><b>Select标签--下拉列表框</b><i>使用name和list属性,list属性的值是一个列表:</i><s:select label="最终学历" name="education" multiple="true" list="{'小学','中学','本科','硕士','博士','其他'}" size="3"></s:select><b>doubleselect标签--关联HTML列表框,产生联动效果</b><s:doubleselect label="请选择居住地" name="province" doubleList="top=='安徽省'?{'合肥市','马鞍山市','芜湖市'}:{'杨浦区','浦东新区','静安区','闸北区'}" list="{'安徽省','上海市'}" doubleName="city"></s:doubleselect><b>updownselect标签--带有上下移动的按钮的列表框</b><s:updownselect name="books" label="请选择您喜欢的书籍" labelposition="top"moveUpLabel="up" moveDownLabel="down" selectAllLabel="all"list="#{'1':'数据结构原理','2':'数据库原理','3':'Java编程','4':'C语言基础' }"listKey="key" listValue="value" size="2"></s:updownselect><b>optiontransferselect标签</b><s:optiontransferselect label="最喜欢的节日" name="lakedays" doubleList="{'元旦', '圣诞节', '万圣节'}" list="{'五一劳动节', '国庆节', '春节'}" doubleName="cBook"></s:optiontransferselect></s:form></body>
</html>

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%><!-- Struts2标签库调用声明 --><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head></head><%List<Object> lists=new ArrayList<Object>();lists.add("aa");lists.add("bb");lists.add("cc");lists.add("dd");lists.add("ee");request.setAttribute("lists", lists);%><body><s:iterator value="#request.lists" var="list" status="stuts"><s:if test="#stuts.odd==true"><li><font color="red">${list}</font></li></s:if><s:else><li><s:property/> </li></s:else></s:iterator></body>
</html>

Struts2介绍(一个大的工具库)相关推荐

  1. 建立一个前端的工具库---搭建篇

    一.需求 工具公司需要,决定搭建一个组件库.为了实用,这个组件库主要需要满足以下几点需求. 能够使用npm安装,支持amd规范: 现代的前端早已经脱离了刀耕火种的时代.我们可以用npm来安装我们所需要 ...

  2. python爬取视频自动播放_介绍一个python视频处理库:moviepy

    处理视频是一个常见的需求.那么在python中如何用代码处理视频呢?最近我无意间发现了一个很好用的python视频处理库moviepy,其使用起来简单易用,而且功能比较强大,这里记录一下分享给大家. ...

  3. h5难做吗_还在担心H5太难做?介绍一个H5制作工具给你!

    H5发展越来越快,不得不让企业营销人对H5页面的未来充满了期待,越来越多的人开始在营销中运用H5页面.但是也有许多人陷入了一个难题,即没有技术怎么制作H5.其实,H5页面的制作很简单,只需要一个工具就 ...

  4. 介绍一个软件开发工具,堪称快速开发神器

    软件快速开发平台是一种软件开发工具,以通用技术架构(如MVC)为基础,集成常用建模工具.二次开发包.基础解决方案等而成.可以大幅缩减编码率,使开发者有更多时间关注客户需求,在项目的需求.设计.开发.测 ...

  5. 用python写一个ip查询工具库

    首先附上一张高清有码的图,效果如图所示. 最近喜欢上了Python,总想用python搞点事情. 写此工具的目的 最近一段时间遇见过不少钓鱼网站,于是乎就在想,这些网站的服务器都在哪里? 为何如此猖獗 ...

  6. 给大家介绍一个大文件传输软件

    软件名称: Kudaa 软件版本: V0.08 软件容量: 约2M 软件类型: 传输软件 应用平台: Windows2000,Windows XP,Windows Vista,Windows Serv ...

  7. 介绍一个生成流程图工具—解放生产力

    ChatGPT生成流程图 ChatGPT+PlantUML生成流程图 ChatGPT+PlantUML生成流程图 环境: 科学上网(或者有其他渠道可以直接访问chatgpt) PlantUML在线生成 ...

  8. (NLP) 淘宝评论处理(1)--工具库介绍

    #文章大纲及涉及的工具库 最近在接触自然语言处理的项目,对目前的情况做一个总结: 项目目标 相关工具库 自然语言处理的基本流程 进一步处理及相关的算法和知识 目前的进展以及所面临的问题 ##项目目标 ...

  9. JavaScript进阶学习(二)—— 基于原型链继承的js工具库的实现方法

    文章来源:小青年原创 发布时间:2016-07-03 关键词:JavaScript,原型链,jQuery类库 转载需标注本文原始地址: http://zhaomenghuan.github.io... ...

最新文章

  1. 基于C++的二叉树的入门讲解
  2. Ubuntu 16.04服务器版查看DHCP自动分配的IP、网关、DNS
  3. SQL Server 数据岸问题
  4. 医疗影像专用计算机,FCI医疗专用接口满足数码影像需求 -医疗电子-与非网
  5. 【Python基础】关于日期特征,你想知道操作都在这儿~
  6. Imation亏损额急剧增长 CEO仍表示“成功”
  7. 十招搞定 MySQL 大规模数据库的性能和伸缩性优化
  8. ARM的交叉编译工具
  9. xtrabackup对MySQL数据库的备份及恢复教程
  10. Java synchronized 中的while 和 notifyAll
  11. 浮点类型误区 “!=”
  12. Linux按键驱动,中断实现流程
  13. SpringMVC自学日志04(Controller 及 RestFul风格 )
  14. 观察者模式(Head First设计模式学习2)
  15. 比较HTML元素和Native组件的区别
  16. 从零实现深度学习框架——动手实现逻辑回归
  17. jquery.cookie 插件使用方法
  18. RGB在线取色器,可视化三通道颜色
  19. Word使用样式技巧:解决创建目录后出现的打印错误---超链接错误
  20. 数据结构韩顺平版(2)

热门文章

  1. c语言中sin67 怎么表示,sin68°sin67°-sin23°cos68°的值为( )A.-22B.22C.32D.1 - 作业在线问答...
  2. 渗透测试-信息打点(红队工具篇)
  3. 小程序 const moment = require('moment')_有C语言程序员说,使用移位操作代替乘除运算效率更高,真的吗?...
  4. java 银行支付接口_cbcPayment 建设银行Java支付接口详细说明文档dsdsdds - 下载 - 搜珍网...
  5. 用excel进行数据分析(一)
  6. asc量子计算机,2020ASC世界大学生超级计算机竞赛聚焦量子计算和语言智能
  7. DataEase看中国 - 中国影星“成龙”电影票房数据分析
  8. linux模拟器玩三国战记,三国战纪手游下载(街机)-三国战纪安卓官网版v0.14.30.0-Linux公社...
  9. SpringBoot+Vue实现前后端分离旅游资源信息系统
  10. 计算机dvd打不开,我的电脑DVD打不开。怎么办?