热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringDataSolr搜索引擎进行开发教程

Solr安装与配置1.1Solr的介绍大多数搜索引擎应用都必须具有某种搜索功能,问题是搜索功能往往是巨大的资源消耗并且它们由于沉重的数据库加载而拖垮你的应用的性能

Solr安装与配置


1.1 Solr的介绍


  1. 大多数搜索引擎应用都必须具有某种搜索功能,问题是搜索功能往往是巨大的资源消耗并且它们由于沉重的数据库加载而拖垮你的应用的性能。这就是为什么转移负载到一个外部的搜索服务器是一个不错的主意,Apache Solr是一个流行的开源搜索服务器,它通过使用类似REST的HTTP API,这就确保你能从几乎任何编程语言来使用solr。

  2. Solr是一个开源搜索平台,用于构建搜索应用程序。 它建立在Lucene(全文搜索引擎)之上。 Solr是企业级的,快速的和高度可扩展的。使用Solr构建的应用程序非常复杂,可提供高性能。

  3. 为了在CNET网络的公司网站上添加搜索功能,YonikSeely于2004年创建了Solr。并在2006年1月,它成为Apache软件基金会下的一个开源项目。并于2016年发布最新版本Solr6.0,支持并行SQL查询的执行。 Solr可以和Hadoop一起使用。由于Hadoop处理大量数据,Solr帮助我们从这么大的源中找到所需的信息。不仅限于搜索,Solr也可以用于存储目的。像其他NoSQL数据库一样,它是一种非关系数据存储和处理技术。总之,Solr是一个可扩展的,可部署,搜索/存储引擎,优化搜索大量以文本为中心的数据。

1.2 Solr的安装


  1. 安装 Tomcat,解压缩即可。

  2. 解压 solr。

  3. 把 solr 下的dist目录solr-4.10.3.war部署到 Tomcat\webapps下(去掉版本号)。

  4. 启动 Tomcat解压缩 war 包

  5. 把solr下example/lib/ext 目录下的所有的 jar 包,添加到 solr 的工程中(\WEB-INF\lib目录下)。

  6. 创建一个 solrhome 。solr 下的/example/solr 目录就是一个 solrhome。复制此目录到D盘改名为solrhome

  7. 关联 solr 及 solrhome。需要修改 solr 工程的 web.xml 文件。

<env-entry><env-entry-name>solr/home</env-entry-name><env-entry-value>D:\tomcat_solr\solrhome</env-entry-value><env-entry-type>java.lang.String</env-entry-type>
</env-entry>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

8 . 启动 Tomcat

这里写图片描述


1.3中文分析器IK Analyzer


1.3.1 IK Analyzer简介

  • IK Analyzer 是一个开源的,基亍 java 语言开发的轻量级的中文分词工具包。从 2006年 12 月推出 1.0 版开始,IKAnalyzer 已经推出了 4 个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从 3.0 版本开始,IK 发展为面向 Java 的公用分词组件,独立亍Lucene 项目,同时提供了对 Lucene 的默认优化实现。在 2012 版本中,IK 实现了简单的分词歧义排除算法,标志着 IK分词器从单纯的词典分词向模拟语义分词衍化。

1.3.2 IK Analyzer配置步骤

  1. 把IKAnalyzer2012FF_u1.jar 添加到 solr 工程的 lib 目录下

  2. 创建WEB-INF/classes文件夹 把扩展词典、停用词词典、配置文件放到 solr 工程的 WEB-INF/classes 目录下。 
    这里写图片描述

  3. 修改 Solrhome 的 schema.xml 文件,配置一个 FieldType,使用 IKAnalyzer

<fieldType name="text_ik" class="solr.TextField"><analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
fieldType>

  • 1
  • 2
  • 3
  • 4

1.4 配置域

域相当于数据库的表字段,用户存放数据,因此用户根据业务需要去定义相关的Field(域),一般来说,每一种对应着一种数据,用户对同一种数据进行相同的操作。

域的常用属性: 
• name:指定域的名称 
• type:指定域的类型 
• indexed:是否索引 
• stored:是否存储 
• required:是否必须 
• multiValued:是否多值


1.4.1域

修改solrhome的schema.xml 文件 设置业务系统 Field(每个field对应一个表数据的字段,以下是根据数据库的一个表字段来设置的)

