仅涉及后端,全部目录看顶部专栏,代码、文档、接口路径在:
【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客
全篇只介绍重点架构逻辑,具体编写看源代码就行,读起来也不复杂~
谨慎:源代码中有一些注释是错误的,有的注释意思完全相反,有的注释对不上号,我在阅读过程中就顺手更新了,并且在我不会的地方添加了新的注释,所以在读源代码过程中一定要谨慎啊!
目录
A1.ES检索
B1.ES基本搭建
B2.更新系统日志的ES存储搭建(关联No2-3)
C1.ElasticsearchRepository操作ES方式
C2.ElasticsearchOperations操作ES方式
C3.直接使用RestHighLevelClient操作ES方式(待更新)
B3.es分词查询(待更新)
剩余内容:消息中间件AMQP、定时任务等
ES学习可以看这篇文章,特别详细:ElasticSearch从入门到精通,史上最全(持续更新,未完待续,每天一点点)
我用的苹果本搭建的es和Kibana,安转时用brew安装一直报错(提示版本问题,我的本版本低了安装不了最新的),之后就直接去官网下载对应的7.3的版本的,然后直接在 bin 里面运行的,也挺方便的。
系统使用的是spring整合的es ,spring-data-elasticsearch,用的版本是Elasticsearch7.x的,这个版本较7.x之前使用上有些改动,虽然我没有用过之前的,但是为了防止使用混淆,还是了解了一下。
其中有一点要清楚的是spring-data-elasticsearch封装操作ES有两种方式,最终都是使用es本身的RestHighLevelClient进行的操作~
能使用RestHighLevelClient尽量使用它。
主要原因是灵活性和更新速度,Spring 将 ElasticSearch 过度封装,让开发者很难跟 ES 的 DSL 查询语句进行关联。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比较缓慢。
具体的看这篇文章,挺清晰的:springboot2.3配置与使用elasticsearch7.x
(1.ElasticsearchRepository;2.ElasticsearchOperations;3.RestHighLevelClient)
springboot本身就自带了spring-boot-starter-data-elasticsearch依赖包,我们就不用依赖了。
该系统使用的也是7.x以上版本的es,这个的配置项就不能直接使用spring的了,已经过时了,所以我们就只需要配置RestHighLevelClient的bean和ElasticsearchOperations的bean就好了,我们需要给出自定义的Properties,当然如果直接写在配置类里也行,就是不建议~
1.在frameword模块中创建Elasticsearch所需要的参数类,并在业务模块中的 yml 里配置参数项;
2.添加 elasticsearch 配置,创建RestHighLevelClient bean;
# 1.在frameword模块中创建Elasticsearch所需要的参数类,并在业务模块中的 yml 里配置参数项;
//参数类,详见:cn.lili.elasticsearch.config.ElasticsearchProperties
#举例 management-api 的yml配置 /lilishop-master/manager-api/src/main/resources/application.yml
lili:
data:
# 自定义 elasticsearch 配置
elasticsearch:
# cluster集群
cluster-name: elasticsearch
# 当前连接节点所属集群的配置信息
cluster-nodes: 127.0.0.1:9200
index:
# 默认分片的副本数
number-of-replicas: 0
# 默认主分片数
number-of-shards: 3
# 索引前缀
index-prefix: lili
# 协议
scheme: http
# account:
# username: elastic
# password: LiLiShopES
//2.添加 elasticsearch 配置,创建RestHighLevelClient bean;
//详见:cn.lili.elasticsearch.config.ElasticsearchConfig
@Slf4j
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Autowired
private ElasticsearchProperties elasticsearchProperties;
private RestHighLevelClient client;
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
RestClientBuilder restBuilder = RestClient
.builder(this.getHttpHosts());
//修改 HTTP 客户端通信,例如添加压缩或加密层。
restBuilder.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder
//自定义连接存活策略
.setKeepAliveStrategy(getConnectionKeepAliveStrategy())
.setMaxConnPerRoute(10).
setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(1).build()));
String username = elasticsearchProperties.getAccount().getUsername();
String password = elasticsearchProperties.getAccount().getPassword();
//如果有认证用户,就添加用户信息
if (username != null && password != null) {
final CredentialsProvider credential = new BasicCredentialsProvider();
credential.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
//修改 HTTP 客户端通信,例如添加压缩或加密层。
restBuilder.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder
.setDefaultCredentialsProvider(credential)
//自定义连接存活策略
.setKeepAliveStrategy(getConnectionKeepAliveStrategy())
.setMaxConnPerRoute(10)
.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(Runtime.getRuntime().availableProcessors()).build()));
}
//配置请求身份验证、超时和其他可以在请求级别设置的属性。
restBuilder.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder.setConnectTimeout(1000) //time until a connection with the server is established.
.setSocketTimeout(12 * 1000) //time of inactivity to wait for packets[data] to receive.
.setConnectionRequestTimeout(-1)); //time to fetch a connection from the connection pool 0 for infinite.
// 创建ES客户端对象
client = new RestHighLevelClient(restBuilder);
return client;
}
/**
* @Description: 配置集群服务器域名/ip
* @param: []
* @return: org.apache.http.HttpHost[]
**/
private HttpHost[] getHttpHosts() {
List clusterNodes = elasticsearchProperties.getClusterNodes();
HttpHost[] httpHosts = new HttpHost[clusterNodes.size()];
for (int i = 0; i < clusterNodes.size(); i++) {
String[] node = clusterNodes.get(i).split(":");
httpHosts[i] = new HttpHost(node[0], Convert.toInt(node[1]), elasticsearchProperties.getScheme());
}
return httpHosts;
}
private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
return (response, context) -> 2 * 60 * 1000;
}
/**
* it gets called when bean instance is getting removed from the context if
* scope is not a prototype
* If there is a method named shutdown or close then spring container will try
* to automatically configure them as callback methods when bean is being
* destroyed
*/
@PreDestroy
public void clientClose() {
try {
this.client.close();
} catch (IOException e) {
log.error("es clientClose错误", e);
}
}
}
我们在 config 里面创建了 RestHighLevelClient bean ,同时 config 又实现了 AbstractElasticsearchConfiguration ,在这个里面又创建了ElasticsearchOperations。
之后使用 ElasticsearchOperations 正常注入使用就好啦,见下面B2
ElasticsearchRepository也可以正常实现使用,见下面B2
之前No2-3里面系统日志框架中我们已经写了 service 类了,现在需要将具体的日志存储给加上。
集成ElasticsearchRepository方式就相当于 mps 的BaseMapper,也需要对应的实体类,实体类里面需要使用@Document标注索引。然后直接创建service类继承ElasticsearchRepository,然后直接使用就可以。
这种方式对于增删改比较方便~
1.系统日志实体类;要加上@Document标注索引,否则使用时会报错
2.系统日志Repository类,实现ElasticsearchRepository类并泛型指定为系统日志实体类;
3.系统日志业务类;
//1.系统日志实体类;要加上@Document标注索引,否则使用时会报错
//详见:cn.lili.modules.permission.entity.vo.SystemLogVO
@Data
//加上了@Document注解之后,默认情况下这个实体中所有的属性都会被建立索引、并且分词
//indexName 索引库的名称,个人建议以项目的名称命名;
@Document(indexName = "#{@elasticsearchProperties.indexPrefix}_" + EsSuffix.LOGS_INDEX_NAME)
@ToString
@NoArgsConstructor
public class SystemLogVO implements Serializable {
。。。
}
//2.系统日志Repository类,实现ElasticsearchRepository类;
//详见:cn.lili.modules.permission.entity.vo.SystemLogVO
/**
* 日志
*
* @author paulG
* @since 2021/12/13
**/
public interface SystemLogRepository extends ElasticsearchRepository {
}
//2.系统日志业务类;
//详见:cn.lili.modules.permission.service.SystemLogService
//详见:cn.lili.modules.permission.serviceimpl.SystemLogServiceImpl
//举例,
@Service
public class SystemLogServiceImpl implements SystemLogService {
@Autowired
private SystemLogRepository systemLogRepository;
/**
* ES
*/
@Autowired
private ElasticsearchOperations restTemplate;
@Override
public void saveLog(SystemLogVO systemLogVO) {
systemLogRepository.save(systemLogVO);
}
。。。
}
此时就可以直接调用管理员登录接口 /manager/passport/user/login 了,调用过程中会经过系统日志切面类,进而在保存日志的线程类中执行系统日志save的业务。
登录成功后,就直接去 Kibana里面搜索查看,就能查看到当前日志啦
ElasticsearchOperations方式更简单,由于我们已经通过config 创建了该 bean,所以直接在业务类中注入该bean就行,然后直接使用~
这种方式对于查询较方便
还是在SystemLogServiceImpl系统日志业务类中使用,查询日志列表;
//详见:cn.lili.modules.permission.serviceimpl.SystemLogServiceImpl
@Service
public class SystemLogServiceImpl implements SystemLogService {
@Autowired
private SystemLogRepository systemLogRepository;
/**
* ES
*/
@Autowired
private ElasticsearchOperations restTemplate;
@Override
public IPage queryLog(String storeId, String operatorName, String key, SearchVO searchVo, PageVO pageVO) {
pageVO.setNotConvert(true);
IPage iPage = new Page<>();
//查询条件构造器,可以往里面添加一些过滤或者其他条件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
if (pageVO.getPageNumber() != null && pageVO.getPageSize() != null) {
int pageNumber = pageVO.getPageNumber() - 1;
if (pageNumber < 0) {
pageNumber = 0;
}
Pageable pageable = PageRequest.of(pageNumber, pageVO.getPageSize());
//添加分页条件
nativeSearchQueryBuilder.withPageable(pageable);
iPage.setCurrent(pageVO.getPageNumber());
iPage.setSize(pageVO.getPageSize());
}
if (CharSequenceUtil.isNotEmpty(storeId)) {
//添加查询条件
nativeSearchQueryBuilder.withFilter(QueryBuilders.matchQuery("storeId", storeId));
}
if (CharSequenceUtil.isNotEmpty(operatorName)) {
//添加查询条件
nativeSearchQueryBuilder.withFilter(QueryBuilders.wildcardQuery("username", "*" + operatorName + "*"));
}
if (CharSequenceUtil.isNotEmpty(key)) {
BoolQueryBuilder filterBuilder = new BoolQueryBuilder();
filterBuilder.should(QueryBuilders.wildcardQuery("requestUrl", "*" + key + "*"))
.should(QueryBuilders.wildcardQuery("requestParam", "*" + key + "*"))
.should(QueryBuilders.wildcardQuery("responseBody", "*" + key + "*"))
.should(QueryBuilders.wildcardQuery("name", "*" + key + "*"));
//添加查询条件
nativeSearchQueryBuilder.withFilter(filterBuilder);
}
//时间有效性判定
if (searchVo.getConvertStartDate() != null && searchVo.getConvertEndDate() != null) {
BoolQueryBuilder filterBuilder = new BoolQueryBuilder();
//大于方法
filterBuilder.must(
QueryBuilders.rangeQuery("createTime")
.gte(DateUtil.format(searchVo.getConvertStartDate(), "dd/MM/yyyy"))
.lte(DateUtil.format(searchVo.getConvertEndDate(), "dd/MM/yyyy")).format("dd/MM/yyyy||yyyy"));
//添加查询条件
nativeSearchQueryBuilder.withFilter(filterBuilder);
}
if (CharSequenceUtil.isNotEmpty(pageVO.getOrder()) && CharSequenceUtil.isNotEmpty(pageVO.getSort())) {
//添加排序条件
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(pageVO.getSort()).order(SortOrder.valueOf(pageVO.getOrder().toUpperCase())));
} else {
//添加排序条件
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
}
//执行查询
SearchHits searchResult = restTemplate.search(nativeSearchQueryBuilder.build(), SystemLogVO.class);
iPage.setTotal(searchResult.getTotalHits());
iPage.setRecords(searchResult.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()));
return iPage;
}
}
注意,这里在正常业务中需要添加两个 VO ,接收前端请求传过来的日期搜索参数SearchVO和分页查询参数PageVO
【如果现在添加了,会有一系列工具类需要处理,需要添加自定义DateUtil、StringUtils】
//日期搜索参数SearchVO,详见:cn.lili.common.vo.SearchVO
//分页查询参数PageVO,详见:cn.lili.common.vo.PageVO
添加好controller、service后,调用接口执行~记得添加token
这种更适用于查询。并且是比较推荐的。会涉及到高亮等等
我们在后面商品查询时会用到,这里先略过