操作ES只需要发送请求就可以,相对来说整合起来比较简单

一、9300:TCP

spring-data-elasticsearch:transport-api.jar;
springboot 版本不同, transport-api.jar 不同,不能适配es 版本
7.x 已经不建议使用,8 以后就要废弃

二、9200:HTTP

1.JestClient:非官方,更新慢
2.RestTemplate:模拟发HTTP 请求,ES 很多操作需要自己封装,麻烦
3.HttpClient:同上
4.Elasticsearch-Rest-Client:官方RestClient,封装了ES 操作,API 层次分明,上手简单(最终选择)

1.导入依赖

版本要跟自己的ES版本对应
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.4.2</version>
</dependency>

导入后注意依赖版本,因为是springboot项目会规定ES版

需要规定ES版本

<properties><java.version>1.8</java.version><elasticsearch.version>7.4.2</elasticsearch.version></properties>

2.编写配置类

使用spring data elasticsearch 也需要配置连接信息等

编写配置给容器中注入一个RestHighLevelClient 对象

@Configuration
public class XmallElasticSearchConfig {//在请求ES时携带请求头信息public static final RequestOptions COMMON_OPTIONS;static {RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();// builder.addHeader("Authorization", "Bearer " + TOKEN);// builder.setHttpAsyncResponseConsumerFactory(//         new HttpAsyncResponseConsumerFactory//                 .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));COMMON_OPTIONS = builder.build();}@Beanpublic RestHighLevelClient esRestClient(){//如果是多个ES 可以配置多个HostRestClientBuilder builder = RestClient.builder(new HttpHost("101.43.122.84", 9200,"http"));return new RestHighLevelClient(builder);}}

3.编写测试类


@RunWith(SpringRunner.class)
@SpringBootTest
public class XmallSearchApplicationTests {//注入ES对象@AutowiredRestHighLevelClient client;/*** 测试往ES中存储数据*/@Testpublic void indexData() throws IOException {IndexRequest indexRequest = new IndexRequest("users");indexRequest.id("1"); //数据的id/** 要保存的数据*/user user = new user();user.setUsername("辛鹏");user.setAge(18);user.setGender("男");String s = JSON.toJSONString(user);indexRequest.source(s, XContentType.JSON);//同步的方式保存数据IndexResponse index = client.index(indexRequest, XmallElasticSearchConfig.COMMON_OPTIONS);//响应数据System.out.println(index);}@Dataclass user{private String username;private String gender;private Integer age;}@Testpublic void contextLoads() {System.out.println(client);}}

4.根据业务来新增Mapping(映射关系)

ES区别于关系型数据库的地方,宽表设计,不能去考虑数 据库范式。

  • index: 默认true,如果为false,表示该字段不会被索引,但是检索结果里面有,但字段本身不能 当做检索条件。**