"item_goodsid" type="long" indexed="true" stored="true"/>
"item_title" type="text_ik" indexed="true" stored="true"/>
"item_price" type="double" indexed="true" stored="true"/>
"item_image" type="string" indexed="false" stored="true" />
"item_category" type="string" indexed="true" stored="true" />
"item_seller" type="text_ik" indexed="true" stored="true" />
"item_brand" type="string" indexed="true" stored="true" />

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

1.4.2复制域

复制域的作用在于将某一个Field中的数据复制到另一个域中(拥有多个域的字段,常用于多条件查询,就是一个输入框内,可以根据标题来搜索,也可以根据品牌名来搜索)

"item_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>
source="item_title" dest="item_keywords"/>
source="item_category" dest="item_keywords"/>
source="item_seller" dest="item_keywords"/>
source="item_brand" dest="item_keywords"/>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.4.3动态域

当我们需要动态扩充字段时,我们需要使用动态域。比如对于商品规格的值是不确定的,所以我们需要使用动态域来实现。需要实现的效果如下: 
这里写图片描述

配置:

"item_spec_*" type="string" indexed="true" stored="true" />

  • 1

Spring Data Solr入门


2.1 Spring Data Solr简介

虽然支持任何编程语言的能力具有很大的市场价值,你可能感兴趣的问题是:我如何将Solr的应用集成到Spring中?Spring Data Solr就是为了方便Solr的开发所研制的一个框架,其底层是对SolrJ(官方API)的封装。


2.2 Spring Data Solr入门小Demo


2.2.1 搭建工程

(1) 创建maven工程,pom.xml中引入依赖

<dependencies><dependency><groupId>org.springframework.datagroupId><artifactId>spring-data-solrartifactId><version>1.5.5.RELEASEversion>dependency> <dependency><groupId>org.springframeworkgroupId><artifactId>spring-testartifactId><version>4.2.4.RELEASEversion>dependency><dependency><groupId>junitgroupId><artifactId>junitartifactId><version>4.9version>dependency>dependencies>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

(2) 在src/main/resources下创建 applicationContext-solr.xml


<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:solr="http://www.springframework.org/schema/data/solr"xsi:schemaLocation="http://www.springframework.org/schema/data/solr http://www.springframework.org/schema/data/solr/spring-solr-1.0.xsdhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><solr:solr-server id="solrServer" url="http://127.0.0.1:8080/solr" /><bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate"><constructor-arg ref="solrServer" />bean>
beans>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.2.2 @Field 注解

创建一个TbItem实体类,属性使用@Field注解标识 。 如果属性与配置文件定义的域名称不一致,需要在注解中指定域名称。

