1. ES服務(wù)搭建
前臺(tái)頁(yè)面很多場(chǎng)景下,都需要用到搜索遭垛,ElasticSearch作為分布式全文搜索引擎尼桶,使用restful api對(duì)文檔操作,我們要集成ES耻卡,把它作為一個(gè)獨(dú)立的服務(wù)疯汁!使其他需要集成搜索功能的服務(wù)模塊,只需要通過(guò)feign向ES服務(wù)發(fā)起調(diào)用即可卵酪!
①:創(chuàng)建es服務(wù)模塊:
? hrm-es-parent
? hrm-es-common
? hrm-es-feign
? hrm-es-server-2050
②:集成注冊(cè)中心
③:集成配置中心
④:集成swagger
⑤:集成zuul網(wǎng)關(guān)
⑥:zuul整合swagger
2. ES與SpringBoot的集成
- 導(dǎo)入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- 創(chuàng)建配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/ #注冊(cè)中心服務(wù)端的注冊(cè)地址
instance:
prefer-ip-address: true #使用ip進(jìn)行注冊(cè)
instance-id: es-server:2050 #服務(wù)注冊(cè)到注冊(cè)中心的id
server:
port: 2050
#應(yīng)用的名字
spring:
application:
name: es-server
data:
elasticsearch:
cluster-name: elasticsearch #集群的名字
cluster-nodes: 127.0.0.1:9300 #9200是圖形界面端,9300代碼端
ribbon: #ribbon超時(shí)
ReadTimeout: 30000
ConnectTimeout: 30000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 40000
- 創(chuàng)建一個(gè)類用來(lái)描述文檔信息
@Document(indexName = "hrm",type = "course")
public class CourseDoc {
//文檔的ID幌蚊,同時(shí)也是數(shù)據(jù)的id
@Id
private Long id;
//標(biāo)題
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String name;
//適用人群
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String users;
//課程類型ID
@Field(type = FieldType.Long)
private Long courseTypeId;
//等級(jí)名字
//@Field(type = FieldType.Keyword)
private String gradeName;
//課程等級(jí)
private Long gradeId;
//機(jī)構(gòu)id
private Long tenantId;
//機(jī)構(gòu)名字
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String tenantName;
//開(kāi)課時(shí)間
private Date startTime;
//結(jié)課時(shí)間
private Date endTime;
//封面
private String pic;
//免費(fèi)、收費(fèi)
private String chargeName;
//qq
private String qq;
//價(jià)格
private Float price;
//原價(jià)
private Float priceOld;
//課程介紹
private String description;
//上線時(shí)間
private Date onlineDate = new Date();
//瀏覽數(shù)
private Integer viewCount;
//購(gòu)買數(shù)
private Integer buyCount;
...
@Document(indexName = "hrm",type = "course"):描述文檔信息,indexName:索引庫(kù),type:文檔類型卷仑。
@Id:文檔的ID窜锯,同時(shí)也是數(shù)據(jù)的id
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word"):描述字段信息驾孔,F(xiàn)ieldType.Text:做分詞處理眉菱,F(xiàn)ieldType.Keyword:不做分詞處理华坦,analyzer:使用什么分詞器做分詞歹袁,searchAnalyzer:使用什么分詞器做查詢孟抗!
- 創(chuàng)建一個(gè)接口繼承ElasticsearchRepository,其中有對(duì)文檔操作的所有方法!
@Repository
public interface CourseDocRepository extends ElasticsearchRepository<CourseDoc,Long> {
}
- 使用SpringBoot的測(cè)試環(huán)境測(cè)試CRUD
@RunWith(SpringRunner.class)
@SpringBootTest
public class CourseDocTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Autowired
private CourseDocRepository courseDocRepository;
@Test
public void test(){
elasticsearchTemplate.createIndex(CourseDoc.class);
elasticsearchTemplate.putMapping(CourseDoc.class);
}
@Test
public void testAddDoc(){
for(int i = 0; i< 20 ; i++){
CourseDoc courseDoc = new CourseDoc();
courseDoc.setId(Long.valueOf(1+i));
if(i % 2 == 0){
courseDoc.setName("Java從入門到超神");
}else{
courseDoc.setName("PHP從入門到放棄");
}
if(i % 3 == 0){
courseDoc.setGradeName("神級(jí)");
}else{
courseDoc.setGradeName("低級(jí)");
}
courseDoc.setCourseTypeId(Long.valueOf(1+i));
courseDocRepository.save(courseDoc);
}
}
@Test
public void testGetDoc(){
Optional<CourseDoc> optional = courseDocRepository.findById(23l);
if(optional!=null){
CourseDoc courseDoc = optional.get();
System.out.println(courseDoc);
}
}
@Test
public void testGetAll(){
Iterator<CourseDoc> iterator = courseDocRepository.findAll().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void testUpdata(){
CourseDoc courseDoc = new CourseDoc();
courseDoc.setId(23l);
courseDoc.setName("JAVA");
courseDocRepository.save(courseDoc);
}
@Test
public void testDelete(){
courseDocRepository.deleteById(23l);
}
ElasticsearchTemplate是es的內(nèi)置對(duì)象,可以用來(lái)創(chuàng)建索引庫(kù),和指定映射規(guī)則!
高級(jí)查詢:
使用關(guān)鍵對(duì)象NativeSearchQueryBuilder構(gòu)建查詢規(guī)則!
@Test
public void testQuery(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
/**
* 高級(jí)查詢分頁(yè)排序: DSL查詢+DSL過(guò)濾
* // 需求:查詢 name中包含 “Java”的課程缕陕,
* // 并且課程分類 CourseTypeId 在 1 - 10
* //課程等級(jí) GradeName為“神級(jí)”
* //查詢第2頁(yè)恶座,每頁(yè)2條
* //按照id倒排序
*/
//查詢 name中包含 “Java”的課程
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(new MatchQueryBuilder("name", "java"));
builder.withQuery(boolQueryBuilder);
//并且課程分類 CourseTypeId 在 1 - 10
boolQueryBuilder.filter(new RangeQueryBuilder("courseTypeId").gte(1).lte(20));
//課程等級(jí) GradeName為“神級(jí)”
boolQueryBuilder.filter(new TermQueryBuilder("gradeName", "神級(jí)"));
//按照id倒排序
builder.withSort(new FieldSortBuilder("id").order(SortOrder.DESC));
//查詢第1頁(yè),每頁(yè)5條
builder.withPageable(PageRequest.of(0, 5));
//執(zhí)行查詢
Page<CourseDoc> docs = courseDocRepository.search(builder.build());
System.out.println("總條數(shù):"+docs.getTotalElements());
System.out.println("查詢的頁(yè)數(shù):"+docs.getTotalPages());
docs.getContent().forEach(e-> System.out.println("======"+e));
Iterator<CourseDoc> iterator = docs.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
3. ES與Feign的集成實(shí)現(xiàn)課程的上線下線
上線場(chǎng)景:
前臺(tái)用戶點(diǎn)擊課程上線碴开,請(qǐng)求到達(dá)控制器巴碗,控制器執(zhí)行業(yè)務(wù)凹嘲!
- 根據(jù)傳過(guò)來(lái)的課程id去數(shù)據(jù)庫(kù)中查詢出來(lái)
- 判斷是否是下線狀態(tài)
- 如果是下線狀態(tài)通過(guò)feign調(diào)用es服務(wù),將數(shù)據(jù)保存到es中
- 修改數(shù)據(jù)庫(kù)中課程的狀態(tài)碼
下線場(chǎng)景:
前臺(tái)用戶點(diǎn)擊課程下線,請(qǐng)求到達(dá)控制器,控制器執(zhí)行業(yè)務(wù)!
- 根據(jù)傳過(guò)來(lái)的課程id去數(shù)據(jù)庫(kù)中查詢出來(lái)
- 判斷是否是上線狀態(tài)
- 如果是上線狀態(tài)通過(guò)feign調(diào)用es服務(wù)抓督,將數(shù)據(jù)從es中刪除
- 修改數(shù)據(jù)庫(kù)中課程的狀態(tài)碼
1:controller
/**
* 課程上線
* @param id
* @return
*/
@PostMapping("/onLineCourse/{id}")
public AjaxResult onLineCourse(@PathVariable Long id){
try {
courseService.onLineCourse(id);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("課程上線失斄逶凇阵具!"+e.getMessage());
}
}
/**
*課程下線
* @param
* @return
*/
@PostMapping("/offLineCourse/{id}")
public AjaxResult offLineCourse(@PathVariable Long id){
try {
courseService.offLineCourse(id);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("課程下線失敶Э弧!"+e.getMessage());
}
}
2:es與feign的集成
①:導(dǎo)包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
②:創(chuàng)建feign的controller桑驱,接收請(qǐng)求的入口!
@RestController
@RequestMapping("/es")
public class EScontroller {
@Autowired
private CourseDocRepository courseDocRepository;
@GetMapping("/get/{id}")
public AjaxResult deleteById(@PathVariable("id") Long id){
courseDocRepository.deleteById(id);
return AjaxResult.me();
}
@PostMapping("/save")
public AjaxResult save(@RequestBody CourseDoc courseDoc){
courseDocRepository.save(courseDoc);
return AjaxResult.me();
}
}
③:創(chuàng)建feign的接口,分發(fā)請(qǐng)求到controller的具體方法
@FeignClient(value = "es-server" ,fallbackFactory = ESFeignFallbackFactory.class)
public interface ESFeignClient{
@GetMapping("/es/get/{id}")
AjaxResult deleteById(@PathVariable("id") Long id);
@PostMapping("/es/save")
AjaxResult save(@RequestBody CourseDoc courseDoc);
}
④:創(chuàng)建托底類谴分,調(diào)用鏈發(fā)生異常后Hystrix返回托底數(shù)據(jù)锈麸!
@Component
public class ESFeignFallbackFactory implements FallbackFactory<ESFeignClient> {
@Override
public ESFeignClient create(Throwable throwable) {
return new ESFeignClient() {
@Override
public AjaxResult deleteById(Long id) {
throwable.printStackTrace();
return AjaxResult.me().setSuccess(false)
.setMessage("發(fā)生了一點(diǎn)小問(wèn)題:["+throwable.getMessage()+"]");
}
@Override
public AjaxResult save(CourseDoc courseDoc) {
throwable.printStackTrace();
return AjaxResult.me().setSuccess(false)
.setMessage("發(fā)生了一點(diǎn)小問(wèn)題:["+throwable.getMessage()+"]");
}
};
}
}
⑤:消費(fèi)者微服務(wù)開(kāi)啟feign(誰(shuí)調(diào)用誰(shuí)開(kāi)啟),課程微服務(wù)開(kāi)啟feign
@SpringBootApplication
@MapperScan("com.hanfengyi.course.mapper")
@EnableTransactionManagement
@EnableFeignClients("com.hanfengyi.feign")
public class CourseService2020 {
public static void main(String[] args) {
SpringApplication.run(CourseService2020.class);
}
}
@EnableFeignClients("com.hanfengyi.feign"):注意這里feign的包名要與提供者微服務(wù)的集成feign的包名要一致牺蹄,如果不一致忘伞,要指定包名!
⑥:課程業(yè)務(wù)層執(zhí)行的業(yè)務(wù)
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements ICourseService {
@Autowired
private CourseDetailMapper courseDetailMapper;
@Autowired
private CourseMarketMapper courseMarketMapper;
@Autowired
private ESFeignClient esFeignClient;
@Override
public void offLineCourse(Long id) {
//1. 根據(jù)id查詢課程
Course course = baseMapper.selectById(id);
//2. 判斷課程狀態(tài)是否是上線狀態(tài)
if(course!=null && course.getStatus()==1){
//3. 刪除es中的數(shù)據(jù)
AjaxResult ajaxResult = esFeignClient.deleteById(id);
//es中數(shù)據(jù)刪除成功
if(ajaxResult.isSuccess()){
//4. 修改mysql中的課程中的課程狀態(tài)為下線狀態(tài)
course.setStatus(Course.COURSE_OFFLINE);
baseMapper.updateById(course);
}
}else{
throw new RuntimeException("課程不存在或者課程已處于下線狀態(tài)");
}
}
@Override
public void onLineCourse(Long id) {
//1. 根據(jù)id查詢課程
Course course = baseMapper.selectById(id);
//2. 判斷課程狀態(tài)是否是下線狀態(tài)
if(course!=null && course.getStatus()==0){
//3. 封裝CourseDoc對(duì)象
CourseDoc courseDoc = new CourseDoc();
//使用工具類拷貝屬性
BeanUtils.copyProperties(course, courseDoc);
CourseDetail courseDetail = courseDetailMapper.selectById(course.getId());
BeanUtils.copyProperties(courseDetail, courseDoc);
CourseMarket courseMarket = courseMarketMapper.selectById(course.getId());
courseDoc.setChargeName(courseMarket.getCharge().longValue()==1?"收費(fèi)":"免費(fèi)");
BeanUtils.copyProperties(courseMarket, courseDoc);
//4. 保存到es中
AjaxResult ajaxResult = esFeignClient.save(courseDoc);
//es中數(shù)據(jù)保存成功
if(ajaxResult.isSuccess()){
//5. 修改mysql中的課程中的課程狀態(tài)為上線狀態(tài)
course.setStatus(Course.COURSE_ONLINE);
baseMapper.updateById(course);
}
}else{
throw new RuntimeException("課程不存在或者課程已處于上線狀態(tài)");
}
}
}
4. zuul的饑餓加載配置
zuul:
ignoredServices: '*' #忽略使用服務(wù)名訪問(wèn)
ribbon:
eager-load:
enabled: true
5. ribbon的饑餓加載配置
ribbon:
eager-load:
enabled: true
clients: es-server,redis-server,fastdfs-server #指定ribbon調(diào)用的服務(wù)名