  • doc_values
    默认true,设置为false,表示不可以做排序、聚合以及脚本操作,这样更节省磁盘空间。
    还可以通过设定doc_values 为true,index 为false 来让字段不能被搜索但可以用于排序、聚
    合以及脚本操作:**


PUT product
{"mappings": {"properties": {"skuId": {"type": "long"},"spuId": {"type": "keyword"  //精确匹配},"skuTitle": {"type": "text","analyzer": "ik_smart"},"skuPrice": {"type": "keyword"},"skuImg": {"type": "keyword","index": false,      //不被检索"doc_values": false  },"saleCount": {"type": "long"},"hasStock": {"type": "boolean"},"hotScore": {"type": "long"},"brandId": {"type": "long"},"catalogId": {"type": "long"},"brandName": {"type": "keyword","index": false,"doc_values": false},"brandImg": {"type": "keyword","index": false,"doc_values": false},"catalogName": {"type": "keyword","index": false,"doc_values": false},"attrs": {"type": "nested","properties": {"attrId": {"type": "long"},"attrName": {"type": "keyword","index": false,"doc_values": false},"attrValue": {"type": "keyword"}}}}}
}

nested数据类型

如果存储的是数组的话,此数据会被扁平化处理,检索会出现误差,使用nested类型可以使用嵌入式数据,让数据不被扁平化处理

检索实现

检索条件分析

  1. 全文检索:skuTitle-》keyword
  2. 排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
  3. 过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs
  4. 聚合:attrs

完整查询条件
完整查询条件可能会有很多
keyword=小米
&sort=saleCount_desc/asc
&hasStock=0/1
&skuPrice=400_1900
&brandId=1
&catalog3Id=1
&attrs=1_3G:4G:5G
&attrs=2_骁龙845
&attrs=4_高清屏

可以将所有的查询条件封装成一个VO对象

package cn.cloud.xmall.search.vo;import lombok.Data;import java.util.List;/*** @Description: 封装页面所有可能传递过来的查询条件,检索关键字,三级分类Id...* @author: Freedom* @QQ: 1556507698* @date:2022/3/17 21:18*/
@Data
public class SearchParam {/*** 页面传递过来的全文匹配关键字*/private String keyword;/*** 品牌id,可以多选*/private List<Long> brandId;/*** 三级分类id*/private Long catalog3Id;/*** 排序条件:sort=price/salecount/hotscore_desc/asc*/private String sort;/*** 是否显示有货*/private Integer hasStock;/*** 价格区间查询*/private String skuPrice;/*** 按照属性进行筛选*/private List<String> attrs;/*** 页码*/private Integer pageNum = 1;/*** 原生的所有查询条件*/private String _queryString;
}
package com.xunqi.gulimall.search.service.impl;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xunqi.common.es.SkuEsModel;
import com.xunqi.common.utils.R;
import com.xunqi.gulimall.search.config.GulimallElasticSearchConfig;
import com.xunqi.gulimall.search.constant.EsConstant;
import com.xunqi.gulimall.search.service.MallSearchService;
import com.xunqi.gulimall.search.vo.AttrResponseVo;
import com.xunqi.gulimall.search.vo.SearchParam;
import com.xunqi.gulimall.search.vo.SearchResult;
import com.xunqi.gulimall.search.feign.ProductFeignService;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** @Description:* @Created: with IntelliJ IDEA.* @author: 夏沫止水* @createTime: 2020-06-13 14:19**/@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {@Autowiredprivate RestHighLevelClient esRestClient;@Resourceprivate ProductFeignService productFeignService;@Overridepublic SearchResult search(SearchParam param) {//1、动态构建出查询需要的DSL语句SearchResult result = null;//1、准备检索请求SearchRequest searchRequest = buildSearchRequest(param);try {//2、执行检索请求SearchResponse response = esRestClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);//3、分析响应数据,封装成我们需要的格式result = buildSearchResult(response,param);} catch (IOException e) {e.printStackTrace();}return result;}/*** 构建结果数据* 模糊匹配,过滤(按照属性、分类、品牌,价格区间,库存),完成排序、分页、高亮,聚合分析功能* @param response* @return*/private SearchResult buildSearchResult(SearchResponse response,SearchParam param) {SearchResult result = new SearchResult();//1、返回的所有查询到的商品SearchHits hits = response.getHits();List<SkuEsModel> esModels = new ArrayList<>();//遍历所有商品信息if (hits.getHits() != null && hits.getHits().length > 0) {for (SearchHit hit : hits.getHits()) {String sourceAsString = hit.getSourceAsString();SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);//判断是否按关键字检索,若是就显示高亮,否则不显示if (!StringUtils.isEmpty(param.getKeyword())) {//拿到高亮信息显示标题HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");String skuTitleValue = skuTitle.getFragments()[0].string();esModel.setSkuTitle(skuTitleValue);}esModels.add(esModel);}}result.setProduct(esModels);//2、当前商品涉及到的所有属性信息List<SearchResult.AttrVo> attrVos = new ArrayList<>();//获取属性信息的聚合ParsedNested attrsAgg = response.getAggregations().get("attr_agg");ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {SearchResult.AttrVo attrVo = new SearchResult.AttrVo();//1、得到属性的idlong attrId = bucket.getKeyAsNumber().longValue();attrVo.setAttrId(attrId);//2、得到属性的名字ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();attrVo.setAttrName(attrName);//3、得到属性的所有值ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());attrVo.setAttrValue(attrValues);attrVos.add(attrVo);}result.setAttrs(attrVos);//3、当前商品涉及到的所有品牌信息List<SearchResult.BrandVo> brandVos = new ArrayList<>();//获取到品牌的聚合ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");for (Terms.Bucket bucket : brandAgg.getBuckets()) {SearchResult.BrandVo brandVo = new SearchResult.BrandVo();//1、得到品牌的idlong brandId = bucket.getKeyAsNumber().longValue();brandVo.setBrandId(brandId);//2、得到品牌的名字ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();brandVo.setBrandName(brandName);//3、得到品牌的图片ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();brandVo.setBrandImg(brandImg);brandVos.add(brandVo);}result.setBrands(brandVos);//4、当前商品涉及到的所有分类信息//获取到分类的聚合List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");for (Terms.Bucket bucket : catalogAgg.getBuckets()) {SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();//得到分类idString keyAsString = bucket.getKeyAsString();catalogVo.setCatalogId(Long.parseLong(keyAsString));//得到分类名ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();catalogVo.setCatalogName(catalogName);catalogVos.add(catalogVo);}result.setCatalogs(catalogVos);//===============以上可以从聚合信息中获取====================////5、分页信息-页码result.setPageNum(param.getPageNum());//5、1分页信息、总记录数long total = hits.getTotalHits().value;result.setTotal(total);//5、2分页信息-总页码-计算int totalPages = (int)total % EsConstant.PRODUCT_PAGESIZE == 0 ?(int)total / EsConstant.PRODUCT_PAGESIZE : ((int)total / EsConstant.PRODUCT_PAGESIZE + 1);result.setTotalPages(totalPages);List<Integer> pageNavs = new ArrayList<>();for (int i = 1; i <= totalPages; i++) {pageNavs.add(i);}result.setPageNavs(pageNavs);//6、构建面包屑导航if (param.getAttrs() != null && param.getAttrs().size() > 0) {List<SearchResult.NavVo> collect = param.getAttrs().stream().map(attr -> {//1、分析每一个attrs传过来的参数值SearchResult.NavVo navVo = new SearchResult.NavVo();String[] s = attr.split("_");navVo.setNavValue(s[1]);R r = productFeignService.attrInfo(Long.parseLong(s[0]));if (r.getCode() == 0) {AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {});navVo.setNavName(data.getAttrName());} else {navVo.setNavName(s[0]);}//2、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空//拿到所有的查询条件,去掉当前String encode = null;try {encode = URLEncoder.encode(attr,"UTF-8");encode.replace("+","%20");  //浏览器对空格的编码和Java不一样,差异化处理} catch (UnsupportedEncodingException e) {e.printStackTrace();}String replace = param.get_queryString().replace("&attrs=" + attr, "");navVo.setLink("http://search.gulimall.com/list.html?" + replace);return navVo;}).collect(Collectors.toList());result.setNavs(collect);}return result;}/*** 准备检索请求* 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析* @return*/private SearchRequest buildSearchRequest(SearchParam param) {SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();/*** 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)*///1. 构建bool-queryBoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();//1.1 bool-mustif(!StringUtils.isEmpty(param.getKeyword())){boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));}//1.2 bool-fiter//1.2.1 catelogIdif(null != param.getCatalog3Id()){boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));}//1.2.2 brandIdif(null != param.getBrandId() && param.getBrandId().size() >0){boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));}//1.2.3 attrsif(param.getAttrs() != null && param.getAttrs().size() > 0){param.getAttrs().forEach(item -> {//attrs=1_5寸:8寸&2_16G:8GBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//attrs=1_5寸:8寸String[] s = item.split("_");String attrId=s[0];String[] attrValues = s[1].split(":");//这个属性检索用的值boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);boolQueryBuilder.filter(nestedQueryBuilder);});}//1.2.4 hasStockif(null != param.getHasStock()){boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));}//1.2.5 skuPriceif(!StringUtils.isEmpty(param.getSkuPrice())){//skuPrice形式为:1_500或_500或500_RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");String[] price = param.getSkuPrice().split("_");if(price.length==2){rangeQueryBuilder.gte(price[0]).lte(price[1]);}else if(price.length == 1){if(param.getSkuPrice().startsWith("_")){rangeQueryBuilder.lte(price[1]);}if(param.getSkuPrice().endsWith("_")){rangeQueryBuilder.gte(price[0]);}}boolQueryBuilder.filter(rangeQueryBuilder);}//封装所有的查询条件searchSourceBuilder.query(boolQueryBuilder);/*** 排序,分页,高亮*///排序//形式为sort=hotScore_asc/descif(!StringUtils.isEmpty(param.getSort())){String sort = param.getSort();String[] sortFileds = sort.split("_");SortOrder sortOrder="asc".equalsIgnoreCase(sortFileds[1])?SortOrder.ASC:SortOrder.DESC;searchSourceBuilder.sort(sortFileds[0],sortOrder);}//分页searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);//高亮if(!StringUtils.isEmpty(param.getKeyword())){HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("skuTitle");highlightBuilder.preTags("<b style='color:red'>");highlightBuilder.postTags("</b>");searchSourceBuilder.highlighter(highlightBuilder);}/*** 聚合分析*///1. 按照品牌进行聚合TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");brand_agg.field("brandId").size(50);//1.1 品牌的子聚合-品牌名聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));//1.2 品牌的子聚合-品牌图片聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));searchSourceBuilder.aggregation(brand_agg);//2. 按照分类信息进行聚合TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");catalog_agg.field("catalogId").size(20);catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));searchSourceBuilder.aggregation(catalog_agg);//2. 按照属性信息进行聚合NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");//2.1 按照属性ID进行聚合TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");attr_agg.subAggregation(attr_id_agg);//2.1.1 在每个属性ID下,按照属性名进行聚合attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));//2.1.1 在每个属性ID下,按照属性值进行聚合attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));searchSourceBuilder.aggregation(attr_agg);log.debug("构建的DSL语句 {}",searchSourceBuilder.toString());SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);return searchRequest;}
}

ElasticSearch-整合SpringBoot相关推荐

  1. Elasticsearch整合Springboot实现基本的全文检索

    本教程仅做个人工作笔记,可能不适用于他人的工作/学习 写在前面 实际生产中,除了考虑产品的性能跟用户体验之外,生产成本也是要考虑的,如果系统是小系统,想对某一个表(200w)做全文检索的话,可以考虑使 ...

  2. elasticsearch整合springBoot

    elasticsearch的安装请参考:https://blog.csdn.net/qq_42410605/article/details/97884456 elasticsearch插件head的安 ...

  3. ElasticSearch整合SpringBoot的API操作

    在我们熟悉了 es 的基本rest 的操作之后,我们将使用SpringBoot进行整合,进一步熟悉Java API的相关操作. 1.创建一个标准的Springboot项目,引入Boot相关依赖之后,还 ...

  4. 关于ElasticSearch整合SpringBoot

    首先导入需要的依赖:这里要注意导入的Jest版本号和你的elasticsearch版本号在同一大版本,我的ES是6.5.4,所以这里我用的6版本的jest docker run -e ES_JAVA_ ...

  5. es springboot 不设置id_es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程详解

    注意:适用于springboot或者springcloud框架 1.首先下载相关文件 2.然后需要去启动相关的启动文件 3.导入相关jar包(如果有相关的依赖包不需要导入)以及配置配置文件,并且写一个 ...

  6. 好玩的ES--第三篇之过滤查询,整合SpringBoot

    好玩的ES--第三篇之过滤查询,整合SpringBoot 过滤查询 过滤查询 使用 类型 term . terms Filter ranage filter exists filter ids fil ...

  7. 使用Gradle整合SpringBoot+Vue.js-开发调试与打包

    为什么80%的码农都做不了架构师?>>>    非常感谢两位作者: kevinz分享的文章<springboot+gradle+vue+webpack 组合使用> 首席卖 ...

  8. 微信公众号授权步骤详细步骤介绍和整合springboot开发(java版)

    文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源. 一.微信公众号授权步骤 首先到微信公众平台注册账号,可以看到有四种类型(服务号,订阅号,小程 ...

  9. spring日志报错提醒_使用爬虫框架htmlunit整合springboot出现的一个不兼容问题

    使用爬虫框架htmlunit整合springboot不兼容的一个问题 本来使用htmlunit爬虫爬取数据非常正常好用,之前一直是直接java程序或者整合Javaswing界面,都没有问题,但是后来整 ...

  10. java kafka client_Kafka Java Client基本使用及整合SpringBoot

    kafka-clients 添加依赖 org.apache.kafka kafka-clients 2.5.0 消费者 Consumer 代码上总体可以分为三部分:消费者的配置消费者的配置在 org. ...

最新文章

  1. 这些SpringBoot天生自带Buff工具类你都用过哪些?
  2. 互联网+”时代,如何实现高效协同移动办公?
  3. Ansj中文分词Java开发小记
  4. 支付宝红包强攻微信社交,臆想出来的豪门恩怨
  5. 特征工程(3):特征选择—信息增益
  6. linux rc.d的作用,init.d,rc.d详解 Linux运行时详解
  7. [物理学与PDEs]第1章第3节 真空中的 Maxwell 方程组, Lorentz 力 3.1 真空中的 Maxwell 方程组...
  8. 搭建elasticsearch测试工程
  9. IOS基础之计算器的编写
  10. 一阶系统单位阶跃响应的特点_一阶系统的阶跃响应有什么特点
  11. 橱窗布置(信息学奥赛一本通-T1279)
  12. 别想赖账,今天微信又可以发520红包了
  13. JAR包中读取资源文件
  14. 认识position=fixed
  15. python处理pdf 层_Python处理PDF及生成多层PDF
  16. Postman下载教程
  17. 腾讯云微服务引擎 TSE 11月产品动态
  18. 抽象语法树(AST)
  19. 健壮的网络编程IO函数-RIO包
  20. 如何实现脑肿瘤红黄绿分割展示

热门文章

  1. 中秋qq表情包 2004 瑞星杀毒软件2007
  2. 手把手教你批量制作MV连播视频
  3. Redis:我是如何与客户端进行通信的
  4. Android体育类游戏有哪些,体验真实的体育 Android竞技游戏推荐
  5. close() was never explicitly called on database 和 database is locked 错误原因
  6. linux centos amd显卡驱动,【14-12-04】 【分享】关于CentOS 7联想电脑AMD显卡驱动的问题...
  7. 社区版PyCharm安装Django(中文版)
  8. Lattice Propel在线Debug/Run
  9. Java后端社招3年
  10. springboot邮件发送以及thyemleaf生成邮件模板