public class TbItem implements Serializable{@Fieldprivate Long id;@Field("item_title")private String title;private String sellPoint;@Field("item_price")private BigDecimal price;private Integer stockCount;private Integer num;private String barcode;@Field("item_image")private String image;private Long categoryid;private String status;private Date createTime;private Date updateTime;private String itemSn;private BigDecimal costPirce;private BigDecimal marketPrice;private String isDefault;@Field("item_goodsid")private Long goodsId;private String sellerId;private String cartThumbnail;@Field("item_category")private String category;@Field("item_brand")private String brand;private String spec;@Field("item_seller")private String seller;@Dynamic@Field("item_spec_*")private Map specMap;public Map getSpecMap() {return specMap;}public void setSpecMap(Map specMap) {this.specMap = specMap;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title == null ? null : title.trim();}public String getSellPoint() {return sellPoint;}public void setSellPoint(String sellPoint) {this.sellPoint = sellPoint == null ? null : sellPoint.trim();}public BigDecimal getPrice() {return price;}public void setPrice(BigDecimal price) {this.price = price;}public Integer getStockCount() {return stockCount;}public void setStockCount(Integer stockCount) {this.stockCount = stockCount;}public Integer getNum() {return num;}public void setNum(Integer num) {this.num = num;}public String getBarcode() {return barcode;}public void setBarcode(String barcode) {this.barcode = barcode == null ? null : barcode.trim();}public String getImage() {return image;}public void setImage(String image) {this.image = image == null ? null : image.trim();}public Long getCategoryid() {return categoryid;}public void setCategoryid(Long categoryid) {this.categoryid = categoryid;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status == null ? null : status.trim();}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime = updateTime;}public String getItemSn() {return itemSn;}public void setItemSn(String itemSn) {this.itemSn = itemSn == null ? null : itemSn.trim();}public BigDecimal getCostPirce() {return costPirce;}public void setCostPirce(BigDecimal costPirce) {this.costPirce = costPirce;}public BigDecimal getMarketPrice() {return marketPrice;}public void setMarketPrice(BigDecimal marketPrice) {this.marketPrice = marketPrice;}public String getIsDefault() {return isDefault;}public void setIsDefault(String isDefault) {this.isDefault = isDefault == null ? null : isDefault.trim();}public Long getGoodsId() {return goodsId;}public void setGoodsId(Long goodsId) {this.goodsId = goodsId;}public String getSellerId() {return sellerId;}public void setSellerId(String sellerId) {this.sellerId = sellerId == null ? null : sellerId.trim();}public String getCartThumbnail() {return cartThumbnail;}public void setCartThumbnail(String cartThumbnail) {this.cartThumbnail = cartThumbnail == null ? null : cartThumbnail.trim();}public String getCategory() {return category;}public void setCategory(String category) {this.category = category == null ? null : category.trim();}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand == null ? null : brand.trim();}public String getSpec() {return spec;}public void setSpec(String spec) {this.spec = spec == null ? null : spec.trim();}public String getSeller() {return seller;}public void setSeller(String seller) {this.seller = seller == null ? null : seller.trim();}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253

2.2.3 增加(修改)

创建测试类TestTemplate.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locatiOns="classpath:applicationContext-solr.xml")
public class TestTemplate {@Autowiredprivate SolrTemplate solrTemplate;@Testpublic void testAdd(){TbItem item=new TbItem();item.setId(1L);item.setBrand("华为");item.setCategory("手机");item.setGoodsId(1L);item.setSeller("华为2号专卖店");item.setTitle("华为Mate9");item.setPrice(new BigDecimal(2000)); solrTemplate.saveBean(item);solrTemplate.commit();}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.2.4 按主键查询

@Testpublic void testFindOne(){TbItem item = solrTemplate.getById(1, TbItem.class);System.out.println(item.getTitle());}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.2.5 按主键删除

@Testpublic void testDelete(){solrTemplate.deleteById("1");solrTemplate.commit();}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.2.6 分页查询

首先循环插入100条测试数据

@Testpublic void testAddList(){List list=new ArrayList();for(int i=0;i<100;i++){TbItem item=new TbItem();item.setId(i+1L);item.setBrand("华为");item.setCategory("手机");item.setGoodsId(1L);item.setSeller("华为2号专卖店");item.setTitle("华为Mate"+i);item.setPrice(new BigDecimal(2000+i)); list.add(item);}solrTemplate.saveBeans(list);solrTemplate.commit();}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

编写分页查询测试代码:

@Testpublic void testPageQuery(){Query query=new SimpleQuery("*:*");query.setOffset(20);//开始索引(默认0)query.setRows(20);//每页记录数(默认10)ScoredPage page = solrTemplate.queryForPage(query, TbItem.class);System.out.println("总记录数:"+page.getTotalElements());List list = page.getContent();showList(list);} //显示记录数据private void showList(List list){ for(TbItem item:list){System.out.println(item.getTitle() +item.getPrice());} }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.2.7 条件查询

Criteria 用于对条件的封装:

@Testpublic void testPageQueryMutil(){ Query query=new SimpleQuery("*:*");Criteria criteria=new Criteria("item_title").contains("2");criteria=criteria.and("item_title").contains("5"); query.addCriteria(criteria);//query.setOffset(20);//开始索引(默认0)//query.setRows(20);//每页记录数(默认10)ScoredPage page = solrTemplate.queryForPage(query, TbItem.class);System.out.println("总记录数:"+page.getTotalElements());List list = page.getContent();showList(list);}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.2.8 删除全部数据

@Testpublic void testDeleteAll(){Query query=new SimpleQuery("*:*");solrTemplate.delete(query);solrTemplate.commit();}

推荐阅读
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
author-avatar
jystmj-2009
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有